glslkit-webgl 0.1.0.pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ # 自動生成 (rake glslkit:embed)。手で編集しない。
4
+ # 再生成するには: bundle exec rake glslkit:embed[shaders_dir,out_dir]
5
+ #
6
+ # MANIFESTのgenerated_atが1970-01-01なのはバグではない。生成の
7
+ # 冪等性(同じ入力から同じ出力になること)のための固定値であり、
8
+ # 実行時刻を使うと同じ入力でも実行のたびにgit diffが出てしまう。
9
+ # 再現性はstages.*.digest(実データから計算される)が担保している。
10
+
11
+ module CubeShaders
12
+ VERTEX = <<'GLSLKIT_VERTEX_SRC'.freeze
13
+ #version 300 es
14
+ layout(location = 0) in vec3 a_position;
15
+ layout(location = 1) in vec2 a_uv;
16
+ uniform mat4 u_mvp;
17
+ out vec2 v_uv;
18
+ void main() {
19
+ v_uv = a_uv;
20
+ gl_Position = u_mvp * vec4(a_position, 1.0);
21
+ }
22
+ GLSLKIT_VERTEX_SRC
23
+
24
+ FRAGMENT = <<'GLSLKIT_FRAGMENT_SRC'.freeze
25
+ #version 300 es
26
+ precision mediump float;
27
+ in vec2 v_uv;
28
+ uniform sampler2D u_texture;
29
+ out vec4 frag_color;
30
+ void main() {
31
+ frag_color = texture(u_texture, v_uv);
32
+ }
33
+ GLSLKIT_FRAGMENT_SRC
34
+
35
+ MANIFEST = {"schema_version"=>1,
36
+ "generated_at"=>"1970-01-01T00:00:00Z",
37
+ "generator"=>"glslkit/0.1.0.pre",
38
+ "programs"=>
39
+ {"cube"=>
40
+ {"digest"=>
41
+ "a3743db5891346c02adf840ae7246c1b7e911c4511eab0290dcc81a7e80eb08a",
42
+ "stages"=>
43
+ {"vertex"=>
44
+ {"path"=>"cube.vert",
45
+ "digest"=>
46
+ "8f1de9ac637f6872e2563399c33af571bc74741e651726eb2ece6e69c03ce433",
47
+ "url"=>"cube.vert"},
48
+ "fragment"=>
49
+ {"path"=>"cube.frag",
50
+ "digest"=>
51
+ "92a077871371847d2a1cd568bc082d5bc2e840293e1c0cecb0ae39559e114dc1",
52
+ "url"=>"cube.frag"}},
53
+ "attributes"=>
54
+ [{"name"=>"a_position", "type"=>"vec3", "location"=>0, "array_size"=>1},
55
+ {"name"=>"a_uv", "type"=>"vec2", "location"=>1, "array_size"=>1}],
56
+ "uniforms"=>
57
+ [{"name"=>"u_mvp",
58
+ "type"=>"mat4",
59
+ "array_size"=>1,
60
+ "setter"=>"uniformMatrix4fv",
61
+ "matrix"=>true,
62
+ "sampler"=>false,
63
+ "components"=>16,
64
+ "element_count"=>16,
65
+ "stages"=>["vertex"]},
66
+ {"name"=>"u_texture",
67
+ "type"=>"sampler2D",
68
+ "array_size"=>1,
69
+ "setter"=>"uniform1iv",
70
+ "matrix"=>false,
71
+ "sampler"=>true,
72
+ "components"=>1,
73
+ "element_count"=>1,
74
+ "stages"=>["fragment"]}],
75
+ "uniform_blocks"=>[],
76
+ "outputs"=>[{"name"=>"frag_color", "type"=>"vec4", "location"=>nil}]}}}.freeze
77
+
78
+ SOURCE_MAPS = {
79
+ vertex: {"version"=>1, "files"=>["cube.vert"], "segments"=>[[2, 0, 2]]},
80
+ fragment: {"version"=>1, "files"=>["cube.frag"], "segments"=>[[2, 0, 2]]}
81
+ }.freeze
82
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ # 自動生成 (rake glslkit:embed)。手で編集しない。
4
+ # 再生成するには: bundle exec rake glslkit:embed[shaders_dir,out_dir]
5
+ #
6
+ # MANIFESTのgenerated_atが1970-01-01なのはバグではない。生成の
7
+ # 冪等性(同じ入力から同じ出力になること)のための固定値であり、
8
+ # 実行時刻を使うと同じ入力でも実行のたびにgit diffが出てしまう。
9
+ # 再現性はstages.*.digest(実データから計算される)が担保している。
10
+
11
+ module NeonShaders
12
+ VERTEX = <<'GLSLKIT_VERTEX_SRC'.freeze
13
+ #version 300 es
14
+ layout(location = 0) in vec2 a_position;
15
+ void main() {
16
+ gl_Position = vec4(a_position, 0.0, 1.0);
17
+ }
18
+ GLSLKIT_VERTEX_SRC
19
+
20
+ FRAGMENT = <<'GLSLKIT_FRAGMENT_SRC'.freeze
21
+ #version 300 es
22
+ precision highp float;
23
+
24
+ uniform float u_time;
25
+ uniform vec2 u_resolution;
26
+ out vec4 frag_color;
27
+
28
+ #define PI 3.14159265359
29
+
30
+
31
+ mat2 rotate2d(float angle) {
32
+ float c = cos(angle), s = sin(angle);
33
+ return mat2(c, -s, s, c);
34
+ }
35
+
36
+ float sdTorus(vec3 p, vec2 t) {
37
+ vec2 q = vec2(length(p.xz) - t.x, p.y);
38
+ return length(q) - t.y;
39
+ }
40
+
41
+ vec3 palette(float t) {
42
+ return 0.55 + 0.45 * cos(6.28318 * (vec3(0.02, 0.18, 0.38) * t + vec3(0.0, 0.18, 0.42)));
43
+ }
44
+
45
+ float scene(vec3 p) {
46
+ p.xz *= rotate2d(u_time * 0.32);
47
+ p.xy *= rotate2d(sin(u_time * 0.27) * 0.55);
48
+
49
+ float angle = atan(p.z, p.x);
50
+ float pulse = 0.10 * sin(angle * 7.0 + u_time * 2.4);
51
+ float portal = sdTorus(p, vec2(1.25, 0.17 + pulse));
52
+
53
+ vec3 orb = p;
54
+ orb.xz *= rotate2d(-u_time * 0.7);
55
+ orb -= vec3(1.25, sin(u_time * 1.8) * 0.22, 0.0);
56
+ float satellite = length(orb) - 0.11;
57
+ return min(portal, satellite);
58
+ }
59
+
60
+ vec3 normalAt(vec3 p) {
61
+ vec2 e = vec2(0.0015, 0.0);
62
+ float d = scene(p);
63
+ return normalize(vec3(
64
+ scene(p + e.xyy) - d,
65
+ scene(p + e.yxy) - d,
66
+ scene(p + e.yyx) - d
67
+ ));
68
+ }
69
+
70
+ float raymarch(vec3 origin, vec3 direction, out vec3 point) {
71
+ float distanceTravelled = 0.0;
72
+ for (int i = 0; i < 96; i++) {
73
+ point = origin + direction * distanceTravelled;
74
+ float distanceToScene = scene(point);
75
+ if (distanceToScene < 0.001 || distanceTravelled > 12.0) break;
76
+ distanceTravelled += distanceToScene * 0.72;
77
+ }
78
+ return distanceTravelled;
79
+ }
80
+
81
+ void main() {
82
+ vec2 uv = (gl_FragCoord.xy * 2.0 - u_resolution) / u_resolution.y;
83
+ float vignette = max(0.0, 1.0 - dot(uv, uv) * 0.28);
84
+
85
+ vec3 origin = vec3(0.0, 0.0, 4.2);
86
+ vec3 direction = normalize(vec3(uv, -1.8));
87
+ vec3 point;
88
+ float travelled = raymarch(origin, direction, point);
89
+
90
+ vec3 color = vec3(0.006, 0.008, 0.025);
91
+ float rays = pow(max(0.0, 1.0 - length(uv)), 5.0);
92
+ color += vec3(0.12, 0.03, 0.30) * rays;
93
+
94
+ if (travelled < 12.0) {
95
+ vec3 normal = normalAt(point);
96
+ vec3 light = normalize(vec3(0.7, 1.2, 2.0));
97
+ float diffuse = max(dot(normal, light), 0.0);
98
+ float rim = pow(1.0 - max(dot(normal, -direction), 0.0), 2.4);
99
+ float bands = 0.5 + 0.5 * sin(atan(point.z, point.x) * 9.0 - u_time * 3.0);
100
+ vec3 neon = palette(bands + u_time * 0.06);
101
+ color += neon * (0.18 + diffuse * 0.65 + rim * 2.4);
102
+ color += pow(max(dot(reflect(-light, normal), -direction), 0.0), 32.0);
103
+ }
104
+
105
+ float glowDistance = abs(length(uv) - 0.47 - sin(atan(uv.y, uv.x) * 7.0 + u_time) * 0.018);
106
+ color += palette(u_time * 0.07 + length(uv)) * 0.012 / max(glowDistance, 0.008);
107
+
108
+ color *= vignette;
109
+ color = pow(color, vec3(0.82));
110
+ frag_color = vec4(color, 1.0);
111
+ }
112
+ GLSLKIT_FRAGMENT_SRC
113
+
114
+ MANIFEST = {"schema_version"=>1,
115
+ "generated_at"=>"1970-01-01T00:00:00Z",
116
+ "generator"=>"glslkit/0.1.0.pre",
117
+ "programs"=>
118
+ {"neon"=>
119
+ {"digest"=>
120
+ "b7fb9408aaff4ba38dd6a9be7429fb99962e9878a085e5e034bf1d9f8b3ea5a7",
121
+ "stages"=>
122
+ {"vertex"=>
123
+ {"path"=>"neon.vert",
124
+ "digest"=>
125
+ "9cf045b60aee6e157a1b7b83b2ce299dd479f8d2d16d2418914db2fedeb37627",
126
+ "url"=>"neon.vert"},
127
+ "fragment"=>
128
+ {"path"=>"neon.frag",
129
+ "digest"=>
130
+ "71c73491886569fc5546df1510755b3023a784aec25b635f31cdcc76facece27",
131
+ "url"=>"neon.frag"}},
132
+ "attributes"=>
133
+ [{"name"=>"a_position", "type"=>"vec2", "location"=>0, "array_size"=>1}],
134
+ "uniforms"=>
135
+ [{"name"=>"u_time",
136
+ "type"=>"float",
137
+ "array_size"=>1,
138
+ "setter"=>"uniform1fv",
139
+ "matrix"=>false,
140
+ "sampler"=>false,
141
+ "components"=>1,
142
+ "element_count"=>1,
143
+ "stages"=>["fragment"]},
144
+ {"name"=>"u_resolution",
145
+ "type"=>"vec2",
146
+ "array_size"=>1,
147
+ "setter"=>"uniform2fv",
148
+ "matrix"=>false,
149
+ "sampler"=>false,
150
+ "components"=>2,
151
+ "element_count"=>2,
152
+ "stages"=>["fragment"]}],
153
+ "uniform_blocks"=>[],
154
+ "outputs"=>[{"name"=>"frag_color", "type"=>"vec4", "location"=>nil}]}}}.freeze
155
+
156
+ SOURCE_MAPS = {
157
+ vertex: {"version"=>1, "files"=>["neon.vert"], "segments"=>[[2, 0, 2]]},
158
+ fragment: {"version"=>1,
159
+ "files"=>["neon.frag", "common/sdf.glsl", "common/color.glsl"],
160
+ "segments"=>[[2, 0, 2], [10, 1, 2], [20, 2, 2], [24, 0, 12]]}
161
+ }.freeze
162
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ # 自動生成 (rake glslkit:embed)。手で編集しない。
4
+ # 再生成するには: bundle exec rake glslkit:embed[shaders_dir,out_dir]
5
+ #
6
+ # MANIFESTのgenerated_atが1970-01-01なのはバグではない。生成の
7
+ # 冪等性(同じ入力から同じ出力になること)のための固定値であり、
8
+ # 実行時刻を使うと同じ入力でも実行のたびにgit diffが出てしまう。
9
+ # 再現性はstages.*.digest(実データから計算される)が担保している。
10
+
11
+ module NeonBrokenShaders
12
+ VERTEX = <<'GLSLKIT_VERTEX_SRC'.freeze
13
+ #version 300 es
14
+ layout(location = 0) in vec2 a_position;
15
+ void main() {
16
+ gl_Position = vec4(a_position, 0.0, 1.0);
17
+ }
18
+ GLSLKIT_VERTEX_SRC
19
+
20
+ FRAGMENT = <<'GLSLKIT_FRAGMENT_SRC'.freeze
21
+ #version 300 es
22
+ precision highp float;
23
+
24
+ uniform float u_time;
25
+ uniform vec2 u_resolution;
26
+ out vec4 frag_color;
27
+
28
+ #define PI 3.14159265359
29
+
30
+
31
+ mat2 rotate2d(float angle) {
32
+ float c = cos(angle), s = sin(angle)
33
+ return mat2(c, -s, s, c);
34
+ }
35
+
36
+ float sdTorus(vec3 p, vec2 t) {
37
+ vec2 q = vec2(length(p.xz) - t.x, p.y);
38
+ return length(q) - t.y;
39
+ }
40
+
41
+ vec3 palette(float t) {
42
+ return 0.55 + 0.45 * cos(6.28318 * (vec3(0.02, 0.18, 0.38) * t + vec3(0.0, 0.18, 0.42)));
43
+ }
44
+
45
+ float scene(vec3 p) {
46
+ p.xz *= rotate2d(u_time * 0.32);
47
+ p.xy *= rotate2d(sin(u_time * 0.27) * 0.55);
48
+
49
+ float angle = atan(p.z, p.x);
50
+ float pulse = 0.10 * sin(angle * 7.0 + u_time * 2.4);
51
+ float portal = sdTorus(p, vec2(1.25, 0.17 + pulse));
52
+
53
+ vec3 orb = p;
54
+ orb.xz *= rotate2d(-u_time * 0.7);
55
+ orb -= vec3(1.25, sin(u_time * 1.8) * 0.22, 0.0);
56
+ float satellite = length(orb) - 0.11;
57
+ return min(portal, satellite);
58
+ }
59
+
60
+ vec3 normalAt(vec3 p) {
61
+ vec2 e = vec2(0.0015, 0.0);
62
+ float d = scene(p);
63
+ return normalize(vec3(
64
+ scene(p + e.xyy) - d,
65
+ scene(p + e.yxy) - d,
66
+ scene(p + e.yyx) - d
67
+ ));
68
+ }
69
+
70
+ float raymarch(vec3 origin, vec3 direction, out vec3 point) {
71
+ float distanceTravelled = 0.0;
72
+ for (int i = 0; i < 96; i++) {
73
+ point = origin + direction * distanceTravelled;
74
+ float distanceToScene = scene(point);
75
+ if (distanceToScene < 0.001 || distanceTravelled > 12.0) break;
76
+ distanceTravelled += distanceToScene * 0.72;
77
+ }
78
+ return distanceTravelled;
79
+ }
80
+
81
+ void main() {
82
+ vec2 uv = (gl_FragCoord.xy * 2.0 - u_resolution) / u_resolution.y;
83
+ float vignette = max(0.0, 1.0 - dot(uv, uv) * 0.28);
84
+
85
+ vec3 origin = vec3(0.0, 0.0, 4.2);
86
+ vec3 direction = normalize(vec3(uv, -1.8));
87
+ vec3 point;
88
+ float travelled = raymarch(origin, direction, point);
89
+
90
+ vec3 color = vec3(0.006, 0.008, 0.025);
91
+ float rays = pow(max(0.0, 1.0 - length(uv)), 5.0);
92
+ color += vec3(0.12, 0.03, 0.30) * rays;
93
+
94
+ if (travelled < 12.0) {
95
+ vec3 normal = normalAt(point);
96
+ vec3 light = normalize(vec3(0.7, 1.2, 2.0));
97
+ float diffuse = max(dot(normal, light), 0.0);
98
+ float rim = pow(1.0 - max(dot(normal, -direction), 0.0), 2.4);
99
+ float bands = 0.5 + 0.5 * sin(atan(point.z, point.x) * 9.0 - u_time * 3.0);
100
+ vec3 neon = palette(bands + u_time * 0.06);
101
+ color += neon * (0.18 + diffuse * 0.65 + rim * 2.4);
102
+ color += pow(max(dot(reflect(-light, normal), -direction), 0.0), 32.0);
103
+ }
104
+
105
+ float glowDistance = abs(length(uv) - 0.47 - sin(atan(uv.y, uv.x) * 7.0 + u_time) * 0.018);
106
+ color += palette(u_time * 0.07 + length(uv)) * 0.012 / max(glowDistance, 0.008);
107
+
108
+ color *= vignette;
109
+ color = pow(color, vec3(0.82));
110
+ frag_color = vec4(color, 1.0);
111
+ }
112
+ GLSLKIT_FRAGMENT_SRC
113
+
114
+ MANIFEST = {"schema_version"=>1,
115
+ "generated_at"=>"1970-01-01T00:00:00Z",
116
+ "generator"=>"glslkit/0.1.0.pre",
117
+ "programs"=>
118
+ {"neon-broken"=>
119
+ {"digest"=>
120
+ "623464715d1428d30dc76fde676b3d68e8362d199b2992afdbec9d755a1ac478",
121
+ "stages"=>
122
+ {"vertex"=>
123
+ {"path"=>"neon-broken.vert",
124
+ "digest"=>
125
+ "9cf045b60aee6e157a1b7b83b2ce299dd479f8d2d16d2418914db2fedeb37627",
126
+ "url"=>"neon-broken.vert"},
127
+ "fragment"=>
128
+ {"path"=>"neon-broken.frag",
129
+ "digest"=>
130
+ "ed19c9ae89d14241a3e5f0ec39dbed94a70b4666e5eeb468d9d618b388194a15",
131
+ "url"=>"neon-broken.frag"}},
132
+ "attributes"=>
133
+ [{"name"=>"a_position", "type"=>"vec2", "location"=>0, "array_size"=>1}],
134
+ "uniforms"=>
135
+ [{"name"=>"u_time",
136
+ "type"=>"float",
137
+ "array_size"=>1,
138
+ "setter"=>"uniform1fv",
139
+ "matrix"=>false,
140
+ "sampler"=>false,
141
+ "components"=>1,
142
+ "element_count"=>1,
143
+ "stages"=>["fragment"]},
144
+ {"name"=>"u_resolution",
145
+ "type"=>"vec2",
146
+ "array_size"=>1,
147
+ "setter"=>"uniform2fv",
148
+ "matrix"=>false,
149
+ "sampler"=>false,
150
+ "components"=>2,
151
+ "element_count"=>2,
152
+ "stages"=>["fragment"]}],
153
+ "uniform_blocks"=>[],
154
+ "outputs"=>[{"name"=>"frag_color", "type"=>"vec4", "location"=>nil}]}}}.freeze
155
+
156
+ SOURCE_MAPS = {
157
+ vertex: {"version"=>1, "files"=>["neon-broken.vert"], "segments"=>[[2, 0, 2]]},
158
+ fragment: {"version"=>1,
159
+ "files"=>["neon-broken.frag", "common/sdf.glsl", "common/color.glsl"],
160
+ "segments"=>[[2, 0, 2], [10, 1, 2], [20, 2, 2], [24, 0, 12]]}
161
+ }.freeze
162
+ end
data/sample/index.html ADDED
@@ -0,0 +1,15 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>glslkit-webgl textured cube</title>
6
+ <style>
7
+ body { margin: 0; min-height: 100vh; display: grid; place-items: center; background: #161923; }
8
+ canvas { width: min(80vw, 640px); height: min(80vw, 640px); border: 1px solid #3a4055; }
9
+ </style>
10
+ </head>
11
+ <body>
12
+ <canvas id="canvas" width="640" height="640"></canvas>
13
+ <script src="../shim/glslkit-webgl.js" data-entry="./app.rb"></script>
14
+ </body>
15
+ </html>
@@ -0,0 +1,82 @@
1
+ <!doctype html>
2
+ <html lang="ja">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>glslkit-webgl compile error demo</title>
7
+ <style>
8
+ * { box-sizing: border-box; }
9
+ body {
10
+ margin: 0; min-height: 100vh; display: flex; flex-direction: column; align-items: center;
11
+ gap: 20px; padding: 32px 16px;
12
+ background: radial-gradient(circle at 50% 15%, #1c1430, #05060c 70%);
13
+ font: 15px/1.6 system-ui, sans-serif; color: #ece9ff;
14
+ }
15
+ h1 {
16
+ font-size: 14px; letter-spacing: .1em; text-transform: uppercase; color: #b9a6ff;
17
+ margin: 0; text-align: center;
18
+ }
19
+ canvas {
20
+ width: min(70vw, 480px); height: min(70vw, 480px); border-radius: 14px;
21
+ box-shadow: 0 0 60px #6b2bff22, inset 0 0 0 1px #ffffff14; background: #0a0a12;
22
+ }
23
+ #status { font-size: 13px; color: #8f88b8; min-height: 1.2em; text-align: center; }
24
+ #panel {
25
+ width: min(92vw, 640px); border: 1px solid #3a2f66; border-radius: 12px;
26
+ background: #12101f; padding: 20px 24px; display: none;
27
+ }
28
+ #panel.visible { display: block; }
29
+ #panel h2 {
30
+ margin: 0 0 14px; font-size: 13px; color: #ff6b81; letter-spacing: .08em;
31
+ text-transform: uppercase;
32
+ }
33
+ .row { display: flex; gap: 12px; margin: 8px 0; font-size: 14px; align-items: baseline; }
34
+ .row .label {
35
+ width: 64px; flex: none; color: #8f88b8; font-size: 11px; letter-spacing: .06em;
36
+ text-transform: uppercase;
37
+ }
38
+ .row .value { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
39
+ #err-file { color: #ffd166; font-weight: 700; font-size: 16px; }
40
+ /* 抜粋が取得できたときだけ枠を出す(fetch失敗時に空の帯が残らないように)。 */
41
+ #excerpt.has-content {
42
+ margin-top: 14px; background: #0a0a12; border-radius: 8px; padding: 10px 0;
43
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px;
44
+ color: #a9a3d6; overflow-x: auto;
45
+ }
46
+ #excerpt .line { display: block; padding: 1px 14px; white-space: pre; }
47
+ #excerpt .line.error-line { background: #4a1030; color: #ffb3c6; }
48
+ details { margin-top: 14px; }
49
+ summary { cursor: pointer; color: #8f88b8; font-size: 12.5px; }
50
+ #err-raw-log {
51
+ white-space: pre-wrap; background: #0a0a12; border-radius: 8px; padding: 12px;
52
+ margin-top: 8px; font-size: 12px; color: #8f88b8; overflow-x: auto;
53
+ }
54
+ button {
55
+ background: #6b2bff; color: white; border: none; border-radius: 8px; padding: 10px 20px;
56
+ font: inherit; font-weight: 600; cursor: pointer; margin-top: 18px;
57
+ }
58
+ button:hover { background: #7c40ff; }
59
+ </style>
60
+ </head>
61
+ <body>
62
+ <h1>glslkit-webgl &middot; compile error &rarr; original file &amp; line</h1>
63
+ <canvas id="canvas" width="480" height="480"></canvas>
64
+ <p id="status">読み込み中&hellip;</p>
65
+
66
+ <div id="panel">
67
+ <h2>Shader compile error</h2>
68
+ <div class="row"><span class="label">stage</span><span class="value" id="err-stage"></span></div>
69
+ <div class="row"><span class="label">file</span><span class="value" id="err-file"></span></div>
70
+ <div class="row"><span class="label">line</span><span class="value" id="err-line"></span></div>
71
+ <div class="row"><span class="label">message</span><span class="value" id="err-message"></span></div>
72
+ <div id="excerpt"></div>
73
+ <details>
74
+ <summary>raw compiler log</summary>
75
+ <pre id="err-raw-log"></pre>
76
+ </details>
77
+ <button id="retry">正常版に切り替えて描画する</button>
78
+ </div>
79
+
80
+ <script src="../shim/glslkit-webgl.js" data-entry="./neon-error.rb"></script>
81
+ </body>
82
+ </html>
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ # M10d: シェーダのコンパイルエラーが、includeされた元ファイルの名前と行番号を
4
+ # 指したままRubyの例外として上がってくることを見せるデモ。
5
+ #
6
+ # 壊れているのは common/sdf.glsl 側(webgl/sample/shaders-broken/)であり、
7
+ # neon.frag 直下ではない — エラーメッセージが「どのファイルの include 先が
8
+ # 壊れているか」まで教えてくれることが本gemの中核価値なので、あえて
9
+ # include の奥で壊している。
10
+ require_relative "../lib/glslkit/webgl"
11
+ require_relative "error_panel"
12
+ require_relative "generated-broken/neon-broken_shaders"
13
+ require_relative "generated/neon_shaders"
14
+
15
+ document = JS.global[:document]
16
+
17
+ set_text = ->(id, value) { document.call(:getElementById, id)[:textContent] = value.to_s }
18
+ set_status = ->(value) { set_text.call("status", value) }
19
+
20
+ build_program = ->(ctx, shaders, name) {
21
+ ctx.program(Glslkit::Manifest.parse(shaders::MANIFEST), name,
22
+ vertex: shaders::VERTEX, fragment: shaders::FRAGMENT,
23
+ source_maps: {
24
+ vertex: Glslkit::SourceMap.from_h(shaders::SOURCE_MAPS[:vertex]),
25
+ fragment: Glslkit::SourceMap.from_h(shaders::SOURCE_MAPS[:fragment])
26
+ })
27
+ }
28
+
29
+ fullscreen_triangle = {a_position: {data: [-1.0, -1.0, 3.0, -1.0, -1.0, 3.0], components: 2}}
30
+
31
+ start_render_loop = ->(ctx, program, geometry) {
32
+ ctx.viewport
33
+ program.set(:u_resolution, [ctx.width.to_f, ctx.height.to_f])
34
+ ctx.loop do |seconds|
35
+ program.set(:u_time, seconds)
36
+ ctx.draw(geometry)
37
+ end
38
+ }
39
+
40
+ # 壊れた行の前後を、includeされた元ファイル(common/sdf.glsl)自体から
41
+ # fetchして見せる(参考情報。任意)。file:// で開く等でfetchが失敗しても
42
+ # パネル本体(file/line/message/raw_log)の表示は壊さない。
43
+ #
44
+ # M11: 元は .await ベースだったが、spike/09-fetch.htmlの実機確認で
45
+ # 「JS::Object#await は RubyVM#evalAsync/RbValue#callAsync からしか呼べない」
46
+ # ことが判明した。この.rbは webgl/shim/glslkit-webgl.js
47
+ # (JS::RequireRemote.instance.load 経由)でロードされ、evalAsyncされているのは
48
+ # そのロード処理自体であって、ロードされたこのファイルのトップレベルは
49
+ # その対象に含まれない — そのため.awaitは常に失敗し、ここは(rescueで
50
+ # 握り潰されて)一度も動いていなかった可能性が高い。.then + Procに書き換える。
51
+ #
52
+ # M11f: 「参考情報なので握り潰す」自体は変えないが、Glslkit::WebGL.debugが
53
+ # trueのときだけconsoleに理由を出すようにする。M10dの見た目上の不具合が
54
+ # 実は.awaitの失敗だったのに気づけなかった反省(このファイル自身の履歴)。
55
+ debug_warn = ->(reason) { JS.global[:console].call(:warn, "[neon-error] source excerpt skipped: #{reason}") if Glslkit::WebGL.debug }
56
+
57
+ show_source_excerpt = ->(file, line) do
58
+ next unless line
59
+
60
+ on_response = proc do |response|
61
+ if response[:ok] == JS::True
62
+ response.call(:text)
63
+ else
64
+ debug_warn.call("fetch #{file} responded with status=#{response[:status]}")
65
+ nil
66
+ end
67
+ end
68
+ on_text = proc do |text|
69
+ next if text.nil?
70
+
71
+ lines = text.to_s.split("\n", -1)
72
+ from = [line - 3, 0].max
73
+ to = [line + 2, lines.length - 1].min
74
+
75
+ container = document.call(:getElementById, "excerpt")
76
+ (from..to).each do |index|
77
+ line_number = index + 1
78
+ span = document.call(:createElement, "span")
79
+ span[:className] = (line_number == line) ? "line error-line" : "line"
80
+ span[:textContent] = format("%3d| %s", line_number, lines[index])
81
+ container.call(:appendChild, span)
82
+ end
83
+ # fetchが成功して実際に行を追加できたときだけ枠を見せる。file://で開いた
84
+ # 場合などfetchが失敗すると、ここに到達せず空の帯が残ることはない。
85
+ container[:className] = "has-content"
86
+ end
87
+ on_rejected = proc { |error| debug_warn.call("fetch #{file} rejected: #{error}") }
88
+ JS.global.fetch(file).call(:then, on_response).call(:then, on_text).call(:catch, on_rejected)
89
+ rescue => e
90
+ debug_warn.call("synchronous error before fetch: #{e.class}: #{e.message}")
91
+ end
92
+
93
+ document.call(:getElementById, "retry").call(:addEventListener, "click") do
94
+ ctx = Glslkit::WebGL.context("#canvas")
95
+ program = build_program.call(ctx, NeonShaders, "neon")
96
+ geometry = ctx.geometry(program: program, attributes: fullscreen_triangle)
97
+
98
+ document.call(:getElementById, "panel")[:className] = ""
99
+ set_status.call("正常版で描画中です。")
100
+ start_render_loop.call(ctx, program, geometry)
101
+ end
102
+
103
+ begin
104
+ ctx = Glslkit::WebGL.context("#canvas")
105
+ build_program.call(ctx, NeonBrokenShaders, "neon-broken")
106
+ set_status.call("想定外: 壊れているはずのシェーダが正常にコンパイルされました。")
107
+ rescue Glslkit::WebGL::CompileError => error
108
+ described = NeonErrorPanel.describe(error)
109
+
110
+ set_text.call("err-stage", described[:stage])
111
+ set_text.call("err-file", described[:file])
112
+ set_text.call("err-line", described[:line])
113
+ set_text.call("err-message", described[:message])
114
+ set_text.call("err-raw-log", described[:raw_log])
115
+ document.call(:getElementById, "panel")[:className] = "visible"
116
+ set_status.call("意図的に壊したシェーダの読み込みに失敗しました。")
117
+
118
+ show_source_excerpt.call(error.file, error.line) if described[:resolved]
119
+ end
data/sample/neon.html ADDED
@@ -0,0 +1,22 @@
1
+ <!doctype html>
2
+ <html lang="ja">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>glslkit-webgl neon portal</title>
7
+ <style>
8
+ * { box-sizing: border-box; }
9
+ body { margin: 0; min-height: 100vh; display: grid; place-items: center; overflow: hidden;
10
+ background: radial-gradient(circle at 50% 35%, #17112d, #05060c 65%); }
11
+ canvas { width: min(92vw, 900px); height: min(92vh, 900px); border-radius: 18px;
12
+ box-shadow: 0 0 80px #753cff33, inset 0 0 0 1px #ffffff18; }
13
+ aside { position: fixed; left: 24px; bottom: 20px; color: #dcd7ff;
14
+ font: 500 13px/1.5 system-ui, sans-serif; letter-spacing: .08em; opacity: .7; }
15
+ </style>
16
+ </head>
17
+ <body>
18
+ <canvas id="canvas" width="900" height="900"></canvas>
19
+ <aside>GLSLKIT · RUBY.WASM · WEBGL2</aside>
20
+ <script src="../shim/glslkit-webgl.js" data-entry="./neon.rb"></script>
21
+ </body>
22
+ </html>
data/sample/neon.rb ADDED
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../lib/glslkit/webgl"
4
+ require_relative "generated/neon_shaders"
5
+
6
+ ctx = Glslkit::WebGL.context("#canvas")
7
+ program = ctx.program(Glslkit::Manifest.parse(NeonShaders::MANIFEST), "neon",
8
+ vertex: NeonShaders::VERTEX, fragment: NeonShaders::FRAGMENT,
9
+ source_maps: {
10
+ vertex: Glslkit::SourceMap.from_h(NeonShaders::SOURCE_MAPS[:vertex]),
11
+ fragment: Glslkit::SourceMap.from_h(NeonShaders::SOURCE_MAPS[:fragment])
12
+ })
13
+ screen = ctx.geometry(program: program, attributes: {
14
+ a_position: {data: [-1.0, -1.0, 3.0, -1.0, -1.0, 3.0], components: 2}
15
+ })
16
+
17
+ ctx.viewport
18
+ program.set(:u_resolution, [ctx.width.to_f, ctx.height.to_f])
19
+ ctx.loop do |seconds|
20
+ program.set(:u_time, seconds)
21
+ ctx.draw(screen)
22
+ end
@@ -0,0 +1,5 @@
1
+ #pragma once
2
+
3
+ vec3 palette(float t) {
4
+ return 0.55 + 0.45 * cos(6.28318 * (vec3(0.02, 0.18, 0.38) * t + vec3(0.0, 0.18, 0.42)));
5
+ }