Skip to content

Commit d708fb0

Browse files
committed
first commit
0 parents  commit d708fb0

27 files changed

+762
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
answers

README.md

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# glslify-workshopper
2+
3+
**WORK IN PROGRESS**
4+
5+
## Creating a new exercise
6+
7+
Add an entry to `exercises.json`. Each key is the label used in the menu, and
8+
each value is the name of the directory in `exercises` to use.
9+
10+
Create a new directory in `exercises`. All that is expected is a `server.js`
11+
file, which should export a function which takes an array of file names (the
12+
ones a student modifies locally) and return a function with the signature
13+
`(req, res)`.
14+
15+
Everything else is optional and can be included/excluded to match the
16+
requirements of the exercise, though you'll probably want to include your own
17+
`index.html` file in there too. [lesson-1](exercises/lesson-1) should be a
18+
reasonable example of how to build up an exercise.
19+
20+
## DONE
21+
22+
* workshopper-style exercise menu.
23+
* bootstraps lesson files for students in the working directory.
24+
* diffs between actual/expected render loop.
25+
* live-reloads shaders on save.
26+
* lesson descriptions.
27+
* magically inline local files using envify.
28+
29+
## TODO
30+
31+
* trigger tests to check if passed.
32+
* back button to return to menu.
33+
* record student progress.
34+
* image/text preview on hover for lesson menu?
35+
* make the exit button work.
36+
* in-browser editor, e.g. like [glsl.heroku.com](http://glsl.heroku.com)?
37+
low priority perhaps...

assets/icon-close.svg

+3
Loading

assets/icon-diff.svg

+3
Loading

assets/icon-onion.svg

+16
Loading

assets/icon-slide.svg

+11
Loading

exercises.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"» LESSON 1": "lesson-1"
3+
}

exercises/common.js

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
var description = require('../lib/description')
2+
var diffui = require('../lib/diff-ui')
3+
var fonts = require('google-fonts')
4+
var marked = require('marked')
5+
var fs = require('fs')
6+
7+
module.exports = function(opts) {
8+
opts = opts || {}
9+
10+
fonts.add({
11+
'Source Code Pro': [200, 600]
12+
})
13+
14+
if (opts.compare) diffui(opts.compare)
15+
if (opts.description) document.body.appendChild(
16+
description(marked(opts.description))
17+
)
18+
}

exercises/lesson-1/README.md

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# demo lesson
2+
3+
This lesson is just an example.
4+
5+
The "expected" shader is the checkerboard of blue/red squares, and the "actual"
6+
shader is the grid with a slight gradient.
7+
8+
All the shaders have had [glslify-live](http://github.com/hughsk/glslify-live)
9+
applied, so you can modify their file contents and they'll update without
10+
needing to refresh the page. Unfortunately it's not resistant to syntax errors
11+
yet so right now will crash the workshop if a shader is ever invalid on save.
12+
13+
`exercises/lesson-1/files/triangle.frag` will be copied to
14+
`lesson-1/triangle.frag` for the student to modify themselves, and the rest
15+
will be kept hidden away. If time permits we might want to also copy over a
16+
dummy project which runs the shader standalone on `npm start`?
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
precision mediump float;
2+
3+
void main() {
4+
float bx = mod(gl_FragCoord.x / 20.0, 4.0);
5+
float by = mod(gl_FragCoord.y / 20.0, 4.0);
6+
float brightness = (
7+
bx > 1.0 && by > 1.0 ||
8+
bx < 1.0 && by < 1.0
9+
) ? 1.0 : 0.0;
10+
11+
gl_FragColor = vec4(vec3(brightness) / gl_FragCoord.y * 100.0, 1.0);
12+
}

exercises/lesson-1/index.html

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>glslify-workshopper: lesson 1</title>
6+
<link rel="stylesheet" href="/style.css">
7+
</head>
8+
<body>
9+
<div id="container"></div>
10+
<script charset="utf-8" src="/lesson-1/index.js"></script>
11+
</body>
12+
</html>

exercises/lesson-1/index.js

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
var triangle = require('a-big-triangle')
2+
var throttle = require('frame-debounce')
3+
var fit = require('canvas-fit')
4+
var getContext = require('gl-context')
5+
var compare = require('gl-compare')
6+
var createShader = require('glslify')
7+
var fs = require('fs')
8+
9+
var container = document.getElementById('container')
10+
var canvas = container.appendChild(document.createElement('canvas'))
11+
var readme = fs.readFileSync(__dirname + '/README.md', 'utf8')
12+
var gl = getContext(canvas, render)
13+
var comparison = compare(gl, actual, expected)
14+
15+
comparison.mode = 'slide'
16+
comparison.amount = 0.5
17+
18+
require('../common')({
19+
description: readme
20+
, compare: comparison
21+
, canvas: canvas
22+
})
23+
24+
window.addEventListener('resize', fit(canvas), false)
25+
26+
var actualShader = createShader({
27+
frag: process.env.file_triangle_frag
28+
, vert: './shaders/triangle.vert'
29+
})(gl)
30+
31+
var expectedShader = createShader({
32+
frag: './shaders/triangle.frag'
33+
, vert: './shaders/triangle.vert'
34+
})(gl)
35+
36+
function render() {
37+
comparison.run()
38+
comparison.render()
39+
}
40+
41+
function actual(fbo) {
42+
fbo.shape = [canvas.height, canvas.width]
43+
fbo.bind()
44+
actualShader.bind()
45+
triangle(gl)
46+
}
47+
48+
function expected(fbo) {
49+
fbo.shape = [canvas.height, canvas.width]
50+
fbo.bind()
51+
expectedShader.bind()
52+
triangle(gl)
53+
}

exercises/lesson-1/server.js

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
var path = require('path')
2+
3+
module.exports = function(sourceFiles) {
4+
var glslify = ['-t', require.resolve('glslify')]
5+
var live = ['-t', require.resolve('glslify-live')]
6+
var brfs = ['-t', require.resolve('brfs')]
7+
var envify = ['-t', '[', require.resolve('envify')]
8+
9+
sourceFiles.forEach(function(file) {
10+
var base = path.basename(file).replace(/\./g, '_')
11+
envify.push('--file_' + base)
12+
envify.push(file)
13+
})
14+
15+
envify.push(']')
16+
17+
return require('beefy')({
18+
cwd: __dirname
19+
, entries: ['index.js']
20+
, quiet: false
21+
, live: false
22+
, debug: false
23+
, bundlerFlags: []
24+
.concat(envify)
25+
.concat(live)
26+
.concat(glslify)
27+
.concat(brfs)
28+
})
29+
}
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
precision mediump float;
2+
3+
void main() {
4+
float bx = mod(gl_FragCoord.x / 20.0, 2.0);
5+
float by = mod(gl_FragCoord.y / 20.0, 2.0);
6+
float brightness = (
7+
bx > 1.0 && by > 1.0 ||
8+
bx < 1.0 && by < 1.0
9+
) ? 1.0 : 0.0;
10+
11+
gl_FragColor = vec4(vec3(brightness, 0.2, 0.5), 1.0);
12+
}
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
precision mediump float;
2+
3+
attribute vec2 position;
4+
5+
void main() {
6+
gl_Position = vec4(position.xy, 1.0, 1.0);
7+
}

index.js

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#!/usr/bin/env node
2+
3+
var answers = require('./lib/create-answers')
4+
var live = require('glslify-live/server')
5+
var exercises = require('./exercises')
6+
var styles = require('./style')
7+
var opener = require('opener')
8+
var beefy = require('beefy')
9+
var http = require('http')
10+
var path = require('path')
11+
var url = require('url')
12+
var fs = require('fs')
13+
14+
var livePort = Number(process.env.GLSLIFY_LIVE_PORT = 12491)
15+
var mainPort = 12492
16+
17+
module.exports = createServer
18+
19+
createServer(process.cwd())
20+
21+
function createServer(root) {
22+
console.error(fs.readFileSync(
23+
__dirname + '/intro.txt', 'utf8'
24+
))
25+
26+
live().listen(livePort, function(err) {
27+
if (err) throw err
28+
29+
answers(root, function(err) {
30+
if (err) throw err
31+
console.error('Done!')
32+
console.error('Booting up the workshop in your browser in just a second...')
33+
console.error('')
34+
setTimeout(loadedAnswers, 1000)
35+
})
36+
})
37+
38+
function loadedAnswers(err) {
39+
if (err) throw err
40+
var exNames = Object.keys(exercises)
41+
var exLinks = exNames.map(function(k) { return exercises[k] })
42+
var exFiles = exLinks.map(function(link) {
43+
var dir = path.resolve(root, link)
44+
45+
return fs.readdirSync(dir).map(function(name) {
46+
return path.resolve(dir, name)
47+
})
48+
})
49+
50+
var exRoutes = exLinks.map(function(link, i) {
51+
return require('./exercises/' + link + '/server.js')(exFiles[i])
52+
})
53+
54+
var menu = beefy({
55+
cwd: path.join(__dirname, 'menu')
56+
, entries: ['index.js']
57+
, quiet: false
58+
})
59+
60+
http.createServer(function(req, res) {
61+
var uri = url.parse(req.url).pathname
62+
63+
if (uri === '/style.css') {
64+
res.setHeader('content-type', 'text/css')
65+
res.end(styles())
66+
return
67+
}
68+
69+
for (var i = 0; i < exLinks.length; i++) {
70+
if (uri.indexOf(exLinks[i]) === 1) {
71+
req.url = req.url
72+
.replace(exLinks[i], '')
73+
.replace(/\/+/g, '/')
74+
75+
return exRoutes[i](req, res)
76+
}
77+
}
78+
79+
return menu(req, res)
80+
}).listen(mainPort, function(err) {
81+
if (err) throw err
82+
opener('http://localhost:'+mainPort)
83+
})
84+
}
85+
}

intro.txt

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
===============================
3+
= ~~~ glslify-workshopper ~~~ =
4+
===============================

0 commit comments

Comments
 (0)