Skip to content

Commit

Permalink
draft implementation of stencil operations
Browse files Browse the repository at this point in the history
  • Loading branch information
mikolalysenko committed Apr 8, 2016
1 parent c90be75 commit 452991c
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 23 deletions.
50 changes: 46 additions & 4 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -486,11 +486,53 @@ var command = regl({

| Property | Description | Default |
|----------|-------------|---------|
| `enable` | Sets `gl.enable(gl.STENCIL_TEST)` | `false` |
| `mask` | Sets `gl.stencilMask` | `0xffffffff` |
| `func` | Sets `gl.stencilFunc` | `` |
| `op` | Sets `gl.stencilOpSeparate` | `` |
| `enable` | Toggles `gl.enable(gl.STENCIL_TEST)` | `false` |
| `mask` | Sets `gl.stencilMask` | `-1` |
| `func` | Sets `gl.stencilFunc` | `{cmp:'always',ref:0,mask:-1}` |
| `opFront` | Sets `gl.stencilOpSeparate` for front face | `{fail:'keep',zfail:'keep',pass:'keep'}` |
| `opBack` | Sets `gl.stencilOpSeparate` for back face | `{fail:'keep',zfail:'keep',pass:'keep'}` |

**Notes**

* `func` is an object which configures the stencil test function. It has 3 properties,
+ `cmp` which is the comparison function
+ `ref` which is the reference value
+ `mask` which is the comparison mask
* `func.cmp` is a comparison operator which takes one of the following values,

| Value | Description |
|-------|-------------|
| `'never'` | `gl.NEVER` |
| `'always'` | gl.ALWAYS` |
| `'<', 'less'` | `gl.LESS` |
| `'<=', 'lequal'` | gl.LEQUAL |
| `'>', 'greater'` | `gl.GREATER` |
| `'>=', 'gequal'` | gl.GEQUAL |
| `'=', 'equal'` | gl.EQUAL |
| `'!=', 'notequal'` | gl.NOTEQUAL |

* `opFront` and `opBack` specify the stencil op. Each is an object which takes the following parameters:
+ `fail`, the stencil op which is applied when the stencil test fails
+ `zfail`, the stencil op which is applied when the stencil test passes and the depth test fails
+ `pass`, the stencil op which is applied when both stencil and depth tests pass
* Values for `opFront.fail`, `opFront.zfail`, etc. can come from the following table

| Stencil Op | Description |
|------------|-------------|
| `'zero'` | `gl.ZERO` |
| `'keep'` | `gl.KEEP` |
| `'replace'` | `gl.REPLACE` |
| `'invert'` | `gl.INVERT` |
| `'increment'` | `gl.INCR` |
| `'decrement'` | `gl.DECR` |
| `'increment wrap'` | `gl.INCR_WRAP` |
| `'decrement wrap'` | `gl.DECR_WRAP` |

**Related WebGL APIs**

* [`gl.stencilFunc`](https://www.khronos.org/opengles/sdk/docs/man/xhtml/glStencilFunc.xml)
* [`gl.stencilMask`](https://www.khronos.org/opengles/sdk/docs/man/xhtml/glStencilMask.xml)
* [`gl.stencilOpSeparate`](http://www.khronos.org/opengles/sdk/2.0/docs/man/xhtml/glStencilOpSeparate.xml)

#### Polygon offset

Expand Down
97 changes: 93 additions & 4 deletions lib/compile.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
var check = require('./check')
var createEnvironment = require('./codegen')

var primTypes = require('./constants/primitives.json')
var glTypes = require('./constants/dtypes.json')
var compareFuncs = require('./constants/comparefuncs.json')
var blendFuncs = require('./constants/blendFuncs.json')
var blendEquations = require('./constants/blendEquations.json')
var stencilOps = require('./constants/stencil-ops.json')

var GL_ELEMENT_ARRAY_BUFFER = 34963

Expand Down Expand Up @@ -36,6 +38,9 @@ var GL_POLYGON_OFFSET_FILL = 0x8037
var GL_SAMPLE_ALPHA_TO_COVERAGE = 0x809E
var GL_SAMPLE_COVERAGE = 0x80A0

var GL_FRONT = 1028
var GL_BACK = 1029

function typeLength (x) {
switch (x) {
case GL_FLOAT_VEC2:
Expand Down Expand Up @@ -449,6 +454,28 @@ module.exports = function reglCompiler (
'["dstAlpha" in ', VALUE, '?', VALUE, '.dstAlpha:', VALUE, '.dst]);')
break

case 'stencil.mask':
batch(GL, '.stencilMask(', VALUE, ');')
break

case 'stencil.func':
var STENCIL_FUNCS = link(compareFuncs)
batch(GL, '.stencilFunc(',
STENCIL_FUNCS, '[', VALUE, '.cmp||"always"],',
VALUE, '.ref|0,',
'"mask" in ', VALUE, '?', VALUE, '.mask:-1);')
break

case 'stencil.opFront':
case 'stencil.opBack':
var STENCIL_OPS = link(stencilOps)
batch(GL, '.stencilOpSeparate(',
option === 'stencil.opFront' ? GL_FRONT : GL_BACK, ',',
STENCIL_OPS, '[', VALUE, '.fail||"keep"],',
STENCIL_OPS, '[', VALUE, '.zfail||"keep"],',
STENCIL_OPS, '[', VALUE, '.pass||"keep"]);')
break

case 'primitives':
case 'offset':
case 'count':
Expand Down Expand Up @@ -625,6 +652,7 @@ module.exports = function reglCompiler (
var ELEMENT_STATE = link(elementState.elements)
var PRIM_TYPES = link(primTypes)
var COMPARE_FUNCS = link(compareFuncs)
var STENCIL_OPS = link(stencilOps)

var CONTEXT_STATE = {}
function linkContext (x) {
Expand Down Expand Up @@ -730,7 +758,6 @@ module.exports = function reglCompiler (
case 'polygonOffset.enable':
case 'sample.alpha':
case 'sample.enable':
case 'stencil.mask':
case 'depth.mask':
check.type(value, 'boolean', param)
handleStaticOption(param, value)
Expand Down Expand Up @@ -809,6 +836,46 @@ module.exports = function reglCompiler (
exit(BLEND_COLOR_STACK, '.pop();')
break

case 'stencil.mask':
check.type(value, 'number', 'stencil mask must be an integer')
var STENCIL_MASK_STACK = linkContext(param)
entry(STENCIL_MASK_STACK, '.push(', value, ');')
exit(STENCIL_MASK_STACK, '.pop();')
break

case 'stencil.func':
check.type(value, 'object', 'stencil func must be an object')
var cmp = value.cmp || 'keep'
var ref = value.ref || 0
var mask = value.mask || -1
check.parameter(cmp, compareFuncs, 'invalid stencil func cmp')
check.type(ref, 'number', 'stencil func ref')
check.type(mask, 'number', 'stencil func mask')
var STENCIL_FUNC_STACK = linkContext(param)
entry(STENCIL_FUNC_STACK, '.push(',
compareFuncs[cmp], ',',
ref, ',',
mask, ');')
entry(STENCIL_FUNC_STACK, '.pop();')
break

case 'stencil.opFront':
case 'stencil.opBack':
check.type(value, 'object', param)
var fail = value.fail || 'keep'
var zfail = value.zfail || 'keep'
var pass = value.pass || 'keep'
check.parameter(fail, stencilOps, param)
check.parameter(zfail, stencilOps, param)
check.parameter(pass, stencilOps, param)
var STENCIL_OP_STACK = linkContext(param)
entry(STENCIL_OP_STACK, '.push(',
stencilOps[fail], ',',
stencilOps[zfail], ',',
stencilOps[pass], ');')
exit(STENCIL_OP_STACK, '.pop();')
break

// Line width
case 'lineWidth':
check(value >= 0 && typeof value === 'number', param)
Expand Down Expand Up @@ -976,7 +1043,6 @@ module.exports = function reglCompiler (
case 'polygonOffset.enable':
case 'sample.alpha':
case 'sample.coverage':
case 'stencil.mask':
case 'depth.mask':
var STATE_STACK = linkContext(param)
dynamicEntry(STATE_STACK, '.push(', variable, ');')
Expand Down Expand Up @@ -1046,6 +1112,31 @@ module.exports = function reglCompiler (
dynamicExit(BLEND_COLOR_STACK, '.pop();')
break

case 'stencil.mask':
var STENCIL_MASK_STACK = linkContext(param)
dynamicEntry(STENCIL_MASK_STACK, '.push(', variable, ');')
dynamicExit(STENCIL_MASK_STACK, '.pop();')
break

case 'stencil.func':
var STENCIL_FUNC_STACK = linkContext(param)
dynamicEntry(STENCIL_FUNC_STACK, '.push(',
COMPARE_FUNCS, '[', variable, '.cmp],',
variable, '.ref|0,',
'"mask" in ', variable, '?', variable, '.mask:-1);')
dynamicExit(STENCIL_FUNC_STACK, '.pop();')
break

case 'stencil.opFront':
case 'stencil.opBack':
var STENCIL_OP_STACK = linkContext(param)
dynamicEntry(STENCIL_OP_STACK, '.push(',
STENCIL_OPS, '[', variable, '.fail||"keep"],',
STENCIL_OPS, '[', variable, '.zfail||"keep"],',
STENCIL_OPS, '[', variable, '.pass||"keep"]);')
dynamicExit(STENCIL_OP_STACK, '.pop();')
break

case 'elements':
var hasPrimitive =
!('primitive' in dynamicOptions) &&
Expand Down Expand Up @@ -1163,15 +1254,13 @@ module.exports = function reglCompiler (
CUR_SHADER, ',',
batch.arg(), ',',
batch.arg(), ');')

// Set dirty on all dynamic flags
Object.keys(dynamicOptions).forEach(function (option) {
var STATE = CONTEXT_STATE[option]
if (STATE) {
batch(STATE, '.setDirty();')
}
})

batch('}', exit)

// -------------------------------
Expand Down
11 changes: 11 additions & 0 deletions lib/constants/stencil-ops.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"0": 0,
"zero": 0,
"keep": 7680,
"replace": 7681,
"increment": 7682,
"decrement": 7683,
"increment wrap": 34055,
"decrement wrap": 34056,
"invert": 5386
}
28 changes: 13 additions & 15 deletions lib/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,25 +105,23 @@ module.exports = function wrapContextState (gl, shaderState) {

// Stencil
'stencil.enable': capStack(GL_STENCIL_TEST),
'stencil.mask': createStack([-1], function (mask) {
gl.stencilMask(mask)
}),
'stencil.func': createStack([
GL_ALWAYS, 0, -1,
GL_ALWAYS, 0, -1
], function (frontFunc, frontRef, frontMask,
backFunc, backRef, backMask) {
gl.stencilFuncSeparate(GL_FRONT, frontFunc, frontRef, frontMask)
gl.stencilFuncSeparate(GL_BACK, backFunc, backRef, backMask)
], function (func, ref, mask) {
gl.stencilFunc(func, ref, mask)
}),
'stencil.opFront': createStack([
GL_KEEP, GL_KEEP, GL_KEEP
], function (fail, zfail, pass) {
gl.stencilOpSeparate(GL_FRONT, fail, zfail, pass)
}),
'stencil.op': createStack([
GL_KEEP, GL_KEEP, GL_KEEP,
'stencil.opBack': createStack([
GL_KEEP, GL_KEEP, GL_KEEP
], function (frontFail, frontDPFail, frontPass,
backFail, backDPFail, backPass) {
gl.stencilOpSeparate(GL_FRONT, frontFail, frontDPFail, frontPass)
gl.stencilOpSeparate(GL_BACK, backFail, backDPFail, backPass)
}),
'stencil.mask': createStack([-1, -1], function (front, back) {
gl.stencilMask(GL_FRONT, front)
gl.stencilMask(GL_BACK, back)
], function (fail, zfail, pass) {
gl.stencilOpSeparate(GL_BACK, fail, zfail, pass)
}),

// Scissor
Expand Down
12 changes: 12 additions & 0 deletions test/stencil.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,22 @@ var createContext = require('./util/create-context')
var createREGL = require('../../regl')
var tape = require('tape')

var stencilOps = require('../lib/constants/stencil-ops.json')

tape('stencil', function (t) {
var gl = createContext(16, 16)
var regl = createREGL(gl)

// Check stencil op codes
t.equals(stencilOps[0], gl.ZERO, 'zero')
t.equals(stencilOps.keep, gl.KEEP, 'keep')
t.equals(stencilOps.replace, gl.REPLACE, 'replace')
t.equals(stencilOps.increment, gl.INCR, 'increment')
t.equals(stencilOps.decrement, gl.DECR, 'decrement')
t.equals(stencilOps['increment wrap'], gl.INCR_WRAP, 'increment wrap')
t.equals(stencilOps['decrement wrap'], gl.DECR_WRAP, 'decrement wrap')
t.equals(stencilOps.invert, gl.INVERT, 'invert')

// TODO

// clearStencil
Expand Down

0 comments on commit 452991c

Please sign in to comment.