3750 lines
115 KiB
JavaScript
3750 lines
115 KiB
JavaScript
var check = require('./util/check')
|
|
var extend = require('./util/extend')
|
|
var createEnvironment = require('./util/codegen')
|
|
var loop = require('./util/loop')
|
|
var isTypedArray = require('./util/is-typed-array')
|
|
var isNDArray = require('./util/is-ndarray')
|
|
var isArrayLike = require('./util/is-array-like')
|
|
var dynamic = require('./dynamic')
|
|
|
|
var primTypes = require('./constants/primitives.json')
|
|
var glTypes = require('./constants/dtypes.json')
|
|
|
|
// "cute" names for vector components
|
|
var CUTE_COMPONENTS = 'xyzw'.split('')
|
|
|
|
var GL_UNSIGNED_BYTE = 5121
|
|
|
|
var ATTRIB_STATE_POINTER = 1
|
|
var ATTRIB_STATE_CONSTANT = 2
|
|
|
|
var DYN_FUNC = 0
|
|
var DYN_PROP = 1
|
|
var DYN_CONTEXT = 2
|
|
var DYN_STATE = 3
|
|
var DYN_THUNK = 4
|
|
var DYN_CONSTANT = 5
|
|
var DYN_ARRAY = 6
|
|
|
|
var S_DITHER = 'dither'
|
|
var S_BLEND_ENABLE = 'blend.enable'
|
|
var S_BLEND_COLOR = 'blend.color'
|
|
var S_BLEND_EQUATION = 'blend.equation'
|
|
var S_BLEND_FUNC = 'blend.func'
|
|
var S_DEPTH_ENABLE = 'depth.enable'
|
|
var S_DEPTH_FUNC = 'depth.func'
|
|
var S_DEPTH_RANGE = 'depth.range'
|
|
var S_DEPTH_MASK = 'depth.mask'
|
|
var S_COLOR_MASK = 'colorMask'
|
|
var S_CULL_ENABLE = 'cull.enable'
|
|
var S_CULL_FACE = 'cull.face'
|
|
var S_FRONT_FACE = 'frontFace'
|
|
var S_LINE_WIDTH = 'lineWidth'
|
|
var S_POLYGON_OFFSET_ENABLE = 'polygonOffset.enable'
|
|
var S_POLYGON_OFFSET_OFFSET = 'polygonOffset.offset'
|
|
var S_SAMPLE_ALPHA = 'sample.alpha'
|
|
var S_SAMPLE_ENABLE = 'sample.enable'
|
|
var S_SAMPLE_COVERAGE = 'sample.coverage'
|
|
var S_STENCIL_ENABLE = 'stencil.enable'
|
|
var S_STENCIL_MASK = 'stencil.mask'
|
|
var S_STENCIL_FUNC = 'stencil.func'
|
|
var S_STENCIL_OPFRONT = 'stencil.opFront'
|
|
var S_STENCIL_OPBACK = 'stencil.opBack'
|
|
var S_SCISSOR_ENABLE = 'scissor.enable'
|
|
var S_SCISSOR_BOX = 'scissor.box'
|
|
var S_VIEWPORT = 'viewport'
|
|
|
|
var S_PROFILE = 'profile'
|
|
|
|
var S_FRAMEBUFFER = 'framebuffer'
|
|
var S_VERT = 'vert'
|
|
var S_FRAG = 'frag'
|
|
var S_ELEMENTS = 'elements'
|
|
var S_PRIMITIVE = 'primitive'
|
|
var S_COUNT = 'count'
|
|
var S_OFFSET = 'offset'
|
|
var S_INSTANCES = 'instances'
|
|
var S_VAO = 'vao'
|
|
|
|
var SUFFIX_WIDTH = 'Width'
|
|
var SUFFIX_HEIGHT = 'Height'
|
|
|
|
var S_FRAMEBUFFER_WIDTH = S_FRAMEBUFFER + SUFFIX_WIDTH
|
|
var S_FRAMEBUFFER_HEIGHT = S_FRAMEBUFFER + SUFFIX_HEIGHT
|
|
var S_VIEWPORT_WIDTH = S_VIEWPORT + SUFFIX_WIDTH
|
|
var S_VIEWPORT_HEIGHT = S_VIEWPORT + SUFFIX_HEIGHT
|
|
var S_DRAWINGBUFFER = 'drawingBuffer'
|
|
var S_DRAWINGBUFFER_WIDTH = S_DRAWINGBUFFER + SUFFIX_WIDTH
|
|
var S_DRAWINGBUFFER_HEIGHT = S_DRAWINGBUFFER + SUFFIX_HEIGHT
|
|
|
|
var NESTED_OPTIONS = [
|
|
S_BLEND_FUNC,
|
|
S_BLEND_EQUATION,
|
|
S_STENCIL_FUNC,
|
|
S_STENCIL_OPFRONT,
|
|
S_STENCIL_OPBACK,
|
|
S_SAMPLE_COVERAGE,
|
|
S_VIEWPORT,
|
|
S_SCISSOR_BOX,
|
|
S_POLYGON_OFFSET_OFFSET
|
|
]
|
|
|
|
var GL_ARRAY_BUFFER = 34962
|
|
var GL_ELEMENT_ARRAY_BUFFER = 34963
|
|
|
|
var GL_FRAGMENT_SHADER = 35632
|
|
var GL_VERTEX_SHADER = 35633
|
|
|
|
var GL_TEXTURE_2D = 0x0DE1
|
|
var GL_TEXTURE_CUBE_MAP = 0x8513
|
|
|
|
var GL_CULL_FACE = 0x0B44
|
|
var GL_BLEND = 0x0BE2
|
|
var GL_DITHER = 0x0BD0
|
|
var GL_STENCIL_TEST = 0x0B90
|
|
var GL_DEPTH_TEST = 0x0B71
|
|
var GL_SCISSOR_TEST = 0x0C11
|
|
var GL_POLYGON_OFFSET_FILL = 0x8037
|
|
var GL_SAMPLE_ALPHA_TO_COVERAGE = 0x809E
|
|
var GL_SAMPLE_COVERAGE = 0x80A0
|
|
|
|
var GL_FLOAT = 5126
|
|
var GL_FLOAT_VEC2 = 35664
|
|
var GL_FLOAT_VEC3 = 35665
|
|
var GL_FLOAT_VEC4 = 35666
|
|
var GL_INT = 5124
|
|
var GL_INT_VEC2 = 35667
|
|
var GL_INT_VEC3 = 35668
|
|
var GL_INT_VEC4 = 35669
|
|
var GL_BOOL = 35670
|
|
var GL_BOOL_VEC2 = 35671
|
|
var GL_BOOL_VEC3 = 35672
|
|
var GL_BOOL_VEC4 = 35673
|
|
var GL_FLOAT_MAT2 = 35674
|
|
var GL_FLOAT_MAT3 = 35675
|
|
var GL_FLOAT_MAT4 = 35676
|
|
var GL_SAMPLER_2D = 35678
|
|
var GL_SAMPLER_CUBE = 35680
|
|
|
|
var GL_TRIANGLES = 4
|
|
|
|
var GL_FRONT = 1028
|
|
var GL_BACK = 1029
|
|
var GL_CW = 0x0900
|
|
var GL_CCW = 0x0901
|
|
var GL_MIN_EXT = 0x8007
|
|
var GL_MAX_EXT = 0x8008
|
|
var GL_ALWAYS = 519
|
|
var GL_KEEP = 7680
|
|
var GL_ZERO = 0
|
|
var GL_ONE = 1
|
|
var GL_FUNC_ADD = 0x8006
|
|
var GL_LESS = 513
|
|
|
|
var GL_FRAMEBUFFER = 0x8D40
|
|
var GL_COLOR_ATTACHMENT0 = 0x8CE0
|
|
|
|
var blendFuncs = {
|
|
'0': 0,
|
|
'1': 1,
|
|
'zero': 0,
|
|
'one': 1,
|
|
'src color': 768,
|
|
'one minus src color': 769,
|
|
'src alpha': 770,
|
|
'one minus src alpha': 771,
|
|
'dst color': 774,
|
|
'one minus dst color': 775,
|
|
'dst alpha': 772,
|
|
'one minus dst alpha': 773,
|
|
'constant color': 32769,
|
|
'one minus constant color': 32770,
|
|
'constant alpha': 32771,
|
|
'one minus constant alpha': 32772,
|
|
'src alpha saturate': 776
|
|
}
|
|
|
|
// There are invalid values for srcRGB and dstRGB. See:
|
|
// https://www.khronos.org/registry/webgl/specs/1.0/#6.13
|
|
// https://github.com/KhronosGroup/WebGL/blob/0d3201f5f7ec3c0060bc1f04077461541f1987b9/conformance-suites/1.0.3/conformance/misc/webgl-specific.html#L56
|
|
var invalidBlendCombinations = [
|
|
'constant color, constant alpha',
|
|
'one minus constant color, constant alpha',
|
|
'constant color, one minus constant alpha',
|
|
'one minus constant color, one minus constant alpha',
|
|
'constant alpha, constant color',
|
|
'constant alpha, one minus constant color',
|
|
'one minus constant alpha, constant color',
|
|
'one minus constant alpha, one minus constant color'
|
|
]
|
|
|
|
var compareFuncs = {
|
|
'never': 512,
|
|
'less': 513,
|
|
'<': 513,
|
|
'equal': 514,
|
|
'=': 514,
|
|
'==': 514,
|
|
'===': 514,
|
|
'lequal': 515,
|
|
'<=': 515,
|
|
'greater': 516,
|
|
'>': 516,
|
|
'notequal': 517,
|
|
'!=': 517,
|
|
'!==': 517,
|
|
'gequal': 518,
|
|
'>=': 518,
|
|
'always': 519
|
|
}
|
|
|
|
var stencilOps = {
|
|
'0': 0,
|
|
'zero': 0,
|
|
'keep': 7680,
|
|
'replace': 7681,
|
|
'increment': 7682,
|
|
'decrement': 7683,
|
|
'increment wrap': 34055,
|
|
'decrement wrap': 34056,
|
|
'invert': 5386
|
|
}
|
|
|
|
var shaderType = {
|
|
'frag': GL_FRAGMENT_SHADER,
|
|
'vert': GL_VERTEX_SHADER
|
|
}
|
|
|
|
var orientationType = {
|
|
'cw': GL_CW,
|
|
'ccw': GL_CCW
|
|
}
|
|
|
|
function isBufferArgs (x) {
|
|
return Array.isArray(x) ||
|
|
isTypedArray(x) ||
|
|
isNDArray(x)
|
|
}
|
|
|
|
// Make sure viewport is processed first
|
|
function sortState (state) {
|
|
return state.sort(function (a, b) {
|
|
if (a === S_VIEWPORT) {
|
|
return -1
|
|
} else if (b === S_VIEWPORT) {
|
|
return 1
|
|
}
|
|
return (a < b) ? -1 : 1
|
|
})
|
|
}
|
|
|
|
function Declaration (thisDep, contextDep, propDep, append) {
|
|
this.thisDep = thisDep
|
|
this.contextDep = contextDep
|
|
this.propDep = propDep
|
|
this.append = append
|
|
}
|
|
|
|
function isStatic (decl) {
|
|
return decl && !(decl.thisDep || decl.contextDep || decl.propDep)
|
|
}
|
|
|
|
function createStaticDecl (append) {
|
|
return new Declaration(false, false, false, append)
|
|
}
|
|
|
|
function createDynamicDecl (dyn, append) {
|
|
var type = dyn.type
|
|
if (type === DYN_FUNC) {
|
|
var numArgs = dyn.data.length
|
|
return new Declaration(
|
|
true,
|
|
numArgs >= 1,
|
|
numArgs >= 2,
|
|
append)
|
|
} else if (type === DYN_THUNK) {
|
|
var data = dyn.data
|
|
return new Declaration(
|
|
data.thisDep,
|
|
data.contextDep,
|
|
data.propDep,
|
|
append)
|
|
} else if (type === DYN_CONSTANT) {
|
|
return new Declaration(
|
|
false,
|
|
false,
|
|
false,
|
|
append)
|
|
} else if (type === DYN_ARRAY) {
|
|
var thisDep = false
|
|
var contextDep = false
|
|
var propDep = false
|
|
for (var i = 0; i < dyn.data.length; ++i) {
|
|
var subDyn = dyn.data[i]
|
|
if (subDyn.type === DYN_PROP) {
|
|
propDep = true
|
|
} else if (subDyn.type === DYN_CONTEXT) {
|
|
contextDep = true
|
|
} else if (subDyn.type === DYN_STATE) {
|
|
thisDep = true
|
|
} else if (subDyn.type === DYN_FUNC) {
|
|
thisDep = true
|
|
var subArgs = subDyn.data
|
|
if (subArgs >= 1) {
|
|
contextDep = true
|
|
}
|
|
if (subArgs >= 2) {
|
|
propDep = true
|
|
}
|
|
} else if (subDyn.type === DYN_THUNK) {
|
|
thisDep = thisDep || subDyn.data.thisDep
|
|
contextDep = contextDep || subDyn.data.contextDep
|
|
propDep = propDep || subDyn.data.propDep
|
|
}
|
|
}
|
|
return new Declaration(
|
|
thisDep,
|
|
contextDep,
|
|
propDep,
|
|
append)
|
|
} else {
|
|
return new Declaration(
|
|
type === DYN_STATE,
|
|
type === DYN_CONTEXT,
|
|
type === DYN_PROP,
|
|
append)
|
|
}
|
|
}
|
|
|
|
var SCOPE_DECL = new Declaration(false, false, false, function () {})
|
|
|
|
module.exports = function reglCore (
|
|
gl,
|
|
stringStore,
|
|
extensions,
|
|
limits,
|
|
bufferState,
|
|
elementState,
|
|
textureState,
|
|
framebufferState,
|
|
uniformState,
|
|
attributeState,
|
|
shaderState,
|
|
drawState,
|
|
contextState,
|
|
timer,
|
|
config) {
|
|
var AttributeRecord = attributeState.Record
|
|
|
|
var blendEquations = {
|
|
'add': 32774,
|
|
'subtract': 32778,
|
|
'reverse subtract': 32779
|
|
}
|
|
if (extensions.ext_blend_minmax) {
|
|
blendEquations.min = GL_MIN_EXT
|
|
blendEquations.max = GL_MAX_EXT
|
|
}
|
|
|
|
var extInstancing = extensions.angle_instanced_arrays
|
|
var extDrawBuffers = extensions.webgl_draw_buffers
|
|
var extVertexArrays = extensions.oes_vertex_array_object
|
|
|
|
// ===================================================
|
|
// ===================================================
|
|
// WEBGL STATE
|
|
// ===================================================
|
|
// ===================================================
|
|
var currentState = {
|
|
dirty: true,
|
|
profile: config.profile
|
|
}
|
|
var nextState = {}
|
|
var GL_STATE_NAMES = []
|
|
var GL_FLAGS = {}
|
|
var GL_VARIABLES = {}
|
|
|
|
function propName (name) {
|
|
return name.replace('.', '_')
|
|
}
|
|
|
|
function stateFlag (sname, cap, init) {
|
|
var name = propName(sname)
|
|
GL_STATE_NAMES.push(sname)
|
|
nextState[name] = currentState[name] = !!init
|
|
GL_FLAGS[name] = cap
|
|
}
|
|
|
|
function stateVariable (sname, func, init) {
|
|
var name = propName(sname)
|
|
GL_STATE_NAMES.push(sname)
|
|
if (Array.isArray(init)) {
|
|
currentState[name] = init.slice()
|
|
nextState[name] = init.slice()
|
|
} else {
|
|
currentState[name] = nextState[name] = init
|
|
}
|
|
GL_VARIABLES[name] = func
|
|
}
|
|
|
|
// Dithering
|
|
stateFlag(S_DITHER, GL_DITHER)
|
|
|
|
// Blending
|
|
stateFlag(S_BLEND_ENABLE, GL_BLEND)
|
|
stateVariable(S_BLEND_COLOR, 'blendColor', [0, 0, 0, 0])
|
|
stateVariable(S_BLEND_EQUATION, 'blendEquationSeparate',
|
|
[GL_FUNC_ADD, GL_FUNC_ADD])
|
|
stateVariable(S_BLEND_FUNC, 'blendFuncSeparate',
|
|
[GL_ONE, GL_ZERO, GL_ONE, GL_ZERO])
|
|
|
|
// Depth
|
|
stateFlag(S_DEPTH_ENABLE, GL_DEPTH_TEST, true)
|
|
stateVariable(S_DEPTH_FUNC, 'depthFunc', GL_LESS)
|
|
stateVariable(S_DEPTH_RANGE, 'depthRange', [0, 1])
|
|
stateVariable(S_DEPTH_MASK, 'depthMask', true)
|
|
|
|
// Color mask
|
|
stateVariable(S_COLOR_MASK, S_COLOR_MASK, [true, true, true, true])
|
|
|
|
// Face culling
|
|
stateFlag(S_CULL_ENABLE, GL_CULL_FACE)
|
|
stateVariable(S_CULL_FACE, 'cullFace', GL_BACK)
|
|
|
|
// Front face orientation
|
|
stateVariable(S_FRONT_FACE, S_FRONT_FACE, GL_CCW)
|
|
|
|
// Line width
|
|
stateVariable(S_LINE_WIDTH, S_LINE_WIDTH, 1)
|
|
|
|
// Polygon offset
|
|
stateFlag(S_POLYGON_OFFSET_ENABLE, GL_POLYGON_OFFSET_FILL)
|
|
stateVariable(S_POLYGON_OFFSET_OFFSET, 'polygonOffset', [0, 0])
|
|
|
|
// Sample coverage
|
|
stateFlag(S_SAMPLE_ALPHA, GL_SAMPLE_ALPHA_TO_COVERAGE)
|
|
stateFlag(S_SAMPLE_ENABLE, GL_SAMPLE_COVERAGE)
|
|
stateVariable(S_SAMPLE_COVERAGE, 'sampleCoverage', [1, false])
|
|
|
|
// Stencil
|
|
stateFlag(S_STENCIL_ENABLE, GL_STENCIL_TEST)
|
|
stateVariable(S_STENCIL_MASK, 'stencilMask', -1)
|
|
stateVariable(S_STENCIL_FUNC, 'stencilFunc', [GL_ALWAYS, 0, -1])
|
|
stateVariable(S_STENCIL_OPFRONT, 'stencilOpSeparate',
|
|
[GL_FRONT, GL_KEEP, GL_KEEP, GL_KEEP])
|
|
stateVariable(S_STENCIL_OPBACK, 'stencilOpSeparate',
|
|
[GL_BACK, GL_KEEP, GL_KEEP, GL_KEEP])
|
|
|
|
// Scissor
|
|
stateFlag(S_SCISSOR_ENABLE, GL_SCISSOR_TEST)
|
|
stateVariable(S_SCISSOR_BOX, 'scissor',
|
|
[0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight])
|
|
|
|
// Viewport
|
|
stateVariable(S_VIEWPORT, S_VIEWPORT,
|
|
[0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight])
|
|
|
|
// ===================================================
|
|
// ===================================================
|
|
// ENVIRONMENT
|
|
// ===================================================
|
|
// ===================================================
|
|
var sharedState = {
|
|
gl: gl,
|
|
context: contextState,
|
|
strings: stringStore,
|
|
next: nextState,
|
|
current: currentState,
|
|
draw: drawState,
|
|
elements: elementState,
|
|
buffer: bufferState,
|
|
shader: shaderState,
|
|
attributes: attributeState.state,
|
|
vao: attributeState,
|
|
uniforms: uniformState,
|
|
framebuffer: framebufferState,
|
|
extensions: extensions,
|
|
|
|
timer: timer,
|
|
isBufferArgs: isBufferArgs
|
|
}
|
|
|
|
var sharedConstants = {
|
|
primTypes: primTypes,
|
|
compareFuncs: compareFuncs,
|
|
blendFuncs: blendFuncs,
|
|
blendEquations: blendEquations,
|
|
stencilOps: stencilOps,
|
|
glTypes: glTypes,
|
|
orientationType: orientationType
|
|
}
|
|
|
|
check.optional(function () {
|
|
sharedState.isArrayLike = isArrayLike
|
|
})
|
|
|
|
if (extDrawBuffers) {
|
|
sharedConstants.backBuffer = [GL_BACK]
|
|
sharedConstants.drawBuffer = loop(limits.maxDrawbuffers, function (i) {
|
|
if (i === 0) {
|
|
return [0]
|
|
}
|
|
return loop(i, function (j) {
|
|
return GL_COLOR_ATTACHMENT0 + j
|
|
})
|
|
})
|
|
}
|
|
|
|
var drawCallCounter = 0
|
|
function createREGLEnvironment () {
|
|
var env = createEnvironment()
|
|
var link = env.link
|
|
var global = env.global
|
|
env.id = drawCallCounter++
|
|
|
|
env.batchId = '0'
|
|
|
|
// link shared state
|
|
var SHARED = link(sharedState)
|
|
var shared = env.shared = {
|
|
props: 'a0'
|
|
}
|
|
Object.keys(sharedState).forEach(function (prop) {
|
|
shared[prop] = global.def(SHARED, '.', prop)
|
|
})
|
|
|
|
// Inject runtime assertion stuff for debug builds
|
|
check.optional(function () {
|
|
env.CHECK = link(check)
|
|
env.commandStr = check.guessCommand()
|
|
env.command = link(env.commandStr)
|
|
env.assert = function (block, pred, message) {
|
|
block(
|
|
'if(!(', pred, '))',
|
|
this.CHECK, '.commandRaise(', link(message), ',', this.command, ');')
|
|
}
|
|
|
|
sharedConstants.invalidBlendCombinations = invalidBlendCombinations
|
|
})
|
|
|
|
// Copy GL state variables over
|
|
var nextVars = env.next = {}
|
|
var currentVars = env.current = {}
|
|
Object.keys(GL_VARIABLES).forEach(function (variable) {
|
|
if (Array.isArray(currentState[variable])) {
|
|
nextVars[variable] = global.def(shared.next, '.', variable)
|
|
currentVars[variable] = global.def(shared.current, '.', variable)
|
|
}
|
|
})
|
|
|
|
// Initialize shared constants
|
|
var constants = env.constants = {}
|
|
Object.keys(sharedConstants).forEach(function (name) {
|
|
constants[name] = global.def(JSON.stringify(sharedConstants[name]))
|
|
})
|
|
|
|
// Helper function for calling a block
|
|
env.invoke = function (block, x) {
|
|
switch (x.type) {
|
|
case DYN_FUNC:
|
|
var argList = [
|
|
'this',
|
|
shared.context,
|
|
shared.props,
|
|
env.batchId
|
|
]
|
|
return block.def(
|
|
link(x.data), '.call(',
|
|
argList.slice(0, Math.max(x.data.length + 1, 4)),
|
|
')')
|
|
case DYN_PROP:
|
|
return block.def(shared.props, x.data)
|
|
case DYN_CONTEXT:
|
|
return block.def(shared.context, x.data)
|
|
case DYN_STATE:
|
|
return block.def('this', x.data)
|
|
case DYN_THUNK:
|
|
x.data.append(env, block)
|
|
return x.data.ref
|
|
case DYN_CONSTANT:
|
|
return x.data.toString()
|
|
case DYN_ARRAY:
|
|
return x.data.map(function (y) {
|
|
return env.invoke(block, y)
|
|
})
|
|
}
|
|
}
|
|
|
|
env.attribCache = {}
|
|
|
|
var scopeAttribs = {}
|
|
env.scopeAttrib = function (name) {
|
|
var id = stringStore.id(name)
|
|
if (id in scopeAttribs) {
|
|
return scopeAttribs[id]
|
|
}
|
|
var binding = attributeState.scope[id]
|
|
if (!binding) {
|
|
binding = attributeState.scope[id] = new AttributeRecord()
|
|
}
|
|
var result = scopeAttribs[id] = link(binding)
|
|
return result
|
|
}
|
|
|
|
return env
|
|
}
|
|
|
|
// ===================================================
|
|
// ===================================================
|
|
// PARSING
|
|
// ===================================================
|
|
// ===================================================
|
|
function parseProfile (options) {
|
|
var staticOptions = options.static
|
|
var dynamicOptions = options.dynamic
|
|
|
|
var profileEnable
|
|
if (S_PROFILE in staticOptions) {
|
|
var value = !!staticOptions[S_PROFILE]
|
|
profileEnable = createStaticDecl(function (env, scope) {
|
|
return value
|
|
})
|
|
profileEnable.enable = value
|
|
} else if (S_PROFILE in dynamicOptions) {
|
|
var dyn = dynamicOptions[S_PROFILE]
|
|
profileEnable = createDynamicDecl(dyn, function (env, scope) {
|
|
return env.invoke(scope, dyn)
|
|
})
|
|
}
|
|
|
|
return profileEnable
|
|
}
|
|
|
|
function parseFramebuffer (options, env) {
|
|
var staticOptions = options.static
|
|
var dynamicOptions = options.dynamic
|
|
|
|
if (S_FRAMEBUFFER in staticOptions) {
|
|
var framebuffer = staticOptions[S_FRAMEBUFFER]
|
|
if (framebuffer) {
|
|
framebuffer = framebufferState.getFramebuffer(framebuffer)
|
|
check.command(framebuffer, 'invalid framebuffer object')
|
|
return createStaticDecl(function (env, block) {
|
|
var FRAMEBUFFER = env.link(framebuffer)
|
|
var shared = env.shared
|
|
block.set(
|
|
shared.framebuffer,
|
|
'.next',
|
|
FRAMEBUFFER)
|
|
var CONTEXT = shared.context
|
|
block.set(
|
|
CONTEXT,
|
|
'.' + S_FRAMEBUFFER_WIDTH,
|
|
FRAMEBUFFER + '.width')
|
|
block.set(
|
|
CONTEXT,
|
|
'.' + S_FRAMEBUFFER_HEIGHT,
|
|
FRAMEBUFFER + '.height')
|
|
return FRAMEBUFFER
|
|
})
|
|
} else {
|
|
return createStaticDecl(function (env, scope) {
|
|
var shared = env.shared
|
|
scope.set(
|
|
shared.framebuffer,
|
|
'.next',
|
|
'null')
|
|
var CONTEXT = shared.context
|
|
scope.set(
|
|
CONTEXT,
|
|
'.' + S_FRAMEBUFFER_WIDTH,
|
|
CONTEXT + '.' + S_DRAWINGBUFFER_WIDTH)
|
|
scope.set(
|
|
CONTEXT,
|
|
'.' + S_FRAMEBUFFER_HEIGHT,
|
|
CONTEXT + '.' + S_DRAWINGBUFFER_HEIGHT)
|
|
return 'null'
|
|
})
|
|
}
|
|
} else if (S_FRAMEBUFFER in dynamicOptions) {
|
|
var dyn = dynamicOptions[S_FRAMEBUFFER]
|
|
return createDynamicDecl(dyn, function (env, scope) {
|
|
var FRAMEBUFFER_FUNC = env.invoke(scope, dyn)
|
|
var shared = env.shared
|
|
var FRAMEBUFFER_STATE = shared.framebuffer
|
|
var FRAMEBUFFER = scope.def(
|
|
FRAMEBUFFER_STATE, '.getFramebuffer(', FRAMEBUFFER_FUNC, ')')
|
|
|
|
check.optional(function () {
|
|
env.assert(scope,
|
|
'!' + FRAMEBUFFER_FUNC + '||' + FRAMEBUFFER,
|
|
'invalid framebuffer object')
|
|
})
|
|
|
|
scope.set(
|
|
FRAMEBUFFER_STATE,
|
|
'.next',
|
|
FRAMEBUFFER)
|
|
var CONTEXT = shared.context
|
|
scope.set(
|
|
CONTEXT,
|
|
'.' + S_FRAMEBUFFER_WIDTH,
|
|
FRAMEBUFFER + '?' + FRAMEBUFFER + '.width:' +
|
|
CONTEXT + '.' + S_DRAWINGBUFFER_WIDTH)
|
|
scope.set(
|
|
CONTEXT,
|
|
'.' + S_FRAMEBUFFER_HEIGHT,
|
|
FRAMEBUFFER +
|
|
'?' + FRAMEBUFFER + '.height:' +
|
|
CONTEXT + '.' + S_DRAWINGBUFFER_HEIGHT)
|
|
return FRAMEBUFFER
|
|
})
|
|
} else {
|
|
return null
|
|
}
|
|
}
|
|
|
|
function parseViewportScissor (options, framebuffer, env) {
|
|
var staticOptions = options.static
|
|
var dynamicOptions = options.dynamic
|
|
|
|
function parseBox (param) {
|
|
if (param in staticOptions) {
|
|
var box = staticOptions[param]
|
|
check.commandType(box, 'object', 'invalid ' + param, env.commandStr)
|
|
|
|
var isStatic = true
|
|
var x = box.x | 0
|
|
var y = box.y | 0
|
|
var w, h
|
|
if ('width' in box) {
|
|
w = box.width | 0
|
|
check.command(w >= 0, 'invalid ' + param, env.commandStr)
|
|
} else {
|
|
isStatic = false
|
|
}
|
|
if ('height' in box) {
|
|
h = box.height | 0
|
|
check.command(h >= 0, 'invalid ' + param, env.commandStr)
|
|
} else {
|
|
isStatic = false
|
|
}
|
|
|
|
return new Declaration(
|
|
!isStatic && framebuffer && framebuffer.thisDep,
|
|
!isStatic && framebuffer && framebuffer.contextDep,
|
|
!isStatic && framebuffer && framebuffer.propDep,
|
|
function (env, scope) {
|
|
var CONTEXT = env.shared.context
|
|
var BOX_W = w
|
|
if (!('width' in box)) {
|
|
BOX_W = scope.def(CONTEXT, '.', S_FRAMEBUFFER_WIDTH, '-', x)
|
|
}
|
|
var BOX_H = h
|
|
if (!('height' in box)) {
|
|
BOX_H = scope.def(CONTEXT, '.', S_FRAMEBUFFER_HEIGHT, '-', y)
|
|
}
|
|
return [x, y, BOX_W, BOX_H]
|
|
})
|
|
} else if (param in dynamicOptions) {
|
|
var dynBox = dynamicOptions[param]
|
|
var result = createDynamicDecl(dynBox, function (env, scope) {
|
|
var BOX = env.invoke(scope, dynBox)
|
|
|
|
check.optional(function () {
|
|
env.assert(scope,
|
|
BOX + '&&typeof ' + BOX + '==="object"',
|
|
'invalid ' + param)
|
|
})
|
|
|
|
var CONTEXT = env.shared.context
|
|
var BOX_X = scope.def(BOX, '.x|0')
|
|
var BOX_Y = scope.def(BOX, '.y|0')
|
|
var BOX_W = scope.def(
|
|
'"width" in ', BOX, '?', BOX, '.width|0:',
|
|
'(', CONTEXT, '.', S_FRAMEBUFFER_WIDTH, '-', BOX_X, ')')
|
|
var BOX_H = scope.def(
|
|
'"height" in ', BOX, '?', BOX, '.height|0:',
|
|
'(', CONTEXT, '.', S_FRAMEBUFFER_HEIGHT, '-', BOX_Y, ')')
|
|
|
|
check.optional(function () {
|
|
env.assert(scope,
|
|
BOX_W + '>=0&&' +
|
|
BOX_H + '>=0',
|
|
'invalid ' + param)
|
|
})
|
|
|
|
return [BOX_X, BOX_Y, BOX_W, BOX_H]
|
|
})
|
|
if (framebuffer) {
|
|
result.thisDep = result.thisDep || framebuffer.thisDep
|
|
result.contextDep = result.contextDep || framebuffer.contextDep
|
|
result.propDep = result.propDep || framebuffer.propDep
|
|
}
|
|
return result
|
|
} else if (framebuffer) {
|
|
return new Declaration(
|
|
framebuffer.thisDep,
|
|
framebuffer.contextDep,
|
|
framebuffer.propDep,
|
|
function (env, scope) {
|
|
var CONTEXT = env.shared.context
|
|
return [
|
|
0, 0,
|
|
scope.def(CONTEXT, '.', S_FRAMEBUFFER_WIDTH),
|
|
scope.def(CONTEXT, '.', S_FRAMEBUFFER_HEIGHT)]
|
|
})
|
|
} else {
|
|
return null
|
|
}
|
|
}
|
|
|
|
var viewport = parseBox(S_VIEWPORT)
|
|
|
|
if (viewport) {
|
|
var prevViewport = viewport
|
|
viewport = new Declaration(
|
|
viewport.thisDep,
|
|
viewport.contextDep,
|
|
viewport.propDep,
|
|
function (env, scope) {
|
|
var VIEWPORT = prevViewport.append(env, scope)
|
|
var CONTEXT = env.shared.context
|
|
scope.set(
|
|
CONTEXT,
|
|
'.' + S_VIEWPORT_WIDTH,
|
|
VIEWPORT[2])
|
|
scope.set(
|
|
CONTEXT,
|
|
'.' + S_VIEWPORT_HEIGHT,
|
|
VIEWPORT[3])
|
|
return VIEWPORT
|
|
})
|
|
}
|
|
|
|
return {
|
|
viewport: viewport,
|
|
scissor_box: parseBox(S_SCISSOR_BOX)
|
|
}
|
|
}
|
|
|
|
function parseAttribLocations (options, attributes) {
|
|
var staticOptions = options.static
|
|
var staticProgram =
|
|
typeof staticOptions[S_FRAG] === 'string' &&
|
|
typeof staticOptions[S_VERT] === 'string'
|
|
if (staticProgram) {
|
|
if (Object.keys(attributes.dynamic).length > 0) {
|
|
return null
|
|
}
|
|
var staticAttributes = attributes.static
|
|
var sAttributes = Object.keys(staticAttributes)
|
|
if (sAttributes.length > 0 && typeof staticAttributes[sAttributes[0]] === 'number') {
|
|
var bindings = []
|
|
for (var i = 0; i < sAttributes.length; ++i) {
|
|
check(typeof staticAttributes[sAttributes[i]] === 'number', 'must specify all vertex attribute locations when using vaos')
|
|
bindings.push([staticAttributes[sAttributes[i]] | 0, sAttributes[i]])
|
|
}
|
|
return bindings
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
function parseProgram (options, env, attribLocations) {
|
|
var staticOptions = options.static
|
|
var dynamicOptions = options.dynamic
|
|
|
|
function parseShader (name) {
|
|
if (name in staticOptions) {
|
|
var id = stringStore.id(staticOptions[name])
|
|
check.optional(function () {
|
|
shaderState.shader(shaderType[name], id, check.guessCommand())
|
|
})
|
|
var result = createStaticDecl(function () {
|
|
return id
|
|
})
|
|
result.id = id
|
|
return result
|
|
} else if (name in dynamicOptions) {
|
|
var dyn = dynamicOptions[name]
|
|
return createDynamicDecl(dyn, function (env, scope) {
|
|
var str = env.invoke(scope, dyn)
|
|
var id = scope.def(env.shared.strings, '.id(', str, ')')
|
|
check.optional(function () {
|
|
scope(
|
|
env.shared.shader, '.shader(',
|
|
shaderType[name], ',',
|
|
id, ',',
|
|
env.command, ');')
|
|
})
|
|
return id
|
|
})
|
|
}
|
|
return null
|
|
}
|
|
|
|
var frag = parseShader(S_FRAG)
|
|
var vert = parseShader(S_VERT)
|
|
|
|
var program = null
|
|
var progVar
|
|
if (isStatic(frag) && isStatic(vert)) {
|
|
program = shaderState.program(vert.id, frag.id, null, attribLocations)
|
|
progVar = createStaticDecl(function (env, scope) {
|
|
return env.link(program)
|
|
})
|
|
} else {
|
|
progVar = new Declaration(
|
|
(frag && frag.thisDep) || (vert && vert.thisDep),
|
|
(frag && frag.contextDep) || (vert && vert.contextDep),
|
|
(frag && frag.propDep) || (vert && vert.propDep),
|
|
function (env, scope) {
|
|
var SHADER_STATE = env.shared.shader
|
|
var fragId
|
|
if (frag) {
|
|
fragId = frag.append(env, scope)
|
|
} else {
|
|
fragId = scope.def(SHADER_STATE, '.', S_FRAG)
|
|
}
|
|
var vertId
|
|
if (vert) {
|
|
vertId = vert.append(env, scope)
|
|
} else {
|
|
vertId = scope.def(SHADER_STATE, '.', S_VERT)
|
|
}
|
|
var progDef = SHADER_STATE + '.program(' + vertId + ',' + fragId
|
|
check.optional(function () {
|
|
progDef += ',' + env.command
|
|
})
|
|
return scope.def(progDef + ')')
|
|
})
|
|
}
|
|
|
|
return {
|
|
frag: frag,
|
|
vert: vert,
|
|
progVar: progVar,
|
|
program: program
|
|
}
|
|
}
|
|
|
|
function parseDraw (options, env) {
|
|
var staticOptions = options.static
|
|
var dynamicOptions = options.dynamic
|
|
|
|
// TODO: should use VAO to get default values for offset properties
|
|
// should move vao parse into here and out of the old stuff
|
|
|
|
var staticDraw = {}
|
|
var vaoActive = false
|
|
|
|
function parseVAO () {
|
|
if (S_VAO in staticOptions) {
|
|
var vao = staticOptions[S_VAO]
|
|
if (vao !== null && attributeState.getVAO(vao) === null) {
|
|
vao = attributeState.createVAO(vao)
|
|
}
|
|
|
|
vaoActive = true
|
|
staticDraw.vao = vao
|
|
|
|
return createStaticDecl(function (env) {
|
|
var vaoRef = attributeState.getVAO(vao)
|
|
if (vaoRef) {
|
|
return env.link(vaoRef)
|
|
} else {
|
|
return 'null'
|
|
}
|
|
})
|
|
} else if (S_VAO in dynamicOptions) {
|
|
vaoActive = true
|
|
var dyn = dynamicOptions[S_VAO]
|
|
return createDynamicDecl(dyn, function (env, scope) {
|
|
var vaoRef = env.invoke(scope, dyn)
|
|
return scope.def(env.shared.vao + '.getVAO(' + vaoRef + ')')
|
|
})
|
|
}
|
|
return null
|
|
}
|
|
|
|
var vao = parseVAO()
|
|
|
|
var elementsActive = false
|
|
|
|
function parseElements () {
|
|
if (S_ELEMENTS in staticOptions) {
|
|
var elements = staticOptions[S_ELEMENTS]
|
|
staticDraw.elements = elements
|
|
if (isBufferArgs(elements)) {
|
|
var e = staticDraw.elements = elementState.create(elements, true)
|
|
elements = elementState.getElements(e)
|
|
elementsActive = true
|
|
} else if (elements) {
|
|
elements = elementState.getElements(elements)
|
|
elementsActive = true
|
|
check.command(elements, 'invalid elements', env.commandStr)
|
|
}
|
|
|
|
var result = createStaticDecl(function (env, scope) {
|
|
if (elements) {
|
|
var result = env.link(elements)
|
|
env.ELEMENTS = result
|
|
return result
|
|
}
|
|
env.ELEMENTS = null
|
|
return null
|
|
})
|
|
result.value = elements
|
|
return result
|
|
} else if (S_ELEMENTS in dynamicOptions) {
|
|
elementsActive = true
|
|
|
|
var dyn = dynamicOptions[S_ELEMENTS]
|
|
return createDynamicDecl(dyn, function (env, scope) {
|
|
var shared = env.shared
|
|
|
|
var IS_BUFFER_ARGS = shared.isBufferArgs
|
|
var ELEMENT_STATE = shared.elements
|
|
|
|
var elementDefn = env.invoke(scope, dyn)
|
|
var elements = scope.def('null')
|
|
var elementStream = scope.def(IS_BUFFER_ARGS, '(', elementDefn, ')')
|
|
|
|
var ifte = env.cond(elementStream)
|
|
.then(elements, '=', ELEMENT_STATE, '.createStream(', elementDefn, ');')
|
|
.else(elements, '=', ELEMENT_STATE, '.getElements(', elementDefn, ');')
|
|
|
|
check.optional(function () {
|
|
env.assert(ifte.else,
|
|
'!' + elementDefn + '||' + elements,
|
|
'invalid elements')
|
|
})
|
|
|
|
scope.entry(ifte)
|
|
scope.exit(
|
|
env.cond(elementStream)
|
|
.then(ELEMENT_STATE, '.destroyStream(', elements, ');'))
|
|
|
|
env.ELEMENTS = elements
|
|
|
|
return elements
|
|
})
|
|
} else if (vaoActive) {
|
|
return new Declaration(
|
|
vao.thisDep,
|
|
vao.contextDep,
|
|
vao.propDep,
|
|
function (env, scope) {
|
|
return scope.def(env.shared.vao + '.currentVAO?' + env.shared.elements + '.getElements(' + env.shared.vao + '.currentVAO.elements):null')
|
|
})
|
|
}
|
|
return null
|
|
}
|
|
|
|
var elements = parseElements()
|
|
|
|
function parsePrimitive () {
|
|
if (S_PRIMITIVE in staticOptions) {
|
|
var primitive = staticOptions[S_PRIMITIVE]
|
|
staticDraw.primitive = primitive
|
|
check.commandParameter(primitive, primTypes, 'invalid primitve', env.commandStr)
|
|
return createStaticDecl(function (env, scope) {
|
|
return primTypes[primitive]
|
|
})
|
|
} else if (S_PRIMITIVE in dynamicOptions) {
|
|
var dynPrimitive = dynamicOptions[S_PRIMITIVE]
|
|
return createDynamicDecl(dynPrimitive, function (env, scope) {
|
|
var PRIM_TYPES = env.constants.primTypes
|
|
var prim = env.invoke(scope, dynPrimitive)
|
|
check.optional(function () {
|
|
env.assert(scope,
|
|
prim + ' in ' + PRIM_TYPES,
|
|
'invalid primitive, must be one of ' + Object.keys(primTypes))
|
|
})
|
|
return scope.def(PRIM_TYPES, '[', prim, ']')
|
|
})
|
|
} else if (elementsActive) {
|
|
if (isStatic(elements)) {
|
|
if (elements.value) {
|
|
return createStaticDecl(function (env, scope) {
|
|
return scope.def(env.ELEMENTS, '.primType')
|
|
})
|
|
} else {
|
|
return createStaticDecl(function () {
|
|
return GL_TRIANGLES
|
|
})
|
|
}
|
|
} else {
|
|
return new Declaration(
|
|
elements.thisDep,
|
|
elements.contextDep,
|
|
elements.propDep,
|
|
function (env, scope) {
|
|
var elements = env.ELEMENTS
|
|
return scope.def(elements, '?', elements, '.primType:', GL_TRIANGLES)
|
|
})
|
|
}
|
|
} else if (vaoActive) {
|
|
return new Declaration(
|
|
vao.thisDep,
|
|
vao.contextDep,
|
|
vao.propDep,
|
|
function (env, scope) {
|
|
return scope.def(env.shared.vao + '.currentVAO?' + env.shared.vao + '.currentVAO.primitive:' + GL_TRIANGLES)
|
|
})
|
|
}
|
|
return null
|
|
}
|
|
|
|
function parseParam (param, isOffset) {
|
|
if (param in staticOptions) {
|
|
var value = staticOptions[param] | 0
|
|
if (isOffset) {
|
|
staticDraw.offset = value
|
|
} else {
|
|
staticDraw.instances = value
|
|
}
|
|
check.command(!isOffset || value >= 0, 'invalid ' + param, env.commandStr)
|
|
return createStaticDecl(function (env, scope) {
|
|
if (isOffset) {
|
|
env.OFFSET = value
|
|
}
|
|
return value
|
|
})
|
|
} else if (param in dynamicOptions) {
|
|
var dynValue = dynamicOptions[param]
|
|
return createDynamicDecl(dynValue, function (env, scope) {
|
|
var result = env.invoke(scope, dynValue)
|
|
if (isOffset) {
|
|
env.OFFSET = result
|
|
check.optional(function () {
|
|
env.assert(scope,
|
|
result + '>=0',
|
|
'invalid ' + param)
|
|
})
|
|
}
|
|
return result
|
|
})
|
|
} else if (isOffset) {
|
|
if (elementsActive) {
|
|
return createStaticDecl(function (env, scope) {
|
|
env.OFFSET = 0
|
|
return 0
|
|
})
|
|
} else if (vaoActive) {
|
|
return new Declaration(
|
|
vao.thisDep,
|
|
vao.contextDep,
|
|
vao.propDep,
|
|
function (env, scope) {
|
|
return scope.def(env.shared.vao + '.currentVAO?' + env.shared.vao + '.currentVAO.offset:0')
|
|
})
|
|
}
|
|
} else if (vaoActive) {
|
|
return new Declaration(
|
|
vao.thisDep,
|
|
vao.contextDep,
|
|
vao.propDep,
|
|
function (env, scope) {
|
|
return scope.def(env.shared.vao + '.currentVAO?' + env.shared.vao + '.currentVAO.instances:-1')
|
|
})
|
|
}
|
|
return null
|
|
}
|
|
|
|
var OFFSET = parseParam(S_OFFSET, true)
|
|
|
|
function parseVertCount () {
|
|
if (S_COUNT in staticOptions) {
|
|
var count = staticOptions[S_COUNT] | 0
|
|
staticDraw.count = count
|
|
check.command(
|
|
typeof count === 'number' && count >= 0, 'invalid vertex count', env.commandStr)
|
|
return createStaticDecl(function () {
|
|
return count
|
|
})
|
|
} else if (S_COUNT in dynamicOptions) {
|
|
var dynCount = dynamicOptions[S_COUNT]
|
|
return createDynamicDecl(dynCount, function (env, scope) {
|
|
var result = env.invoke(scope, dynCount)
|
|
check.optional(function () {
|
|
env.assert(scope,
|
|
'typeof ' + result + '==="number"&&' +
|
|
result + '>=0&&' +
|
|
result + '===(' + result + '|0)',
|
|
'invalid vertex count')
|
|
})
|
|
return result
|
|
})
|
|
} else if (elementsActive) {
|
|
if (isStatic(elements)) {
|
|
if (elements) {
|
|
if (OFFSET) {
|
|
return new Declaration(
|
|
OFFSET.thisDep,
|
|
OFFSET.contextDep,
|
|
OFFSET.propDep,
|
|
function (env, scope) {
|
|
var result = scope.def(
|
|
env.ELEMENTS, '.vertCount-', env.OFFSET)
|
|
|
|
check.optional(function () {
|
|
env.assert(scope,
|
|
result + '>=0',
|
|
'invalid vertex offset/element buffer too small')
|
|
})
|
|
|
|
return result
|
|
})
|
|
} else {
|
|
return createStaticDecl(function (env, scope) {
|
|
return scope.def(env.ELEMENTS, '.vertCount')
|
|
})
|
|
}
|
|
} else {
|
|
var result = createStaticDecl(function () {
|
|
return -1
|
|
})
|
|
check.optional(function () {
|
|
result.MISSING = true
|
|
})
|
|
return result
|
|
}
|
|
} else {
|
|
var variable = new Declaration(
|
|
elements.thisDep || OFFSET.thisDep,
|
|
elements.contextDep || OFFSET.contextDep,
|
|
elements.propDep || OFFSET.propDep,
|
|
function (env, scope) {
|
|
var elements = env.ELEMENTS
|
|
if (env.OFFSET) {
|
|
return scope.def(elements, '?', elements, '.vertCount-',
|
|
env.OFFSET, ':-1')
|
|
}
|
|
return scope.def(elements, '?', elements, '.vertCount:-1')
|
|
})
|
|
check.optional(function () {
|
|
variable.DYNAMIC = true
|
|
})
|
|
return variable
|
|
}
|
|
} else if (vaoActive) {
|
|
var countVariable = new Declaration(
|
|
vao.thisDep,
|
|
vao.contextDep,
|
|
vao.propDep,
|
|
function (env, scope) {
|
|
return scope.def(env.shared.vao, '.currentVAO?', env.shared.vao, '.currentVAO.count:-1')
|
|
})
|
|
return countVariable
|
|
}
|
|
return null
|
|
}
|
|
|
|
var primitive = parsePrimitive()
|
|
var count = parseVertCount()
|
|
var instances = parseParam(S_INSTANCES, false)
|
|
|
|
return {
|
|
elements: elements,
|
|
primitive: primitive,
|
|
count: count,
|
|
instances: instances,
|
|
offset: OFFSET,
|
|
vao: vao,
|
|
|
|
vaoActive: vaoActive,
|
|
elementsActive: elementsActive,
|
|
|
|
// static draw props
|
|
static: staticDraw
|
|
}
|
|
}
|
|
|
|
function parseGLState (options, env) {
|
|
var staticOptions = options.static
|
|
var dynamicOptions = options.dynamic
|
|
|
|
var STATE = {}
|
|
|
|
GL_STATE_NAMES.forEach(function (prop) {
|
|
var param = propName(prop)
|
|
|
|
function parseParam (parseStatic, parseDynamic) {
|
|
if (prop in staticOptions) {
|
|
var value = parseStatic(staticOptions[prop])
|
|
STATE[param] = createStaticDecl(function () {
|
|
return value
|
|
})
|
|
} else if (prop in dynamicOptions) {
|
|
var dyn = dynamicOptions[prop]
|
|
STATE[param] = createDynamicDecl(dyn, function (env, scope) {
|
|
return parseDynamic(env, scope, env.invoke(scope, dyn))
|
|
})
|
|
}
|
|
}
|
|
|
|
switch (prop) {
|
|
case S_CULL_ENABLE:
|
|
case S_BLEND_ENABLE:
|
|
case S_DITHER:
|
|
case S_STENCIL_ENABLE:
|
|
case S_DEPTH_ENABLE:
|
|
case S_SCISSOR_ENABLE:
|
|
case S_POLYGON_OFFSET_ENABLE:
|
|
case S_SAMPLE_ALPHA:
|
|
case S_SAMPLE_ENABLE:
|
|
case S_DEPTH_MASK:
|
|
return parseParam(
|
|
function (value) {
|
|
check.commandType(value, 'boolean', prop, env.commandStr)
|
|
return value
|
|
},
|
|
function (env, scope, value) {
|
|
check.optional(function () {
|
|
env.assert(scope,
|
|
'typeof ' + value + '==="boolean"',
|
|
'invalid flag ' + prop, env.commandStr)
|
|
})
|
|
return value
|
|
})
|
|
|
|
case S_DEPTH_FUNC:
|
|
return parseParam(
|
|
function (value) {
|
|
check.commandParameter(value, compareFuncs, 'invalid ' + prop, env.commandStr)
|
|
return compareFuncs[value]
|
|
},
|
|
function (env, scope, value) {
|
|
var COMPARE_FUNCS = env.constants.compareFuncs
|
|
check.optional(function () {
|
|
env.assert(scope,
|
|
value + ' in ' + COMPARE_FUNCS,
|
|
'invalid ' + prop + ', must be one of ' + Object.keys(compareFuncs))
|
|
})
|
|
return scope.def(COMPARE_FUNCS, '[', value, ']')
|
|
})
|
|
|
|
case S_DEPTH_RANGE:
|
|
return parseParam(
|
|
function (value) {
|
|
check.command(
|
|
isArrayLike(value) &&
|
|
value.length === 2 &&
|
|
typeof value[0] === 'number' &&
|
|
typeof value[1] === 'number' &&
|
|
value[0] <= value[1],
|
|
'depth range is 2d array',
|
|
env.commandStr)
|
|
return value
|
|
},
|
|
function (env, scope, value) {
|
|
check.optional(function () {
|
|
env.assert(scope,
|
|
env.shared.isArrayLike + '(' + value + ')&&' +
|
|
value + '.length===2&&' +
|
|
'typeof ' + value + '[0]==="number"&&' +
|
|
'typeof ' + value + '[1]==="number"&&' +
|
|
value + '[0]<=' + value + '[1]',
|
|
'depth range must be a 2d array')
|
|
})
|
|
|
|
var Z_NEAR = scope.def('+', value, '[0]')
|
|
var Z_FAR = scope.def('+', value, '[1]')
|
|
return [Z_NEAR, Z_FAR]
|
|
})
|
|
|
|
case S_BLEND_FUNC:
|
|
return parseParam(
|
|
function (value) {
|
|
check.commandType(value, 'object', 'blend.func', env.commandStr)
|
|
var srcRGB = ('srcRGB' in value ? value.srcRGB : value.src)
|
|
var srcAlpha = ('srcAlpha' in value ? value.srcAlpha : value.src)
|
|
var dstRGB = ('dstRGB' in value ? value.dstRGB : value.dst)
|
|
var dstAlpha = ('dstAlpha' in value ? value.dstAlpha : value.dst)
|
|
check.commandParameter(srcRGB, blendFuncs, param + '.srcRGB', env.commandStr)
|
|
check.commandParameter(srcAlpha, blendFuncs, param + '.srcAlpha', env.commandStr)
|
|
check.commandParameter(dstRGB, blendFuncs, param + '.dstRGB', env.commandStr)
|
|
check.commandParameter(dstAlpha, blendFuncs, param + '.dstAlpha', env.commandStr)
|
|
|
|
check.command(
|
|
(invalidBlendCombinations.indexOf(srcRGB + ', ' + dstRGB) === -1),
|
|
'unallowed blending combination (srcRGB, dstRGB) = (' + srcRGB + ', ' + dstRGB + ')', env.commandStr)
|
|
|
|
return [
|
|
blendFuncs[srcRGB],
|
|
blendFuncs[dstRGB],
|
|
blendFuncs[srcAlpha],
|
|
blendFuncs[dstAlpha]
|
|
]
|
|
},
|
|
function (env, scope, value) {
|
|
var BLEND_FUNCS = env.constants.blendFuncs
|
|
|
|
check.optional(function () {
|
|
env.assert(scope,
|
|
value + '&&typeof ' + value + '==="object"',
|
|
'invalid blend func, must be an object')
|
|
})
|
|
|
|
function read (prefix, suffix) {
|
|
var func = scope.def(
|
|
'"', prefix, suffix, '" in ', value,
|
|
'?', value, '.', prefix, suffix,
|
|
':', value, '.', prefix)
|
|
|
|
check.optional(function () {
|
|
env.assert(scope,
|
|
func + ' in ' + BLEND_FUNCS,
|
|
'invalid ' + prop + '.' + prefix + suffix + ', must be one of ' + Object.keys(blendFuncs))
|
|
})
|
|
|
|
return func
|
|
}
|
|
|
|
var srcRGB = read('src', 'RGB')
|
|
var dstRGB = read('dst', 'RGB')
|
|
|
|
check.optional(function () {
|
|
var INVALID_BLEND_COMBINATIONS = env.constants.invalidBlendCombinations
|
|
|
|
env.assert(scope,
|
|
INVALID_BLEND_COMBINATIONS +
|
|
'.indexOf(' + srcRGB + '+", "+' + dstRGB + ') === -1 ',
|
|
'unallowed blending combination for (srcRGB, dstRGB)'
|
|
)
|
|
})
|
|
|
|
var SRC_RGB = scope.def(BLEND_FUNCS, '[', srcRGB, ']')
|
|
var SRC_ALPHA = scope.def(BLEND_FUNCS, '[', read('src', 'Alpha'), ']')
|
|
var DST_RGB = scope.def(BLEND_FUNCS, '[', dstRGB, ']')
|
|
var DST_ALPHA = scope.def(BLEND_FUNCS, '[', read('dst', 'Alpha'), ']')
|
|
|
|
return [SRC_RGB, DST_RGB, SRC_ALPHA, DST_ALPHA]
|
|
})
|
|
|
|
case S_BLEND_EQUATION:
|
|
return parseParam(
|
|
function (value) {
|
|
if (typeof value === 'string') {
|
|
check.commandParameter(value, blendEquations, 'invalid ' + prop, env.commandStr)
|
|
return [
|
|
blendEquations[value],
|
|
blendEquations[value]
|
|
]
|
|
} else if (typeof value === 'object') {
|
|
check.commandParameter(
|
|
value.rgb, blendEquations, prop + '.rgb', env.commandStr)
|
|
check.commandParameter(
|
|
value.alpha, blendEquations, prop + '.alpha', env.commandStr)
|
|
return [
|
|
blendEquations[value.rgb],
|
|
blendEquations[value.alpha]
|
|
]
|
|
} else {
|
|
check.commandRaise('invalid blend.equation', env.commandStr)
|
|
}
|
|
},
|
|
function (env, scope, value) {
|
|
var BLEND_EQUATIONS = env.constants.blendEquations
|
|
|
|
var RGB = scope.def()
|
|
var ALPHA = scope.def()
|
|
|
|
var ifte = env.cond('typeof ', value, '==="string"')
|
|
|
|
check.optional(function () {
|
|
function checkProp (block, name, value) {
|
|
env.assert(block,
|
|
value + ' in ' + BLEND_EQUATIONS,
|
|
'invalid ' + name + ', must be one of ' + Object.keys(blendEquations))
|
|
}
|
|
checkProp(ifte.then, prop, value)
|
|
|
|
env.assert(ifte.else,
|
|
value + '&&typeof ' + value + '==="object"',
|
|
'invalid ' + prop)
|
|
checkProp(ifte.else, prop + '.rgb', value + '.rgb')
|
|
checkProp(ifte.else, prop + '.alpha', value + '.alpha')
|
|
})
|
|
|
|
ifte.then(
|
|
RGB, '=', ALPHA, '=', BLEND_EQUATIONS, '[', value, '];')
|
|
ifte.else(
|
|
RGB, '=', BLEND_EQUATIONS, '[', value, '.rgb];',
|
|
ALPHA, '=', BLEND_EQUATIONS, '[', value, '.alpha];')
|
|
|
|
scope(ifte)
|
|
|
|
return [RGB, ALPHA]
|
|
})
|
|
|
|
case S_BLEND_COLOR:
|
|
return parseParam(
|
|
function (value) {
|
|
check.command(
|
|
isArrayLike(value) &&
|
|
value.length === 4,
|
|
'blend.color must be a 4d array', env.commandStr)
|
|
return loop(4, function (i) {
|
|
return +value[i]
|
|
})
|
|
},
|
|
function (env, scope, value) {
|
|
check.optional(function () {
|
|
env.assert(scope,
|
|
env.shared.isArrayLike + '(' + value + ')&&' +
|
|
value + '.length===4',
|
|
'blend.color must be a 4d array')
|
|
})
|
|
return loop(4, function (i) {
|
|
return scope.def('+', value, '[', i, ']')
|
|
})
|
|
})
|
|
|
|
case S_STENCIL_MASK:
|
|
return parseParam(
|
|
function (value) {
|
|
check.commandType(value, 'number', param, env.commandStr)
|
|
return value | 0
|
|
},
|
|
function (env, scope, value) {
|
|
check.optional(function () {
|
|
env.assert(scope,
|
|
'typeof ' + value + '==="number"',
|
|
'invalid stencil.mask')
|
|
})
|
|
return scope.def(value, '|0')
|
|
})
|
|
|
|
case S_STENCIL_FUNC:
|
|
return parseParam(
|
|
function (value) {
|
|
check.commandType(value, 'object', param, env.commandStr)
|
|
var cmp = value.cmp || 'keep'
|
|
var ref = value.ref || 0
|
|
var mask = 'mask' in value ? value.mask : -1
|
|
check.commandParameter(cmp, compareFuncs, prop + '.cmp', env.commandStr)
|
|
check.commandType(ref, 'number', prop + '.ref', env.commandStr)
|
|
check.commandType(mask, 'number', prop + '.mask', env.commandStr)
|
|
return [
|
|
compareFuncs[cmp],
|
|
ref,
|
|
mask
|
|
]
|
|
},
|
|
function (env, scope, value) {
|
|
var COMPARE_FUNCS = env.constants.compareFuncs
|
|
check.optional(function () {
|
|
function assert () {
|
|
env.assert(scope,
|
|
Array.prototype.join.call(arguments, ''),
|
|
'invalid stencil.func')
|
|
}
|
|
assert(value + '&&typeof ', value, '==="object"')
|
|
assert('!("cmp" in ', value, ')||(',
|
|
value, '.cmp in ', COMPARE_FUNCS, ')')
|
|
})
|
|
var cmp = scope.def(
|
|
'"cmp" in ', value,
|
|
'?', COMPARE_FUNCS, '[', value, '.cmp]',
|
|
':', GL_KEEP)
|
|
var ref = scope.def(value, '.ref|0')
|
|
var mask = scope.def(
|
|
'"mask" in ', value,
|
|
'?', value, '.mask|0:-1')
|
|
return [cmp, ref, mask]
|
|
})
|
|
|
|
case S_STENCIL_OPFRONT:
|
|
case S_STENCIL_OPBACK:
|
|
return parseParam(
|
|
function (value) {
|
|
check.commandType(value, 'object', param, env.commandStr)
|
|
var fail = value.fail || 'keep'
|
|
var zfail = value.zfail || 'keep'
|
|
var zpass = value.zpass || 'keep'
|
|
check.commandParameter(fail, stencilOps, prop + '.fail', env.commandStr)
|
|
check.commandParameter(zfail, stencilOps, prop + '.zfail', env.commandStr)
|
|
check.commandParameter(zpass, stencilOps, prop + '.zpass', env.commandStr)
|
|
return [
|
|
prop === S_STENCIL_OPBACK ? GL_BACK : GL_FRONT,
|
|
stencilOps[fail],
|
|
stencilOps[zfail],
|
|
stencilOps[zpass]
|
|
]
|
|
},
|
|
function (env, scope, value) {
|
|
var STENCIL_OPS = env.constants.stencilOps
|
|
|
|
check.optional(function () {
|
|
env.assert(scope,
|
|
value + '&&typeof ' + value + '==="object"',
|
|
'invalid ' + prop)
|
|
})
|
|
|
|
function read (name) {
|
|
check.optional(function () {
|
|
env.assert(scope,
|
|
'!("' + name + '" in ' + value + ')||' +
|
|
'(' + value + '.' + name + ' in ' + STENCIL_OPS + ')',
|
|
'invalid ' + prop + '.' + name + ', must be one of ' + Object.keys(stencilOps))
|
|
})
|
|
|
|
return scope.def(
|
|
'"', name, '" in ', value,
|
|
'?', STENCIL_OPS, '[', value, '.', name, ']:',
|
|
GL_KEEP)
|
|
}
|
|
|
|
return [
|
|
prop === S_STENCIL_OPBACK ? GL_BACK : GL_FRONT,
|
|
read('fail'),
|
|
read('zfail'),
|
|
read('zpass')
|
|
]
|
|
})
|
|
|
|
case S_POLYGON_OFFSET_OFFSET:
|
|
return parseParam(
|
|
function (value) {
|
|
check.commandType(value, 'object', param, env.commandStr)
|
|
var factor = value.factor | 0
|
|
var units = value.units | 0
|
|
check.commandType(factor, 'number', param + '.factor', env.commandStr)
|
|
check.commandType(units, 'number', param + '.units', env.commandStr)
|
|
return [factor, units]
|
|
},
|
|
function (env, scope, value) {
|
|
check.optional(function () {
|
|
env.assert(scope,
|
|
value + '&&typeof ' + value + '==="object"',
|
|
'invalid ' + prop)
|
|
})
|
|
|
|
var FACTOR = scope.def(value, '.factor|0')
|
|
var UNITS = scope.def(value, '.units|0')
|
|
|
|
return [FACTOR, UNITS]
|
|
})
|
|
|
|
case S_CULL_FACE:
|
|
return parseParam(
|
|
function (value) {
|
|
var face = 0
|
|
if (value === 'front') {
|
|
face = GL_FRONT
|
|
} else if (value === 'back') {
|
|
face = GL_BACK
|
|
}
|
|
check.command(!!face, param, env.commandStr)
|
|
return face
|
|
},
|
|
function (env, scope, value) {
|
|
check.optional(function () {
|
|
env.assert(scope,
|
|
value + '==="front"||' +
|
|
value + '==="back"',
|
|
'invalid cull.face')
|
|
})
|
|
return scope.def(value, '==="front"?', GL_FRONT, ':', GL_BACK)
|
|
})
|
|
|
|
case S_LINE_WIDTH:
|
|
return parseParam(
|
|
function (value) {
|
|
check.command(
|
|
typeof value === 'number' &&
|
|
value >= limits.lineWidthDims[0] &&
|
|
value <= limits.lineWidthDims[1],
|
|
'invalid line width, must be a positive number between ' +
|
|
limits.lineWidthDims[0] + ' and ' + limits.lineWidthDims[1], env.commandStr)
|
|
return value
|
|
},
|
|
function (env, scope, value) {
|
|
check.optional(function () {
|
|
env.assert(scope,
|
|
'typeof ' + value + '==="number"&&' +
|
|
value + '>=' + limits.lineWidthDims[0] + '&&' +
|
|
value + '<=' + limits.lineWidthDims[1],
|
|
'invalid line width')
|
|
})
|
|
|
|
return value
|
|
})
|
|
|
|
case S_FRONT_FACE:
|
|
return parseParam(
|
|
function (value) {
|
|
check.commandParameter(value, orientationType, param, env.commandStr)
|
|
return orientationType[value]
|
|
},
|
|
function (env, scope, value) {
|
|
check.optional(function () {
|
|
env.assert(scope,
|
|
value + '==="cw"||' +
|
|
value + '==="ccw"',
|
|
'invalid frontFace, must be one of cw,ccw')
|
|
})
|
|
return scope.def(value + '==="cw"?' + GL_CW + ':' + GL_CCW)
|
|
})
|
|
|
|
case S_COLOR_MASK:
|
|
return parseParam(
|
|
function (value) {
|
|
check.command(
|
|
isArrayLike(value) && value.length === 4,
|
|
'color.mask must be length 4 array', env.commandStr)
|
|
return value.map(function (v) { return !!v })
|
|
},
|
|
function (env, scope, value) {
|
|
check.optional(function () {
|
|
env.assert(scope,
|
|
env.shared.isArrayLike + '(' + value + ')&&' +
|
|
value + '.length===4',
|
|
'invalid color.mask')
|
|
})
|
|
return loop(4, function (i) {
|
|
return '!!' + value + '[' + i + ']'
|
|
})
|
|
})
|
|
|
|
case S_SAMPLE_COVERAGE:
|
|
return parseParam(
|
|
function (value) {
|
|
check.command(typeof value === 'object' && value, param, env.commandStr)
|
|
var sampleValue = 'value' in value ? value.value : 1
|
|
var sampleInvert = !!value.invert
|
|
check.command(
|
|
typeof sampleValue === 'number' &&
|
|
sampleValue >= 0 && sampleValue <= 1,
|
|
'sample.coverage.value must be a number between 0 and 1', env.commandStr)
|
|
return [sampleValue, sampleInvert]
|
|
},
|
|
function (env, scope, value) {
|
|
check.optional(function () {
|
|
env.assert(scope,
|
|
value + '&&typeof ' + value + '==="object"',
|
|
'invalid sample.coverage')
|
|
})
|
|
var VALUE = scope.def(
|
|
'"value" in ', value, '?+', value, '.value:1')
|
|
var INVERT = scope.def('!!', value, '.invert')
|
|
return [VALUE, INVERT]
|
|
})
|
|
}
|
|
})
|
|
|
|
return STATE
|
|
}
|
|
|
|
function parseUniforms (uniforms, env) {
|
|
var staticUniforms = uniforms.static
|
|
var dynamicUniforms = uniforms.dynamic
|
|
|
|
var UNIFORMS = {}
|
|
|
|
Object.keys(staticUniforms).forEach(function (name) {
|
|
var value = staticUniforms[name]
|
|
var result
|
|
if (typeof value === 'number' ||
|
|
typeof value === 'boolean') {
|
|
result = createStaticDecl(function () {
|
|
return value
|
|
})
|
|
} else if (typeof value === 'function') {
|
|
var reglType = value._reglType
|
|
if (reglType === 'texture2d' ||
|
|
reglType === 'textureCube') {
|
|
result = createStaticDecl(function (env) {
|
|
return env.link(value)
|
|
})
|
|
} else if (reglType === 'framebuffer' ||
|
|
reglType === 'framebufferCube') {
|
|
check.command(value.color.length > 0,
|
|
'missing color attachment for framebuffer sent to uniform "' + name + '"', env.commandStr)
|
|
result = createStaticDecl(function (env) {
|
|
return env.link(value.color[0])
|
|
})
|
|
} else {
|
|
check.commandRaise('invalid data for uniform "' + name + '"', env.commandStr)
|
|
}
|
|
} else if (isArrayLike(value)) {
|
|
result = createStaticDecl(function (env) {
|
|
var ITEM = env.global.def('[',
|
|
loop(value.length, function (i) {
|
|
check.command(
|
|
typeof value[i] === 'number' ||
|
|
typeof value[i] === 'boolean',
|
|
'invalid uniform ' + name, env.commandStr)
|
|
return value[i]
|
|
}), ']')
|
|
return ITEM
|
|
})
|
|
} else {
|
|
check.commandRaise('invalid or missing data for uniform "' + name + '"', env.commandStr)
|
|
}
|
|
result.value = value
|
|
UNIFORMS[name] = result
|
|
})
|
|
|
|
Object.keys(dynamicUniforms).forEach(function (key) {
|
|
var dyn = dynamicUniforms[key]
|
|
UNIFORMS[key] = createDynamicDecl(dyn, function (env, scope) {
|
|
return env.invoke(scope, dyn)
|
|
})
|
|
})
|
|
|
|
return UNIFORMS
|
|
}
|
|
|
|
function parseAttributes (attributes, env) {
|
|
var staticAttributes = attributes.static
|
|
var dynamicAttributes = attributes.dynamic
|
|
|
|
var attributeDefs = {}
|
|
|
|
Object.keys(staticAttributes).forEach(function (attribute) {
|
|
var value = staticAttributes[attribute]
|
|
var id = stringStore.id(attribute)
|
|
|
|
var record = new AttributeRecord()
|
|
if (isBufferArgs(value)) {
|
|
record.state = ATTRIB_STATE_POINTER
|
|
record.buffer = bufferState.getBuffer(
|
|
bufferState.create(value, GL_ARRAY_BUFFER, false, true))
|
|
record.type = 0
|
|
} else {
|
|
var buffer = bufferState.getBuffer(value)
|
|
if (buffer) {
|
|
record.state = ATTRIB_STATE_POINTER
|
|
record.buffer = buffer
|
|
record.type = 0
|
|
} else {
|
|
check.command(typeof value === 'object' && value,
|
|
'invalid data for attribute ' + attribute, env.commandStr)
|
|
if ('constant' in value) {
|
|
var constant = value.constant
|
|
record.buffer = 'null'
|
|
record.state = ATTRIB_STATE_CONSTANT
|
|
if (typeof constant === 'number') {
|
|
record.x = constant
|
|
} else {
|
|
check.command(
|
|
isArrayLike(constant) &&
|
|
constant.length > 0 &&
|
|
constant.length <= 4,
|
|
'invalid constant for attribute ' + attribute, env.commandStr)
|
|
CUTE_COMPONENTS.forEach(function (c, i) {
|
|
if (i < constant.length) {
|
|
record[c] = constant[i]
|
|
}
|
|
})
|
|
}
|
|
} else {
|
|
if (isBufferArgs(value.buffer)) {
|
|
buffer = bufferState.getBuffer(
|
|
bufferState.create(value.buffer, GL_ARRAY_BUFFER, false, true))
|
|
} else {
|
|
buffer = bufferState.getBuffer(value.buffer)
|
|
}
|
|
check.command(!!buffer, 'missing buffer for attribute "' + attribute + '"', env.commandStr)
|
|
|
|
var offset = value.offset | 0
|
|
check.command(offset >= 0,
|
|
'invalid offset for attribute "' + attribute + '"', env.commandStr)
|
|
|
|
var stride = value.stride | 0
|
|
check.command(stride >= 0 && stride < 256,
|
|
'invalid stride for attribute "' + attribute + '", must be integer betweeen [0, 255]', env.commandStr)
|
|
|
|
var size = value.size | 0
|
|
check.command(!('size' in value) || (size > 0 && size <= 4),
|
|
'invalid size for attribute "' + attribute + '", must be 1,2,3,4', env.commandStr)
|
|
|
|
var normalized = !!value.normalized
|
|
|
|
var type = 0
|
|
if ('type' in value) {
|
|
check.commandParameter(
|
|
value.type, glTypes,
|
|
'invalid type for attribute ' + attribute, env.commandStr)
|
|
type = glTypes[value.type]
|
|
}
|
|
|
|
var divisor = value.divisor | 0
|
|
check.optional(function () {
|
|
if ('divisor' in value) {
|
|
check.command(divisor === 0 || extInstancing,
|
|
'cannot specify divisor for attribute "' + attribute + '", instancing not supported', env.commandStr)
|
|
check.command(divisor >= 0,
|
|
'invalid divisor for attribute "' + attribute + '"', env.commandStr)
|
|
}
|
|
|
|
var command = env.commandStr
|
|
|
|
var VALID_KEYS = [
|
|
'buffer',
|
|
'offset',
|
|
'divisor',
|
|
'normalized',
|
|
'type',
|
|
'size',
|
|
'stride'
|
|
]
|
|
|
|
Object.keys(value).forEach(function (prop) {
|
|
check.command(
|
|
VALID_KEYS.indexOf(prop) >= 0,
|
|
'unknown parameter "' + prop + '" for attribute pointer "' + attribute + '" (valid parameters are ' + VALID_KEYS + ')',
|
|
command)
|
|
})
|
|
})
|
|
|
|
record.buffer = buffer
|
|
record.state = ATTRIB_STATE_POINTER
|
|
record.size = size
|
|
record.normalized = normalized
|
|
record.type = type || buffer.dtype
|
|
record.offset = offset
|
|
record.stride = stride
|
|
record.divisor = divisor
|
|
}
|
|
}
|
|
}
|
|
|
|
attributeDefs[attribute] = createStaticDecl(function (env, scope) {
|
|
var cache = env.attribCache
|
|
if (id in cache) {
|
|
return cache[id]
|
|
}
|
|
var result = {
|
|
isStream: false
|
|
}
|
|
Object.keys(record).forEach(function (key) {
|
|
result[key] = record[key]
|
|
})
|
|
if (record.buffer) {
|
|
result.buffer = env.link(record.buffer)
|
|
result.type = result.type || (result.buffer + '.dtype')
|
|
}
|
|
cache[id] = result
|
|
return result
|
|
})
|
|
})
|
|
|
|
Object.keys(dynamicAttributes).forEach(function (attribute) {
|
|
var dyn = dynamicAttributes[attribute]
|
|
|
|
function appendAttributeCode (env, block) {
|
|
var VALUE = env.invoke(block, dyn)
|
|
|
|
var shared = env.shared
|
|
var constants = env.constants
|
|
|
|
var IS_BUFFER_ARGS = shared.isBufferArgs
|
|
var BUFFER_STATE = shared.buffer
|
|
|
|
// Perform validation on attribute
|
|
check.optional(function () {
|
|
env.assert(block,
|
|
VALUE + '&&(typeof ' + VALUE + '==="object"||typeof ' +
|
|
VALUE + '==="function")&&(' +
|
|
IS_BUFFER_ARGS + '(' + VALUE + ')||' +
|
|
BUFFER_STATE + '.getBuffer(' + VALUE + ')||' +
|
|
BUFFER_STATE + '.getBuffer(' + VALUE + '.buffer)||' +
|
|
IS_BUFFER_ARGS + '(' + VALUE + '.buffer)||' +
|
|
'("constant" in ' + VALUE +
|
|
'&&(typeof ' + VALUE + '.constant==="number"||' +
|
|
shared.isArrayLike + '(' + VALUE + '.constant))))',
|
|
'invalid dynamic attribute "' + attribute + '"')
|
|
})
|
|
|
|
// allocate names for result
|
|
var result = {
|
|
isStream: block.def(false)
|
|
}
|
|
var defaultRecord = new AttributeRecord()
|
|
defaultRecord.state = ATTRIB_STATE_POINTER
|
|
Object.keys(defaultRecord).forEach(function (key) {
|
|
result[key] = block.def('' + defaultRecord[key])
|
|
})
|
|
|
|
var BUFFER = result.buffer
|
|
var TYPE = result.type
|
|
block(
|
|
'if(', IS_BUFFER_ARGS, '(', VALUE, ')){',
|
|
result.isStream, '=true;',
|
|
BUFFER, '=', BUFFER_STATE, '.createStream(', GL_ARRAY_BUFFER, ',', VALUE, ');',
|
|
TYPE, '=', BUFFER, '.dtype;',
|
|
'}else{',
|
|
BUFFER, '=', BUFFER_STATE, '.getBuffer(', VALUE, ');',
|
|
'if(', BUFFER, '){',
|
|
TYPE, '=', BUFFER, '.dtype;',
|
|
'}else if("constant" in ', VALUE, '){',
|
|
result.state, '=', ATTRIB_STATE_CONSTANT, ';',
|
|
'if(typeof ' + VALUE + '.constant === "number"){',
|
|
result[CUTE_COMPONENTS[0]], '=', VALUE, '.constant;',
|
|
CUTE_COMPONENTS.slice(1).map(function (n) {
|
|
return result[n]
|
|
}).join('='), '=0;',
|
|
'}else{',
|
|
CUTE_COMPONENTS.map(function (name, i) {
|
|
return (
|
|
result[name] + '=' + VALUE + '.constant.length>' + i +
|
|
'?' + VALUE + '.constant[' + i + ']:0;'
|
|
)
|
|
}).join(''),
|
|
'}}else{',
|
|
'if(', IS_BUFFER_ARGS, '(', VALUE, '.buffer)){',
|
|
BUFFER, '=', BUFFER_STATE, '.createStream(', GL_ARRAY_BUFFER, ',', VALUE, '.buffer);',
|
|
'}else{',
|
|
BUFFER, '=', BUFFER_STATE, '.getBuffer(', VALUE, '.buffer);',
|
|
'}',
|
|
TYPE, '="type" in ', VALUE, '?',
|
|
constants.glTypes, '[', VALUE, '.type]:', BUFFER, '.dtype;',
|
|
result.normalized, '=!!', VALUE, '.normalized;')
|
|
function emitReadRecord (name) {
|
|
block(result[name], '=', VALUE, '.', name, '|0;')
|
|
}
|
|
emitReadRecord('size')
|
|
emitReadRecord('offset')
|
|
emitReadRecord('stride')
|
|
emitReadRecord('divisor')
|
|
|
|
block('}}')
|
|
|
|
block.exit(
|
|
'if(', result.isStream, '){',
|
|
BUFFER_STATE, '.destroyStream(', BUFFER, ');',
|
|
'}')
|
|
|
|
return result
|
|
}
|
|
|
|
attributeDefs[attribute] = createDynamicDecl(dyn, appendAttributeCode)
|
|
})
|
|
|
|
return attributeDefs
|
|
}
|
|
|
|
function parseContext (context) {
|
|
var staticContext = context.static
|
|
var dynamicContext = context.dynamic
|
|
var result = {}
|
|
|
|
Object.keys(staticContext).forEach(function (name) {
|
|
var value = staticContext[name]
|
|
result[name] = createStaticDecl(function (env, scope) {
|
|
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
return '' + value
|
|
} else {
|
|
return env.link(value)
|
|
}
|
|
})
|
|
})
|
|
|
|
Object.keys(dynamicContext).forEach(function (name) {
|
|
var dyn = dynamicContext[name]
|
|
result[name] = createDynamicDecl(dyn, function (env, scope) {
|
|
return env.invoke(scope, dyn)
|
|
})
|
|
})
|
|
|
|
return result
|
|
}
|
|
|
|
function parseArguments (options, attributes, uniforms, context, env) {
|
|
var staticOptions = options.static
|
|
var dynamicOptions = options.dynamic
|
|
|
|
check.optional(function () {
|
|
var KEY_NAMES = [
|
|
S_FRAMEBUFFER,
|
|
S_VERT,
|
|
S_FRAG,
|
|
S_ELEMENTS,
|
|
S_PRIMITIVE,
|
|
S_OFFSET,
|
|
S_COUNT,
|
|
S_INSTANCES,
|
|
S_PROFILE,
|
|
S_VAO
|
|
].concat(GL_STATE_NAMES)
|
|
|
|
function checkKeys (dict) {
|
|
Object.keys(dict).forEach(function (key) {
|
|
check.command(
|
|
KEY_NAMES.indexOf(key) >= 0,
|
|
'unknown parameter "' + key + '"',
|
|
env.commandStr)
|
|
})
|
|
}
|
|
|
|
checkKeys(staticOptions)
|
|
checkKeys(dynamicOptions)
|
|
})
|
|
|
|
var attribLocations = parseAttribLocations(options, attributes)
|
|
|
|
var framebuffer = parseFramebuffer(options, env)
|
|
var viewportAndScissor = parseViewportScissor(options, framebuffer, env)
|
|
var draw = parseDraw(options, env)
|
|
var state = parseGLState(options, env)
|
|
var shader = parseProgram(options, env, attribLocations)
|
|
|
|
function copyBox (name) {
|
|
var defn = viewportAndScissor[name]
|
|
if (defn) {
|
|
state[name] = defn
|
|
}
|
|
}
|
|
copyBox(S_VIEWPORT)
|
|
copyBox(propName(S_SCISSOR_BOX))
|
|
|
|
var dirty = Object.keys(state).length > 0
|
|
|
|
var result = {
|
|
framebuffer: framebuffer,
|
|
draw: draw,
|
|
shader: shader,
|
|
state: state,
|
|
dirty: dirty,
|
|
scopeVAO: null,
|
|
drawVAO: null,
|
|
useVAO: false,
|
|
attributes: {}
|
|
}
|
|
|
|
result.profile = parseProfile(options, env)
|
|
result.uniforms = parseUniforms(uniforms, env)
|
|
result.drawVAO = result.scopeVAO = draw.vao
|
|
// special case: check if we can statically allocate a vertex array object for this program
|
|
if (!result.drawVAO &&
|
|
shader.program &&
|
|
!attribLocations &&
|
|
extensions.angle_instanced_arrays &&
|
|
draw.static.elements) {
|
|
var useVAO = true
|
|
var staticBindings = shader.program.attributes.map(function (attr) {
|
|
var binding = attributes.static[attr]
|
|
useVAO = useVAO && !!binding
|
|
return binding
|
|
})
|
|
if (useVAO && staticBindings.length > 0) {
|
|
var vao = attributeState.getVAO(attributeState.createVAO({
|
|
attributes: staticBindings,
|
|
elements: draw.static.elements
|
|
}))
|
|
result.drawVAO = new Declaration(null, null, null, function (env, scope) {
|
|
return env.link(vao)
|
|
})
|
|
result.useVAO = true
|
|
}
|
|
}
|
|
if (attribLocations) {
|
|
result.useVAO = true
|
|
} else {
|
|
result.attributes = parseAttributes(attributes, env)
|
|
}
|
|
result.context = parseContext(context, env)
|
|
return result
|
|
}
|
|
|
|
// ===================================================
|
|
// ===================================================
|
|
// COMMON UPDATE FUNCTIONS
|
|
// ===================================================
|
|
// ===================================================
|
|
function emitContext (env, scope, context) {
|
|
var shared = env.shared
|
|
var CONTEXT = shared.context
|
|
|
|
var contextEnter = env.scope()
|
|
|
|
Object.keys(context).forEach(function (name) {
|
|
scope.save(CONTEXT, '.' + name)
|
|
var defn = context[name]
|
|
var value = defn.append(env, scope)
|
|
if (Array.isArray(value)) {
|
|
contextEnter(CONTEXT, '.', name, '=[', value.join(), '];')
|
|
} else {
|
|
contextEnter(CONTEXT, '.', name, '=', value, ';')
|
|
}
|
|
})
|
|
|
|
scope(contextEnter)
|
|
}
|
|
|
|
// ===================================================
|
|
// ===================================================
|
|
// COMMON DRAWING FUNCTIONS
|
|
// ===================================================
|
|
// ===================================================
|
|
function emitPollFramebuffer (env, scope, framebuffer, skipCheck) {
|
|
var shared = env.shared
|
|
|
|
var GL = shared.gl
|
|
var FRAMEBUFFER_STATE = shared.framebuffer
|
|
var EXT_DRAW_BUFFERS
|
|
if (extDrawBuffers) {
|
|
EXT_DRAW_BUFFERS = scope.def(shared.extensions, '.webgl_draw_buffers')
|
|
}
|
|
|
|
var constants = env.constants
|
|
|
|
var DRAW_BUFFERS = constants.drawBuffer
|
|
var BACK_BUFFER = constants.backBuffer
|
|
|
|
var NEXT
|
|
if (framebuffer) {
|
|
NEXT = framebuffer.append(env, scope)
|
|
} else {
|
|
NEXT = scope.def(FRAMEBUFFER_STATE, '.next')
|
|
}
|
|
|
|
if (!skipCheck) {
|
|
scope('if(', NEXT, '!==', FRAMEBUFFER_STATE, '.cur){')
|
|
}
|
|
scope(
|
|
'if(', NEXT, '){',
|
|
GL, '.bindFramebuffer(', GL_FRAMEBUFFER, ',', NEXT, '.framebuffer);')
|
|
if (extDrawBuffers) {
|
|
scope(EXT_DRAW_BUFFERS, '.drawBuffersWEBGL(',
|
|
DRAW_BUFFERS, '[', NEXT, '.colorAttachments.length]);')
|
|
}
|
|
scope('}else{',
|
|
GL, '.bindFramebuffer(', GL_FRAMEBUFFER, ',null);')
|
|
if (extDrawBuffers) {
|
|
scope(EXT_DRAW_BUFFERS, '.drawBuffersWEBGL(', BACK_BUFFER, ');')
|
|
}
|
|
scope(
|
|
'}',
|
|
FRAMEBUFFER_STATE, '.cur=', NEXT, ';')
|
|
if (!skipCheck) {
|
|
scope('}')
|
|
}
|
|
}
|
|
|
|
function emitPollState (env, scope, args) {
|
|
var shared = env.shared
|
|
|
|
var GL = shared.gl
|
|
|
|
var CURRENT_VARS = env.current
|
|
var NEXT_VARS = env.next
|
|
var CURRENT_STATE = shared.current
|
|
var NEXT_STATE = shared.next
|
|
|
|
var block = env.cond(CURRENT_STATE, '.dirty')
|
|
|
|
GL_STATE_NAMES.forEach(function (prop) {
|
|
var param = propName(prop)
|
|
if (param in args.state) {
|
|
return
|
|
}
|
|
|
|
var NEXT, CURRENT
|
|
if (param in NEXT_VARS) {
|
|
NEXT = NEXT_VARS[param]
|
|
CURRENT = CURRENT_VARS[param]
|
|
var parts = loop(currentState[param].length, function (i) {
|
|
return block.def(NEXT, '[', i, ']')
|
|
})
|
|
block(env.cond(parts.map(function (p, i) {
|
|
return p + '!==' + CURRENT + '[' + i + ']'
|
|
}).join('||'))
|
|
.then(
|
|
GL, '.', GL_VARIABLES[param], '(', parts, ');',
|
|
parts.map(function (p, i) {
|
|
return CURRENT + '[' + i + ']=' + p
|
|
}).join(';'), ';'))
|
|
} else {
|
|
NEXT = block.def(NEXT_STATE, '.', param)
|
|
var ifte = env.cond(NEXT, '!==', CURRENT_STATE, '.', param)
|
|
block(ifte)
|
|
if (param in GL_FLAGS) {
|
|
ifte(
|
|
env.cond(NEXT)
|
|
.then(GL, '.enable(', GL_FLAGS[param], ');')
|
|
.else(GL, '.disable(', GL_FLAGS[param], ');'),
|
|
CURRENT_STATE, '.', param, '=', NEXT, ';')
|
|
} else {
|
|
ifte(
|
|
GL, '.', GL_VARIABLES[param], '(', NEXT, ');',
|
|
CURRENT_STATE, '.', param, '=', NEXT, ';')
|
|
}
|
|
}
|
|
})
|
|
if (Object.keys(args.state).length === 0) {
|
|
block(CURRENT_STATE, '.dirty=false;')
|
|
}
|
|
scope(block)
|
|
}
|
|
|
|
function emitSetOptions (env, scope, options, filter) {
|
|
var shared = env.shared
|
|
var CURRENT_VARS = env.current
|
|
var CURRENT_STATE = shared.current
|
|
var GL = shared.gl
|
|
sortState(Object.keys(options)).forEach(function (param) {
|
|
var defn = options[param]
|
|
if (filter && !filter(defn)) {
|
|
return
|
|
}
|
|
var variable = defn.append(env, scope)
|
|
if (GL_FLAGS[param]) {
|
|
var flag = GL_FLAGS[param]
|
|
if (isStatic(defn)) {
|
|
if (variable) {
|
|
scope(GL, '.enable(', flag, ');')
|
|
} else {
|
|
scope(GL, '.disable(', flag, ');')
|
|
}
|
|
} else {
|
|
scope(env.cond(variable)
|
|
.then(GL, '.enable(', flag, ');')
|
|
.else(GL, '.disable(', flag, ');'))
|
|
}
|
|
scope(CURRENT_STATE, '.', param, '=', variable, ';')
|
|
} else if (isArrayLike(variable)) {
|
|
var CURRENT = CURRENT_VARS[param]
|
|
scope(
|
|
GL, '.', GL_VARIABLES[param], '(', variable, ');',
|
|
variable.map(function (v, i) {
|
|
return CURRENT + '[' + i + ']=' + v
|
|
}).join(';'), ';')
|
|
} else {
|
|
scope(
|
|
GL, '.', GL_VARIABLES[param], '(', variable, ');',
|
|
CURRENT_STATE, '.', param, '=', variable, ';')
|
|
}
|
|
})
|
|
}
|
|
|
|
function injectExtensions (env, scope) {
|
|
if (extInstancing) {
|
|
env.instancing = scope.def(
|
|
env.shared.extensions, '.angle_instanced_arrays')
|
|
}
|
|
}
|
|
|
|
function emitProfile (env, scope, args, useScope, incrementCounter) {
|
|
var shared = env.shared
|
|
var STATS = env.stats
|
|
var CURRENT_STATE = shared.current
|
|
var TIMER = shared.timer
|
|
var profileArg = args.profile
|
|
|
|
function perfCounter () {
|
|
if (typeof performance === 'undefined') {
|
|
return 'Date.now()'
|
|
} else {
|
|
return 'performance.now()'
|
|
}
|
|
}
|
|
|
|
var CPU_START, QUERY_COUNTER
|
|
function emitProfileStart (block) {
|
|
CPU_START = scope.def()
|
|
block(CPU_START, '=', perfCounter(), ';')
|
|
if (typeof incrementCounter === 'string') {
|
|
block(STATS, '.count+=', incrementCounter, ';')
|
|
} else {
|
|
block(STATS, '.count++;')
|
|
}
|
|
if (timer) {
|
|
if (useScope) {
|
|
QUERY_COUNTER = scope.def()
|
|
block(QUERY_COUNTER, '=', TIMER, '.getNumPendingQueries();')
|
|
} else {
|
|
block(TIMER, '.beginQuery(', STATS, ');')
|
|
}
|
|
}
|
|
}
|
|
|
|
function emitProfileEnd (block) {
|
|
block(STATS, '.cpuTime+=', perfCounter(), '-', CPU_START, ';')
|
|
if (timer) {
|
|
if (useScope) {
|
|
block(TIMER, '.pushScopeStats(',
|
|
QUERY_COUNTER, ',',
|
|
TIMER, '.getNumPendingQueries(),',
|
|
STATS, ');')
|
|
} else {
|
|
block(TIMER, '.endQuery();')
|
|
}
|
|
}
|
|
}
|
|
|
|
function scopeProfile (value) {
|
|
var prev = scope.def(CURRENT_STATE, '.profile')
|
|
scope(CURRENT_STATE, '.profile=', value, ';')
|
|
scope.exit(CURRENT_STATE, '.profile=', prev, ';')
|
|
}
|
|
|
|
var USE_PROFILE
|
|
if (profileArg) {
|
|
if (isStatic(profileArg)) {
|
|
if (profileArg.enable) {
|
|
emitProfileStart(scope)
|
|
emitProfileEnd(scope.exit)
|
|
scopeProfile('true')
|
|
} else {
|
|
scopeProfile('false')
|
|
}
|
|
return
|
|
}
|
|
USE_PROFILE = profileArg.append(env, scope)
|
|
scopeProfile(USE_PROFILE)
|
|
} else {
|
|
USE_PROFILE = scope.def(CURRENT_STATE, '.profile')
|
|
}
|
|
|
|
var start = env.block()
|
|
emitProfileStart(start)
|
|
scope('if(', USE_PROFILE, '){', start, '}')
|
|
var end = env.block()
|
|
emitProfileEnd(end)
|
|
scope.exit('if(', USE_PROFILE, '){', end, '}')
|
|
}
|
|
|
|
function emitAttributes (env, scope, args, attributes, filter) {
|
|
var shared = env.shared
|
|
|
|
function typeLength (x) {
|
|
switch (x) {
|
|
case GL_FLOAT_VEC2:
|
|
case GL_INT_VEC2:
|
|
case GL_BOOL_VEC2:
|
|
return 2
|
|
case GL_FLOAT_VEC3:
|
|
case GL_INT_VEC3:
|
|
case GL_BOOL_VEC3:
|
|
return 3
|
|
case GL_FLOAT_VEC4:
|
|
case GL_INT_VEC4:
|
|
case GL_BOOL_VEC4:
|
|
return 4
|
|
default:
|
|
return 1
|
|
}
|
|
}
|
|
|
|
function emitBindAttribute (ATTRIBUTE, size, record) {
|
|
var GL = shared.gl
|
|
|
|
var LOCATION = scope.def(ATTRIBUTE, '.location')
|
|
var BINDING = scope.def(shared.attributes, '[', LOCATION, ']')
|
|
|
|
var STATE = record.state
|
|
var BUFFER = record.buffer
|
|
var CONST_COMPONENTS = [
|
|
record.x,
|
|
record.y,
|
|
record.z,
|
|
record.w
|
|
]
|
|
|
|
var COMMON_KEYS = [
|
|
'buffer',
|
|
'normalized',
|
|
'offset',
|
|
'stride'
|
|
]
|
|
|
|
function emitBuffer () {
|
|
scope(
|
|
'if(!', BINDING, '.buffer){',
|
|
GL, '.enableVertexAttribArray(', LOCATION, ');}')
|
|
|
|
var TYPE = record.type
|
|
var SIZE
|
|
if (!record.size) {
|
|
SIZE = size
|
|
} else {
|
|
SIZE = scope.def(record.size, '||', size)
|
|
}
|
|
|
|
scope('if(',
|
|
BINDING, '.type!==', TYPE, '||',
|
|
BINDING, '.size!==', SIZE, '||',
|
|
COMMON_KEYS.map(function (key) {
|
|
return BINDING + '.' + key + '!==' + record[key]
|
|
}).join('||'),
|
|
'){',
|
|
GL, '.bindBuffer(', GL_ARRAY_BUFFER, ',', BUFFER, '.buffer);',
|
|
GL, '.vertexAttribPointer(', [
|
|
LOCATION,
|
|
SIZE,
|
|
TYPE,
|
|
record.normalized,
|
|
record.stride,
|
|
record.offset
|
|
], ');',
|
|
BINDING, '.type=', TYPE, ';',
|
|
BINDING, '.size=', SIZE, ';',
|
|
COMMON_KEYS.map(function (key) {
|
|
return BINDING + '.' + key + '=' + record[key] + ';'
|
|
}).join(''),
|
|
'}')
|
|
|
|
if (extInstancing) {
|
|
var DIVISOR = record.divisor
|
|
scope(
|
|
'if(', BINDING, '.divisor!==', DIVISOR, '){',
|
|
env.instancing, '.vertexAttribDivisorANGLE(', [LOCATION, DIVISOR], ');',
|
|
BINDING, '.divisor=', DIVISOR, ';}')
|
|
}
|
|
}
|
|
|
|
function emitConstant () {
|
|
scope(
|
|
'if(', BINDING, '.buffer){',
|
|
GL, '.disableVertexAttribArray(', LOCATION, ');',
|
|
BINDING, '.buffer=null;',
|
|
'}if(', CUTE_COMPONENTS.map(function (c, i) {
|
|
return BINDING + '.' + c + '!==' + CONST_COMPONENTS[i]
|
|
}).join('||'), '){',
|
|
GL, '.vertexAttrib4f(', LOCATION, ',', CONST_COMPONENTS, ');',
|
|
CUTE_COMPONENTS.map(function (c, i) {
|
|
return BINDING + '.' + c + '=' + CONST_COMPONENTS[i] + ';'
|
|
}).join(''),
|
|
'}')
|
|
}
|
|
|
|
if (STATE === ATTRIB_STATE_POINTER) {
|
|
emitBuffer()
|
|
} else if (STATE === ATTRIB_STATE_CONSTANT) {
|
|
emitConstant()
|
|
} else {
|
|
scope('if(', STATE, '===', ATTRIB_STATE_POINTER, '){')
|
|
emitBuffer()
|
|
scope('}else{')
|
|
emitConstant()
|
|
scope('}')
|
|
}
|
|
}
|
|
|
|
attributes.forEach(function (attribute) {
|
|
var name = attribute.name
|
|
var arg = args.attributes[name]
|
|
var record
|
|
if (arg) {
|
|
if (!filter(arg)) {
|
|
return
|
|
}
|
|
record = arg.append(env, scope)
|
|
} else {
|
|
if (!filter(SCOPE_DECL)) {
|
|
return
|
|
}
|
|
var scopeAttrib = env.scopeAttrib(name)
|
|
check.optional(function () {
|
|
env.assert(scope,
|
|
scopeAttrib + '.state',
|
|
'missing attribute ' + name)
|
|
})
|
|
record = {}
|
|
Object.keys(new AttributeRecord()).forEach(function (key) {
|
|
record[key] = scope.def(scopeAttrib, '.', key)
|
|
})
|
|
}
|
|
emitBindAttribute(
|
|
env.link(attribute), typeLength(attribute.info.type), record)
|
|
})
|
|
}
|
|
|
|
function emitUniforms (env, scope, args, uniforms, filter, isBatchInnerLoop) {
|
|
var shared = env.shared
|
|
var GL = shared.gl
|
|
|
|
var definedArrUniforms = {}
|
|
var infix
|
|
for (var i = 0; i < uniforms.length; ++i) {
|
|
var uniform = uniforms[i]
|
|
var name = uniform.name
|
|
var type = uniform.info.type
|
|
var size = uniform.info.size
|
|
var arg = args.uniforms[name]
|
|
if (size > 1) {
|
|
// either foo[n] or foos, avoid define both
|
|
if (!arg) {
|
|
continue
|
|
}
|
|
var arrUniformName = name.replace('[0]', '')
|
|
if (definedArrUniforms[arrUniformName]) {
|
|
continue
|
|
}
|
|
definedArrUniforms[arrUniformName] = 1
|
|
}
|
|
var UNIFORM = env.link(uniform)
|
|
var LOCATION = UNIFORM + '.location'
|
|
|
|
var VALUE
|
|
if (arg) {
|
|
if (!filter(arg)) {
|
|
continue
|
|
}
|
|
if (isStatic(arg)) {
|
|
var value = arg.value
|
|
check.command(
|
|
value !== null && typeof value !== 'undefined',
|
|
'missing uniform "' + name + '"', env.commandStr)
|
|
if (type === GL_SAMPLER_2D || type === GL_SAMPLER_CUBE) {
|
|
check.command(
|
|
typeof value === 'function' &&
|
|
((type === GL_SAMPLER_2D &&
|
|
(value._reglType === 'texture2d' ||
|
|
value._reglType === 'framebuffer')) ||
|
|
(type === GL_SAMPLER_CUBE &&
|
|
(value._reglType === 'textureCube' ||
|
|
value._reglType === 'framebufferCube'))),
|
|
'invalid texture for uniform ' + name, env.commandStr)
|
|
var TEX_VALUE = env.link(value._texture || value.color[0]._texture)
|
|
scope(GL, '.uniform1i(', LOCATION, ',', TEX_VALUE + '.bind());')
|
|
scope.exit(TEX_VALUE, '.unbind();')
|
|
} else if (
|
|
type === GL_FLOAT_MAT2 ||
|
|
type === GL_FLOAT_MAT3 ||
|
|
type === GL_FLOAT_MAT4) {
|
|
check.optional(function () {
|
|
check.command(isArrayLike(value),
|
|
'invalid matrix for uniform ' + name, env.commandStr)
|
|
check.command(
|
|
(type === GL_FLOAT_MAT2 && value.length === 4) ||
|
|
(type === GL_FLOAT_MAT3 && value.length === 9) ||
|
|
(type === GL_FLOAT_MAT4 && value.length === 16),
|
|
'invalid length for matrix uniform ' + name, env.commandStr)
|
|
})
|
|
var MAT_VALUE = env.global.def('new Float32Array([' +
|
|
Array.prototype.slice.call(value) + '])')
|
|
var dim = 2
|
|
if (type === GL_FLOAT_MAT3) {
|
|
dim = 3
|
|
} else if (type === GL_FLOAT_MAT4) {
|
|
dim = 4
|
|
}
|
|
scope(
|
|
GL, '.uniformMatrix', dim, 'fv(',
|
|
LOCATION, ',false,', MAT_VALUE, ');')
|
|
} else {
|
|
switch (type) {
|
|
case GL_FLOAT:
|
|
if (size === 1) {
|
|
check.commandType(value, 'number', 'uniform ' + name, env.commandStr)
|
|
} else {
|
|
check.command(
|
|
isArrayLike(value) && (value.length === size),
|
|
'uniform ' + name, env.commandStr)
|
|
}
|
|
infix = '1f'
|
|
break
|
|
case GL_FLOAT_VEC2:
|
|
check.command(
|
|
isArrayLike(value) && (value.length && value.length % 2 === 0 && value.length <= size * 2),
|
|
'uniform ' + name, env.commandStr)
|
|
infix = '2f'
|
|
break
|
|
case GL_FLOAT_VEC3:
|
|
check.command(
|
|
isArrayLike(value) && (value.length && value.length % 3 === 0 && value.length <= size * 3),
|
|
'uniform ' + name, env.commandStr)
|
|
infix = '3f'
|
|
break
|
|
case GL_FLOAT_VEC4:
|
|
check.command(
|
|
isArrayLike(value) && (value.length && value.length % 4 === 0 && value.length <= size * 4),
|
|
'uniform ' + name, env.commandStr)
|
|
infix = '4f'
|
|
break
|
|
case GL_BOOL:
|
|
if (size === 1) {
|
|
check.commandType(value, 'boolean', 'uniform ' + name, env.commandStr)
|
|
} else {
|
|
check.command(
|
|
isArrayLike(value) && (value.length === size),
|
|
'uniform ' + name, env.commandStr)
|
|
}
|
|
infix = '1i'
|
|
break
|
|
case GL_INT:
|
|
if (size === 1) {
|
|
check.commandType(value, 'number', 'uniform ' + name, env.commandStr)
|
|
} else {
|
|
check.command(
|
|
isArrayLike(value) && (value.length === size),
|
|
'uniform ' + name, env.commandStr)
|
|
}
|
|
infix = '1i'
|
|
break
|
|
case GL_BOOL_VEC2:
|
|
check.command(
|
|
isArrayLike(value) && (value.length && value.length % 2 === 0 && value.length <= size * 2),
|
|
'uniform ' + name, env.commandStr)
|
|
infix = '2i'
|
|
break
|
|
case GL_INT_VEC2:
|
|
check.command(
|
|
isArrayLike(value) && (value.length && value.length % 2 === 0 && value.length <= size * 2),
|
|
'uniform ' + name, env.commandStr)
|
|
infix = '2i'
|
|
break
|
|
case GL_BOOL_VEC3:
|
|
check.command(
|
|
isArrayLike(value) && (value.length && value.length % 3 === 0 && value.length <= size * 3),
|
|
'uniform ' + name, env.commandStr)
|
|
infix = '3i'
|
|
break
|
|
case GL_INT_VEC3:
|
|
check.command(
|
|
isArrayLike(value) && (value.length && value.length % 3 === 0 && value.length <= size * 3),
|
|
'uniform ' + name, env.commandStr)
|
|
infix = '3i'
|
|
break
|
|
case GL_BOOL_VEC4:
|
|
check.command(
|
|
isArrayLike(value) && (value.length && value.length % 4 === 0 && value.length <= size * 4),
|
|
'uniform ' + name, env.commandStr)
|
|
infix = '4i'
|
|
break
|
|
case GL_INT_VEC4:
|
|
check.command(
|
|
isArrayLike(value) && (value.length && value.length % 4 === 0 && value.length <= size * 4),
|
|
'uniform ' + name, env.commandStr)
|
|
infix = '4i'
|
|
break
|
|
}
|
|
if (size > 1) {
|
|
infix += 'v'
|
|
value = env.global.def('[' +
|
|
Array.prototype.slice.call(value) + ']')
|
|
} else {
|
|
value = isArrayLike(value) ? Array.prototype.slice.call(value) : value
|
|
}
|
|
scope(GL, '.uniform', infix, '(', LOCATION, ',',
|
|
value,
|
|
');')
|
|
}
|
|
continue
|
|
} else {
|
|
VALUE = arg.append(env, scope)
|
|
}
|
|
} else {
|
|
if (!filter(SCOPE_DECL)) {
|
|
continue
|
|
}
|
|
VALUE = scope.def(shared.uniforms, '[', stringStore.id(name), ']')
|
|
}
|
|
|
|
if (type === GL_SAMPLER_2D) {
|
|
check(!Array.isArray(VALUE), 'must specify a scalar prop for textures')
|
|
scope(
|
|
'if(', VALUE, '&&', VALUE, '._reglType==="framebuffer"){',
|
|
VALUE, '=', VALUE, '.color[0];',
|
|
'}')
|
|
} else if (type === GL_SAMPLER_CUBE) {
|
|
check(!Array.isArray(VALUE), 'must specify a scalar prop for cube maps')
|
|
scope(
|
|
'if(', VALUE, '&&', VALUE, '._reglType==="framebufferCube"){',
|
|
VALUE, '=', VALUE, '.color[0];',
|
|
'}')
|
|
}
|
|
|
|
// perform type validation
|
|
check.optional(function () {
|
|
function emitCheck (pred, message) {
|
|
env.assert(scope, pred,
|
|
'bad data or missing for uniform "' + name + '". ' + message)
|
|
}
|
|
|
|
function checkType (type, size) {
|
|
if (size === 1) {
|
|
check(!Array.isArray(VALUE), 'must not specify an array type for uniform')
|
|
}
|
|
emitCheck(
|
|
'Array.isArray(' + VALUE + ') && typeof ' + VALUE + '[0]===" ' + type + '"' +
|
|
' || typeof ' + VALUE + '==="' + type + '"',
|
|
'invalid type, expected ' + type)
|
|
}
|
|
|
|
function checkVector (n, type, size) {
|
|
if (Array.isArray(VALUE)) {
|
|
check(VALUE.length && VALUE.length % n === 0 && VALUE.length <= n * size, 'must have length of ' + (size === 1 ? '' : 'n * ') + n)
|
|
} else {
|
|
emitCheck(
|
|
shared.isArrayLike + '(' + VALUE + ')&&' + VALUE + '.length && ' + VALUE + '.length % ' + n + ' === 0' +
|
|
' && ' + VALUE + '.length<=' + n * size,
|
|
'invalid vector, should have length of ' + (size === 1 ? '' : 'n * ') + n, env.commandStr)
|
|
}
|
|
}
|
|
|
|
function checkTexture (target) {
|
|
check(!Array.isArray(VALUE), 'must not specify a value type')
|
|
emitCheck(
|
|
'typeof ' + VALUE + '==="function"&&' +
|
|
VALUE + '._reglType==="texture' +
|
|
(target === GL_TEXTURE_2D ? '2d' : 'Cube') + '"',
|
|
'invalid texture type', env.commandStr)
|
|
}
|
|
|
|
switch (type) {
|
|
case GL_INT:
|
|
checkType('number', size)
|
|
break
|
|
case GL_INT_VEC2:
|
|
checkVector(2, 'number', size)
|
|
break
|
|
case GL_INT_VEC3:
|
|
checkVector(3, 'number', size)
|
|
break
|
|
case GL_INT_VEC4:
|
|
checkVector(4, 'number', size)
|
|
break
|
|
case GL_FLOAT:
|
|
checkType('number', size)
|
|
break
|
|
case GL_FLOAT_VEC2:
|
|
checkVector(2, 'number', size)
|
|
break
|
|
case GL_FLOAT_VEC3:
|
|
checkVector(3, 'number', size)
|
|
break
|
|
case GL_FLOAT_VEC4:
|
|
checkVector(4, 'number', size)
|
|
break
|
|
case GL_BOOL:
|
|
checkType('boolean', size)
|
|
break
|
|
case GL_BOOL_VEC2:
|
|
checkVector(2, 'boolean', size)
|
|
break
|
|
case GL_BOOL_VEC3:
|
|
checkVector(3, 'boolean', size)
|
|
break
|
|
case GL_BOOL_VEC4:
|
|
checkVector(4, 'boolean', size)
|
|
break
|
|
case GL_FLOAT_MAT2:
|
|
checkVector(4, 'number', size)
|
|
break
|
|
case GL_FLOAT_MAT3:
|
|
checkVector(9, 'number', size)
|
|
break
|
|
case GL_FLOAT_MAT4:
|
|
checkVector(16, 'number', size)
|
|
break
|
|
case GL_SAMPLER_2D:
|
|
checkTexture(GL_TEXTURE_2D)
|
|
break
|
|
case GL_SAMPLER_CUBE:
|
|
checkTexture(GL_TEXTURE_CUBE_MAP)
|
|
break
|
|
}
|
|
})
|
|
|
|
var unroll = 1
|
|
switch (type) {
|
|
case GL_SAMPLER_2D:
|
|
case GL_SAMPLER_CUBE:
|
|
var TEX = scope.def(VALUE, '._texture')
|
|
scope(GL, '.uniform1i(', LOCATION, ',', TEX, '.bind());')
|
|
scope.exit(TEX, '.unbind();')
|
|
continue
|
|
|
|
case GL_INT:
|
|
case GL_BOOL:
|
|
infix = '1i'
|
|
break
|
|
|
|
case GL_INT_VEC2:
|
|
case GL_BOOL_VEC2:
|
|
infix = '2i'
|
|
unroll = 2
|
|
break
|
|
|
|
case GL_INT_VEC3:
|
|
case GL_BOOL_VEC3:
|
|
infix = '3i'
|
|
unroll = 3
|
|
break
|
|
|
|
case GL_INT_VEC4:
|
|
case GL_BOOL_VEC4:
|
|
infix = '4i'
|
|
unroll = 4
|
|
break
|
|
|
|
case GL_FLOAT:
|
|
infix = '1f'
|
|
break
|
|
|
|
case GL_FLOAT_VEC2:
|
|
infix = '2f'
|
|
unroll = 2
|
|
break
|
|
|
|
case GL_FLOAT_VEC3:
|
|
infix = '3f'
|
|
unroll = 3
|
|
break
|
|
|
|
case GL_FLOAT_VEC4:
|
|
infix = '4f'
|
|
unroll = 4
|
|
break
|
|
|
|
case GL_FLOAT_MAT2:
|
|
infix = 'Matrix2fv'
|
|
break
|
|
|
|
case GL_FLOAT_MAT3:
|
|
infix = 'Matrix3fv'
|
|
break
|
|
|
|
case GL_FLOAT_MAT4:
|
|
infix = 'Matrix4fv'
|
|
break
|
|
}
|
|
|
|
if (infix.indexOf('Matrix') === -1 && size > 1) {
|
|
infix += 'v'
|
|
unroll = 1
|
|
}
|
|
|
|
if (infix.charAt(0) === 'M') {
|
|
scope(GL, '.uniform', infix, '(', LOCATION, ',')
|
|
var matSize = Math.pow(type - GL_FLOAT_MAT2 + 2, 2)
|
|
var STORAGE = env.global.def('new Float32Array(', matSize, ')')
|
|
if (Array.isArray(VALUE)) {
|
|
scope(
|
|
'false,(',
|
|
loop(matSize, function (i) {
|
|
return STORAGE + '[' + i + ']=' + VALUE[i]
|
|
}), ',', STORAGE, ')')
|
|
} else {
|
|
scope(
|
|
'false,(Array.isArray(', VALUE, ')||', VALUE, ' instanceof Float32Array)?', VALUE, ':(',
|
|
loop(matSize, function (i) {
|
|
return STORAGE + '[' + i + ']=' + VALUE + '[' + i + ']'
|
|
}), ',', STORAGE, ')')
|
|
}
|
|
scope(');')
|
|
} else if (unroll > 1) {
|
|
var prev = []
|
|
var cur = []
|
|
for (var j = 0; j < unroll; ++j) {
|
|
if (Array.isArray(VALUE)) {
|
|
cur.push(VALUE[j])
|
|
} else {
|
|
cur.push(scope.def(VALUE + '[' + j + ']'))
|
|
}
|
|
if (isBatchInnerLoop) {
|
|
prev.push(scope.def())
|
|
}
|
|
}
|
|
if (isBatchInnerLoop) {
|
|
scope('if(!', env.batchId, '||', prev.map(function (p, i) {
|
|
return p + '!==' + cur[i]
|
|
}).join('||'), '){', prev.map(function (p, i) {
|
|
return p + '=' + cur[i] + ';'
|
|
}).join(''))
|
|
}
|
|
scope(GL, '.uniform', infix, '(', LOCATION, ',', cur.join(','), ');')
|
|
if (isBatchInnerLoop) {
|
|
scope('}')
|
|
}
|
|
} else {
|
|
check(!Array.isArray(VALUE), 'uniform value must not be an array')
|
|
if (isBatchInnerLoop) {
|
|
var prevS = scope.def()
|
|
scope('if(!', env.batchId, '||', prevS, '!==', VALUE, '){',
|
|
prevS, '=', VALUE, ';')
|
|
}
|
|
scope(GL, '.uniform', infix, '(', LOCATION, ',', VALUE, ');')
|
|
if (isBatchInnerLoop) {
|
|
scope('}')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function emitDraw (env, outer, inner, args) {
|
|
var shared = env.shared
|
|
var GL = shared.gl
|
|
var DRAW_STATE = shared.draw
|
|
|
|
var drawOptions = args.draw
|
|
|
|
function emitElements () {
|
|
var defn = drawOptions.elements
|
|
var ELEMENTS
|
|
var scope = outer
|
|
if (defn) {
|
|
if ((defn.contextDep && args.contextDynamic) || defn.propDep) {
|
|
scope = inner
|
|
}
|
|
ELEMENTS = defn.append(env, scope)
|
|
if (drawOptions.elementsActive) {
|
|
scope(
|
|
'if(' + ELEMENTS + ')' +
|
|
GL + '.bindBuffer(' + GL_ELEMENT_ARRAY_BUFFER + ',' + ELEMENTS + '.buffer.buffer);')
|
|
}
|
|
} else {
|
|
ELEMENTS = scope.def()
|
|
scope(
|
|
ELEMENTS, '=', DRAW_STATE, '.', S_ELEMENTS, ';',
|
|
'if(', ELEMENTS, '){',
|
|
GL, '.bindBuffer(', GL_ELEMENT_ARRAY_BUFFER, ',', ELEMENTS, '.buffer.buffer);}',
|
|
'else if(', shared.vao, '.currentVAO){',
|
|
ELEMENTS, '=', env.shared.elements + '.getElements(' + shared.vao, '.currentVAO.elements);',
|
|
(!extVertexArrays ? 'if(' + ELEMENTS + ')' + GL + '.bindBuffer(' + GL_ELEMENT_ARRAY_BUFFER + ',' + ELEMENTS + '.buffer.buffer);' : ''),
|
|
'}')
|
|
}
|
|
return ELEMENTS
|
|
}
|
|
|
|
function emitCount () {
|
|
var defn = drawOptions.count
|
|
var COUNT
|
|
var scope = outer
|
|
if (defn) {
|
|
if ((defn.contextDep && args.contextDynamic) || defn.propDep) {
|
|
scope = inner
|
|
}
|
|
COUNT = defn.append(env, scope)
|
|
check.optional(function () {
|
|
if (defn.MISSING) {
|
|
env.assert(outer, 'false', 'missing vertex count')
|
|
}
|
|
if (defn.DYNAMIC) {
|
|
env.assert(scope, COUNT + '>=0', 'missing vertex count')
|
|
}
|
|
})
|
|
} else {
|
|
COUNT = scope.def(DRAW_STATE, '.', S_COUNT)
|
|
check.optional(function () {
|
|
env.assert(scope, COUNT + '>=0', 'missing vertex count')
|
|
})
|
|
}
|
|
return COUNT
|
|
}
|
|
|
|
var ELEMENTS = emitElements()
|
|
function emitValue (name) {
|
|
var defn = drawOptions[name]
|
|
if (defn) {
|
|
if ((defn.contextDep && args.contextDynamic) || defn.propDep) {
|
|
return defn.append(env, inner)
|
|
} else {
|
|
return defn.append(env, outer)
|
|
}
|
|
} else {
|
|
return outer.def(DRAW_STATE, '.', name)
|
|
}
|
|
}
|
|
|
|
var PRIMITIVE = emitValue(S_PRIMITIVE)
|
|
var OFFSET = emitValue(S_OFFSET)
|
|
|
|
var COUNT = emitCount()
|
|
if (typeof COUNT === 'number') {
|
|
if (COUNT === 0) {
|
|
return
|
|
}
|
|
} else {
|
|
inner('if(', COUNT, '){')
|
|
inner.exit('}')
|
|
}
|
|
|
|
var INSTANCES, EXT_INSTANCING
|
|
if (extInstancing) {
|
|
INSTANCES = emitValue(S_INSTANCES)
|
|
EXT_INSTANCING = env.instancing
|
|
}
|
|
|
|
var ELEMENT_TYPE = ELEMENTS + '.type'
|
|
|
|
var elementsStatic = drawOptions.elements && isStatic(drawOptions.elements) && !drawOptions.vaoActive
|
|
|
|
function emitInstancing () {
|
|
function drawElements () {
|
|
inner(EXT_INSTANCING, '.drawElementsInstancedANGLE(', [
|
|
PRIMITIVE,
|
|
COUNT,
|
|
ELEMENT_TYPE,
|
|
OFFSET + '<<((' + ELEMENT_TYPE + '-' + GL_UNSIGNED_BYTE + ')>>1)',
|
|
INSTANCES
|
|
], ');')
|
|
}
|
|
|
|
function drawArrays () {
|
|
inner(EXT_INSTANCING, '.drawArraysInstancedANGLE(',
|
|
[PRIMITIVE, OFFSET, COUNT, INSTANCES], ');')
|
|
}
|
|
|
|
if (ELEMENTS && ELEMENTS !== 'null') {
|
|
if (!elementsStatic) {
|
|
inner('if(', ELEMENTS, '){')
|
|
drawElements()
|
|
inner('}else{')
|
|
drawArrays()
|
|
inner('}')
|
|
} else {
|
|
drawElements()
|
|
}
|
|
} else {
|
|
drawArrays()
|
|
}
|
|
}
|
|
|
|
function emitRegular () {
|
|
function drawElements () {
|
|
inner(GL + '.drawElements(' + [
|
|
PRIMITIVE,
|
|
COUNT,
|
|
ELEMENT_TYPE,
|
|
OFFSET + '<<((' + ELEMENT_TYPE + '-' + GL_UNSIGNED_BYTE + ')>>1)'
|
|
] + ');')
|
|
}
|
|
|
|
function drawArrays () {
|
|
inner(GL + '.drawArrays(' + [PRIMITIVE, OFFSET, COUNT] + ');')
|
|
}
|
|
|
|
if (ELEMENTS && ELEMENTS !== 'null') {
|
|
if (!elementsStatic) {
|
|
inner('if(', ELEMENTS, '){')
|
|
drawElements()
|
|
inner('}else{')
|
|
drawArrays()
|
|
inner('}')
|
|
} else {
|
|
drawElements()
|
|
}
|
|
} else {
|
|
drawArrays()
|
|
}
|
|
}
|
|
|
|
if (extInstancing && (typeof INSTANCES !== 'number' || INSTANCES >= 0)) {
|
|
if (typeof INSTANCES === 'string') {
|
|
inner('if(', INSTANCES, '>0){')
|
|
emitInstancing()
|
|
inner('}else if(', INSTANCES, '<0){')
|
|
emitRegular()
|
|
inner('}')
|
|
} else {
|
|
emitInstancing()
|
|
}
|
|
} else {
|
|
emitRegular()
|
|
}
|
|
}
|
|
|
|
function createBody (emitBody, parentEnv, args, program, count) {
|
|
var env = createREGLEnvironment()
|
|
var scope = env.proc('body', count)
|
|
check.optional(function () {
|
|
env.commandStr = parentEnv.commandStr
|
|
env.command = env.link(parentEnv.commandStr)
|
|
})
|
|
if (extInstancing) {
|
|
env.instancing = scope.def(
|
|
env.shared.extensions, '.angle_instanced_arrays')
|
|
}
|
|
emitBody(env, scope, args, program)
|
|
return env.compile().body
|
|
}
|
|
|
|
// ===================================================
|
|
// ===================================================
|
|
// DRAW PROC
|
|
// ===================================================
|
|
// ===================================================
|
|
function emitDrawBody (env, draw, args, program) {
|
|
injectExtensions(env, draw)
|
|
if (args.useVAO) {
|
|
if (args.drawVAO) {
|
|
draw(env.shared.vao, '.setVAO(', args.drawVAO.append(env, draw), ');')
|
|
} else {
|
|
draw(env.shared.vao, '.setVAO(', env.shared.vao, '.targetVAO);')
|
|
}
|
|
} else {
|
|
draw(env.shared.vao, '.setVAO(null);')
|
|
emitAttributes(env, draw, args, program.attributes, function () {
|
|
return true
|
|
})
|
|
}
|
|
emitUniforms(env, draw, args, program.uniforms, function () {
|
|
return true
|
|
}, false)
|
|
emitDraw(env, draw, draw, args)
|
|
}
|
|
|
|
function emitDrawProc (env, args) {
|
|
var draw = env.proc('draw', 1)
|
|
|
|
injectExtensions(env, draw)
|
|
|
|
emitContext(env, draw, args.context)
|
|
emitPollFramebuffer(env, draw, args.framebuffer)
|
|
|
|
emitPollState(env, draw, args)
|
|
emitSetOptions(env, draw, args.state)
|
|
|
|
emitProfile(env, draw, args, false, true)
|
|
|
|
var program = args.shader.progVar.append(env, draw)
|
|
draw(env.shared.gl, '.useProgram(', program, '.program);')
|
|
|
|
if (args.shader.program) {
|
|
emitDrawBody(env, draw, args, args.shader.program)
|
|
} else {
|
|
draw(env.shared.vao, '.setVAO(null);')
|
|
var drawCache = env.global.def('{}')
|
|
var PROG_ID = draw.def(program, '.id')
|
|
var CACHED_PROC = draw.def(drawCache, '[', PROG_ID, ']')
|
|
draw(
|
|
env.cond(CACHED_PROC)
|
|
.then(CACHED_PROC, '.call(this,a0);')
|
|
.else(
|
|
CACHED_PROC, '=', drawCache, '[', PROG_ID, ']=',
|
|
env.link(function (program) {
|
|
return createBody(emitDrawBody, env, args, program, 1)
|
|
}), '(', program, ');',
|
|
CACHED_PROC, '.call(this,a0);'))
|
|
}
|
|
|
|
if (Object.keys(args.state).length > 0) {
|
|
draw(env.shared.current, '.dirty=true;')
|
|
}
|
|
if (env.shared.vao) {
|
|
draw(env.shared.vao, '.setVAO(null);')
|
|
}
|
|
}
|
|
|
|
// ===================================================
|
|
// ===================================================
|
|
// BATCH PROC
|
|
// ===================================================
|
|
// ===================================================
|
|
|
|
function emitBatchDynamicShaderBody (env, scope, args, program) {
|
|
env.batchId = 'a1'
|
|
|
|
injectExtensions(env, scope)
|
|
|
|
function all () {
|
|
return true
|
|
}
|
|
|
|
emitAttributes(env, scope, args, program.attributes, all)
|
|
emitUniforms(env, scope, args, program.uniforms, all, false)
|
|
emitDraw(env, scope, scope, args)
|
|
}
|
|
|
|
function emitBatchBody (env, scope, args, program) {
|
|
injectExtensions(env, scope)
|
|
|
|
var contextDynamic = args.contextDep
|
|
|
|
var BATCH_ID = scope.def()
|
|
var PROP_LIST = 'a0'
|
|
var NUM_PROPS = 'a1'
|
|
var PROPS = scope.def()
|
|
env.shared.props = PROPS
|
|
env.batchId = BATCH_ID
|
|
|
|
var outer = env.scope()
|
|
var inner = env.scope()
|
|
|
|
scope(
|
|
outer.entry,
|
|
'for(', BATCH_ID, '=0;', BATCH_ID, '<', NUM_PROPS, ';++', BATCH_ID, '){',
|
|
PROPS, '=', PROP_LIST, '[', BATCH_ID, '];',
|
|
inner,
|
|
'}',
|
|
outer.exit)
|
|
|
|
function isInnerDefn (defn) {
|
|
return ((defn.contextDep && contextDynamic) || defn.propDep)
|
|
}
|
|
|
|
function isOuterDefn (defn) {
|
|
return !isInnerDefn(defn)
|
|
}
|
|
|
|
if (args.needsContext) {
|
|
emitContext(env, inner, args.context)
|
|
}
|
|
if (args.needsFramebuffer) {
|
|
emitPollFramebuffer(env, inner, args.framebuffer)
|
|
}
|
|
emitSetOptions(env, inner, args.state, isInnerDefn)
|
|
|
|
if (args.profile && isInnerDefn(args.profile)) {
|
|
emitProfile(env, inner, args, false, true)
|
|
}
|
|
|
|
if (!program) {
|
|
var progCache = env.global.def('{}')
|
|
var PROGRAM = args.shader.progVar.append(env, inner)
|
|
var PROG_ID = inner.def(PROGRAM, '.id')
|
|
var CACHED_PROC = inner.def(progCache, '[', PROG_ID, ']')
|
|
inner(
|
|
env.shared.gl, '.useProgram(', PROGRAM, '.program);',
|
|
'if(!', CACHED_PROC, '){',
|
|
CACHED_PROC, '=', progCache, '[', PROG_ID, ']=',
|
|
env.link(function (program) {
|
|
return createBody(
|
|
emitBatchDynamicShaderBody, env, args, program, 2)
|
|
}), '(', PROGRAM, ');}',
|
|
CACHED_PROC, '.call(this,a0[', BATCH_ID, '],', BATCH_ID, ');')
|
|
} else {
|
|
if (args.useVAO) {
|
|
if (args.drawVAO) {
|
|
if (isInnerDefn(args.drawVAO)) {
|
|
// vao is a prop
|
|
inner(env.shared.vao, '.setVAO(', args.drawVAO.append(env, inner), ');')
|
|
} else {
|
|
// vao is invariant
|
|
outer(env.shared.vao, '.setVAO(', args.drawVAO.append(env, outer), ');')
|
|
}
|
|
} else {
|
|
// scoped vao binding
|
|
outer(env.shared.vao, '.setVAO(', env.shared.vao, '.targetVAO);')
|
|
}
|
|
} else {
|
|
outer(env.shared.vao, '.setVAO(null);')
|
|
emitAttributes(env, outer, args, program.attributes, isOuterDefn)
|
|
emitAttributes(env, inner, args, program.attributes, isInnerDefn)
|
|
}
|
|
emitUniforms(env, outer, args, program.uniforms, isOuterDefn, false)
|
|
emitUniforms(env, inner, args, program.uniforms, isInnerDefn, true)
|
|
emitDraw(env, outer, inner, args)
|
|
}
|
|
}
|
|
|
|
function emitBatchProc (env, args) {
|
|
var batch = env.proc('batch', 2)
|
|
env.batchId = '0'
|
|
|
|
injectExtensions(env, batch)
|
|
|
|
// Check if any context variables depend on props
|
|
var contextDynamic = false
|
|
var needsContext = true
|
|
Object.keys(args.context).forEach(function (name) {
|
|
contextDynamic = contextDynamic || args.context[name].propDep
|
|
})
|
|
if (!contextDynamic) {
|
|
emitContext(env, batch, args.context)
|
|
needsContext = false
|
|
}
|
|
|
|
// framebuffer state affects framebufferWidth/height context vars
|
|
var framebuffer = args.framebuffer
|
|
var needsFramebuffer = false
|
|
if (framebuffer) {
|
|
if (framebuffer.propDep) {
|
|
contextDynamic = needsFramebuffer = true
|
|
} else if (framebuffer.contextDep && contextDynamic) {
|
|
needsFramebuffer = true
|
|
}
|
|
if (!needsFramebuffer) {
|
|
emitPollFramebuffer(env, batch, framebuffer)
|
|
}
|
|
} else {
|
|
emitPollFramebuffer(env, batch, null)
|
|
}
|
|
|
|
// viewport is weird because it can affect context vars
|
|
if (args.state.viewport && args.state.viewport.propDep) {
|
|
contextDynamic = true
|
|
}
|
|
|
|
function isInnerDefn (defn) {
|
|
return (defn.contextDep && contextDynamic) || defn.propDep
|
|
}
|
|
|
|
// set webgl options
|
|
emitPollState(env, batch, args)
|
|
emitSetOptions(env, batch, args.state, function (defn) {
|
|
return !isInnerDefn(defn)
|
|
})
|
|
|
|
if (!args.profile || !isInnerDefn(args.profile)) {
|
|
emitProfile(env, batch, args, false, 'a1')
|
|
}
|
|
|
|
// Save these values to args so that the batch body routine can use them
|
|
args.contextDep = contextDynamic
|
|
args.needsContext = needsContext
|
|
args.needsFramebuffer = needsFramebuffer
|
|
|
|
// determine if shader is dynamic
|
|
var progDefn = args.shader.progVar
|
|
if ((progDefn.contextDep && contextDynamic) || progDefn.propDep) {
|
|
emitBatchBody(
|
|
env,
|
|
batch,
|
|
args,
|
|
null)
|
|
} else {
|
|
var PROGRAM = progDefn.append(env, batch)
|
|
batch(env.shared.gl, '.useProgram(', PROGRAM, '.program);')
|
|
if (args.shader.program) {
|
|
emitBatchBody(
|
|
env,
|
|
batch,
|
|
args,
|
|
args.shader.program)
|
|
} else {
|
|
batch(env.shared.vao, '.setVAO(null);')
|
|
var batchCache = env.global.def('{}')
|
|
var PROG_ID = batch.def(PROGRAM, '.id')
|
|
var CACHED_PROC = batch.def(batchCache, '[', PROG_ID, ']')
|
|
batch(
|
|
env.cond(CACHED_PROC)
|
|
.then(CACHED_PROC, '.call(this,a0,a1);')
|
|
.else(
|
|
CACHED_PROC, '=', batchCache, '[', PROG_ID, ']=',
|
|
env.link(function (program) {
|
|
return createBody(emitBatchBody, env, args, program, 2)
|
|
}), '(', PROGRAM, ');',
|
|
CACHED_PROC, '.call(this,a0,a1);'))
|
|
}
|
|
}
|
|
|
|
if (Object.keys(args.state).length > 0) {
|
|
batch(env.shared.current, '.dirty=true;')
|
|
}
|
|
|
|
if (env.shared.vao) {
|
|
batch(env.shared.vao, '.setVAO(null);')
|
|
}
|
|
}
|
|
|
|
// ===================================================
|
|
// ===================================================
|
|
// SCOPE COMMAND
|
|
// ===================================================
|
|
// ===================================================
|
|
function emitScopeProc (env, args) {
|
|
var scope = env.proc('scope', 3)
|
|
env.batchId = 'a2'
|
|
|
|
var shared = env.shared
|
|
var CURRENT_STATE = shared.current
|
|
|
|
emitContext(env, scope, args.context)
|
|
|
|
if (args.framebuffer) {
|
|
args.framebuffer.append(env, scope)
|
|
}
|
|
|
|
sortState(Object.keys(args.state)).forEach(function (name) {
|
|
var defn = args.state[name]
|
|
var value = defn.append(env, scope)
|
|
if (isArrayLike(value)) {
|
|
value.forEach(function (v, i) {
|
|
scope.set(env.next[name], '[' + i + ']', v)
|
|
})
|
|
} else {
|
|
scope.set(shared.next, '.' + name, value)
|
|
}
|
|
})
|
|
|
|
emitProfile(env, scope, args, true, true)
|
|
|
|
;[S_ELEMENTS, S_OFFSET, S_COUNT, S_INSTANCES, S_PRIMITIVE].forEach(
|
|
function (opt) {
|
|
var variable = args.draw[opt]
|
|
if (!variable) {
|
|
return
|
|
}
|
|
scope.set(shared.draw, '.' + opt, '' + variable.append(env, scope))
|
|
})
|
|
|
|
Object.keys(args.uniforms).forEach(function (opt) {
|
|
var value = args.uniforms[opt].append(env, scope)
|
|
if (Array.isArray(value)) {
|
|
value = '[' + value.join() + ']'
|
|
}
|
|
scope.set(
|
|
shared.uniforms,
|
|
'[' + stringStore.id(opt) + ']',
|
|
value)
|
|
})
|
|
|
|
Object.keys(args.attributes).forEach(function (name) {
|
|
var record = args.attributes[name].append(env, scope)
|
|
var scopeAttrib = env.scopeAttrib(name)
|
|
Object.keys(new AttributeRecord()).forEach(function (prop) {
|
|
scope.set(scopeAttrib, '.' + prop, record[prop])
|
|
})
|
|
})
|
|
|
|
if (args.scopeVAO) {
|
|
scope.set(shared.vao, '.targetVAO', args.scopeVAO.append(env, scope))
|
|
}
|
|
|
|
function saveShader (name) {
|
|
var shader = args.shader[name]
|
|
if (shader) {
|
|
scope.set(shared.shader, '.' + name, shader.append(env, scope))
|
|
}
|
|
}
|
|
saveShader(S_VERT)
|
|
saveShader(S_FRAG)
|
|
|
|
if (Object.keys(args.state).length > 0) {
|
|
scope(CURRENT_STATE, '.dirty=true;')
|
|
scope.exit(CURRENT_STATE, '.dirty=true;')
|
|
}
|
|
|
|
scope('a1(', env.shared.context, ',a0,', env.batchId, ');')
|
|
}
|
|
|
|
function isDynamicObject (object) {
|
|
if (typeof object !== 'object' || isArrayLike(object)) {
|
|
return
|
|
}
|
|
var props = Object.keys(object)
|
|
for (var i = 0; i < props.length; ++i) {
|
|
if (dynamic.isDynamic(object[props[i]])) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
function splatObject (env, options, name) {
|
|
var object = options.static[name]
|
|
if (!object || !isDynamicObject(object)) {
|
|
return
|
|
}
|
|
|
|
var globals = env.global
|
|
var keys = Object.keys(object)
|
|
var thisDep = false
|
|
var contextDep = false
|
|
var propDep = false
|
|
var objectRef = env.global.def('{}')
|
|
keys.forEach(function (key) {
|
|
var value = object[key]
|
|
if (dynamic.isDynamic(value)) {
|
|
if (typeof value === 'function') {
|
|
value = object[key] = dynamic.unbox(value)
|
|
}
|
|
var deps = createDynamicDecl(value, null)
|
|
thisDep = thisDep || deps.thisDep
|
|
propDep = propDep || deps.propDep
|
|
contextDep = contextDep || deps.contextDep
|
|
} else {
|
|
globals(objectRef, '.', key, '=')
|
|
switch (typeof value) {
|
|
case 'number':
|
|
globals(value)
|
|
break
|
|
case 'string':
|
|
globals('"', value, '"')
|
|
break
|
|
case 'object':
|
|
if (Array.isArray(value)) {
|
|
globals('[', value.join(), ']')
|
|
}
|
|
break
|
|
default:
|
|
globals(env.link(value))
|
|
break
|
|
}
|
|
globals(';')
|
|
}
|
|
})
|
|
|
|
function appendBlock (env, block) {
|
|
keys.forEach(function (key) {
|
|
var value = object[key]
|
|
if (!dynamic.isDynamic(value)) {
|
|
return
|
|
}
|
|
var ref = env.invoke(block, value)
|
|
block(objectRef, '.', key, '=', ref, ';')
|
|
})
|
|
}
|
|
|
|
options.dynamic[name] = new dynamic.DynamicVariable(DYN_THUNK, {
|
|
thisDep: thisDep,
|
|
contextDep: contextDep,
|
|
propDep: propDep,
|
|
ref: objectRef,
|
|
append: appendBlock
|
|
})
|
|
delete options.static[name]
|
|
}
|
|
|
|
// ===========================================================================
|
|
// ===========================================================================
|
|
// MAIN DRAW COMMAND
|
|
// ===========================================================================
|
|
// ===========================================================================
|
|
function compileCommand (options, attributes, uniforms, context, stats) {
|
|
var env = createREGLEnvironment()
|
|
|
|
// link stats, so that we can easily access it in the program.
|
|
env.stats = env.link(stats)
|
|
|
|
// splat options and attributes to allow for dynamic nested properties
|
|
Object.keys(attributes.static).forEach(function (key) {
|
|
splatObject(env, attributes, key)
|
|
})
|
|
NESTED_OPTIONS.forEach(function (name) {
|
|
splatObject(env, options, name)
|
|
})
|
|
|
|
var args = parseArguments(options, attributes, uniforms, context, env)
|
|
|
|
emitDrawProc(env, args)
|
|
emitScopeProc(env, args)
|
|
emitBatchProc(env, args)
|
|
|
|
return extend(env.compile(), {
|
|
destroy: function () {
|
|
args.shader.program.destroy()
|
|
}
|
|
})
|
|
}
|
|
|
|
// ===========================================================================
|
|
// ===========================================================================
|
|
// POLL / REFRESH
|
|
// ===========================================================================
|
|
// ===========================================================================
|
|
return {
|
|
next: nextState,
|
|
current: currentState,
|
|
procs: (function () {
|
|
var env = createREGLEnvironment()
|
|
var poll = env.proc('poll')
|
|
var refresh = env.proc('refresh')
|
|
var common = env.block()
|
|
poll(common)
|
|
refresh(common)
|
|
|
|
var shared = env.shared
|
|
var GL = shared.gl
|
|
var NEXT_STATE = shared.next
|
|
var CURRENT_STATE = shared.current
|
|
|
|
common(CURRENT_STATE, '.dirty=false;')
|
|
|
|
emitPollFramebuffer(env, poll)
|
|
emitPollFramebuffer(env, refresh, null, true)
|
|
|
|
// Refresh updates all attribute state changes
|
|
var INSTANCING
|
|
if (extInstancing) {
|
|
INSTANCING = env.link(extInstancing)
|
|
}
|
|
|
|
// update vertex array bindings
|
|
if (extensions.oes_vertex_array_object) {
|
|
refresh(env.link(extensions.oes_vertex_array_object), '.bindVertexArrayOES(null);')
|
|
}
|
|
for (var i = 0; i < limits.maxAttributes; ++i) {
|
|
var BINDING = refresh.def(shared.attributes, '[', i, ']')
|
|
var ifte = env.cond(BINDING, '.buffer')
|
|
ifte.then(
|
|
GL, '.enableVertexAttribArray(', i, ');',
|
|
GL, '.bindBuffer(',
|
|
GL_ARRAY_BUFFER, ',',
|
|
BINDING, '.buffer.buffer);',
|
|
GL, '.vertexAttribPointer(',
|
|
i, ',',
|
|
BINDING, '.size,',
|
|
BINDING, '.type,',
|
|
BINDING, '.normalized,',
|
|
BINDING, '.stride,',
|
|
BINDING, '.offset);'
|
|
).else(
|
|
GL, '.disableVertexAttribArray(', i, ');',
|
|
GL, '.vertexAttrib4f(',
|
|
i, ',',
|
|
BINDING, '.x,',
|
|
BINDING, '.y,',
|
|
BINDING, '.z,',
|
|
BINDING, '.w);',
|
|
BINDING, '.buffer=null;')
|
|
refresh(ifte)
|
|
if (extInstancing) {
|
|
refresh(
|
|
INSTANCING, '.vertexAttribDivisorANGLE(',
|
|
i, ',',
|
|
BINDING, '.divisor);')
|
|
}
|
|
}
|
|
refresh(
|
|
env.shared.vao, '.currentVAO=null;',
|
|
env.shared.vao, '.setVAO(', env.shared.vao, '.targetVAO);')
|
|
|
|
Object.keys(GL_FLAGS).forEach(function (flag) {
|
|
var cap = GL_FLAGS[flag]
|
|
var NEXT = common.def(NEXT_STATE, '.', flag)
|
|
var block = env.block()
|
|
block('if(', NEXT, '){',
|
|
GL, '.enable(', cap, ')}else{',
|
|
GL, '.disable(', cap, ')}',
|
|
CURRENT_STATE, '.', flag, '=', NEXT, ';')
|
|
refresh(block)
|
|
poll(
|
|
'if(', NEXT, '!==', CURRENT_STATE, '.', flag, '){',
|
|
block,
|
|
'}')
|
|
})
|
|
|
|
Object.keys(GL_VARIABLES).forEach(function (name) {
|
|
var func = GL_VARIABLES[name]
|
|
var init = currentState[name]
|
|
var NEXT, CURRENT
|
|
var block = env.block()
|
|
block(GL, '.', func, '(')
|
|
if (isArrayLike(init)) {
|
|
var n = init.length
|
|
NEXT = env.global.def(NEXT_STATE, '.', name)
|
|
CURRENT = env.global.def(CURRENT_STATE, '.', name)
|
|
block(
|
|
loop(n, function (i) {
|
|
return NEXT + '[' + i + ']'
|
|
}), ');',
|
|
loop(n, function (i) {
|
|
return CURRENT + '[' + i + ']=' + NEXT + '[' + i + '];'
|
|
}).join(''))
|
|
poll(
|
|
'if(', loop(n, function (i) {
|
|
return NEXT + '[' + i + ']!==' + CURRENT + '[' + i + ']'
|
|
}).join('||'), '){',
|
|
block,
|
|
'}')
|
|
} else {
|
|
NEXT = common.def(NEXT_STATE, '.', name)
|
|
CURRENT = common.def(CURRENT_STATE, '.', name)
|
|
block(
|
|
NEXT, ');',
|
|
CURRENT_STATE, '.', name, '=', NEXT, ';')
|
|
poll(
|
|
'if(', NEXT, '!==', CURRENT, '){',
|
|
block,
|
|
'}')
|
|
}
|
|
refresh(block)
|
|
})
|
|
|
|
return env.compile()
|
|
})(),
|
|
compile: compileCommand
|
|
}
|
|
}
|