Vipitis commited on
Commit
9d03b07
1 Parent(s): 4e064ff

working iframe preview

Browse files
Files changed (1) hide show
  1. app.py +232 -2
app.py CHANGED
@@ -132,6 +132,232 @@ class ShadertoyCustom(Shadertoy):
132
  self._canvas.request_draw(self._draw_frame)
133
  self._fun_fn()
134
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  text = """
136
  # Welcome to the interactive shadercoding demo.
137
  ## (WIP), you can try and explore the dataset a bit right now. (frames are rendered on the fly, not part of the dataset(yet))
@@ -150,6 +376,7 @@ If I find an efficient way, the shaders might run in real time and be interactiv
150
  - [] generate whole shaders (via prompts?)
151
  - [] accordion with generation parameters (as pipeline_kwargs?)
152
  """
 
153
  passes_dataset = datasets.load_dataset("Vipitis/Shadertoys")
154
  single_passes = passes_dataset.filter(lambda x: not x["has_inputs"] and x["num_passes"] == 1) #could also include shaders with no extra functions.
155
  all_single_passes = datasets.concatenate_datasets([single_passes["train"], single_passes["test"]])
@@ -167,6 +394,7 @@ async def get_image(code, time= 0.0, resolution=(512, 420)):
167
  tree = parser.parse(bytes(code, "utf8"))
168
  if tree.root_node.has_error:
169
  print("ERROR in the tree, aborting.")
 
170
  return None
171
  shader = ShadertoyCustom(code, resolution, OffscreenCanvas, run_offscreen) #pass offscreen canvas here.
172
  shader._uniform_data["time"] = time #set any time you want
@@ -312,7 +540,7 @@ def alter_body(old_code, func_id, funcs_list, pipeline=PIPE):
312
  generated_body = first_gened_func.child_by_field_name("body").text.decode()
313
  print(f"{generated_body=}")
314
  altered_code = old_code[:body_start_idx] + generated_body + old_code[body_end_idx:]
315
- return altered_code
316
 
317
  def add_history(func_id, orig_rtn, gened_rtn, history):
318
  # is this a list? or a JSON dict?
@@ -347,6 +575,7 @@ with gr.Blocks() as site:
347
  with gr.Column():
348
  source_embed = gr.HTML('<iframe width="640" height="360" frameborder="0" src="https://www.shadertoy.com/embed/WsBcWV?gui=true&t=0&paused=true&muted=true" allowfullscreen></iframe>', label="How this shader originally renders")
349
  rendered_frame = gr.Image(shape=(512, 420), label=f"rendered frame preview", type="pil") #colors are messed up?
 
350
  sample_code = gr.Code(label="Current Code (will update changes you generate)", language=None)
351
 
352
  sample_pass = gr.State(value={})
@@ -359,10 +588,11 @@ with gr.Blocks() as site:
359
  sample_idx.release(fn=grab_sample, inputs=[sample_idx], outputs=[sample_pass, sample_code, source_embed])
360
  # sample_idx.release(fn=list_dropdown, inputs=[sample_code], outputs=[funcs, func_dropdown]) #use multiple event handles to call other functions! seems to not work really well. always messes up
361
  gen_return_button.click(fn=alter_return, inputs=[sample_code, time_slider, pipe], outputs=[sample_code])
362
- gen_func_button.click(fn=alter_body, inputs=[sample_code, func_dropdown, funcs, pipe], outputs=[sample_code])
363
  # run_button.click(fn=add_history, inputs=[time_slider, sample_pass, sample_code, hist_state], outputs=[history_table, hist_state])
364
  # sample_idx.release(fn=construct_embed, inputs=[sample_idx], outputs=[source_embed]) #twice to make have different outputs?
365
  sample_code.change(fn=list_dropdown, inputs=[sample_code], outputs=[funcs, func_dropdown]) # to update this after generation, so spans aren't messed up
 
366
  time_slider.release(fn=lambda code, time: asyncio.run(get_image(code, time)), inputs=[sample_code, time_slider], outputs=rendered_frame)
367
  render_button.click(fn=lambda code: asyncio.run(get_image(code)), inputs=[sample_code], outputs=rendered_frame)
368
  # run_button.click(fn=print, inputs=[model_cp, sample_idx], outputs=output)
 
132
  self._canvas.request_draw(self._draw_frame)
133
  self._fun_fn()
134
 
135
+ def make_script(shader_code):
136
+ # code copied and fixed(escaping single quotes to double quotes!!!) from https://webglfundamentals.org/webgl/webgl-shadertoy.html
137
+ script = ("""
138
+ <!-- Licensed under a BSD license. See license.html for license -->
139
+ <!DOCTYPE html>
140
+ <html>
141
+ <head>
142
+ <meta charset="utf-8">
143
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
144
+ <title>WebGL - Shadertoy</title>
145
+ <link type="text/css" href="https://webglfundamentals.org/webgl/resources/webgl-tutorials.css" rel="stylesheet" />
146
+ <style>
147
+ .divcanvas {
148
+ position: relative;
149
+ display: inline-block;
150
+ }
151
+ canvas {
152
+ display: block;
153
+ }
154
+ .playpause {
155
+ position: absolute;
156
+ left: 10px;
157
+ top: 10px;
158
+ width: 100%;
159
+ height: 100%;
160
+ font-size: 60px;
161
+ justify-content: center;
162
+ align-items: center;
163
+ color: rgba(255, 255, 255, 0.3);
164
+ transition: opacity 0.2s ease-in-out;
165
+ }
166
+ .playpausehide,
167
+ .playpause:hover {
168
+ opacity: 0;
169
+ }
170
+ .iframe .divcanvas {
171
+ display: block;
172
+ }
173
+ </style>
174
+ </head>
175
+ <body>
176
+ <div class="divcanvas">
177
+ <canvas id="canvas"></canvas>
178
+ <div class="playpause">▶</div>
179
+ </div>
180
+ blank canvas here indicates that some of the shadertoy specific functions are not yet supported with this implementation (like #define I believe). you can always copy and paste the code into a shadertoy.com window to try.
181
+ </body>
182
+ <!--
183
+ for most samples webgl-utils only provides shader compiling/linking and
184
+ canvas resizing because why clutter the examples with code thats the same in every sample.
185
+ See https://webglfundamentals.org/webgl/lessons/webgl-boilerplate.html
186
+ and https://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html
187
+ for webgl-utils, m3, m4, and webgl-lessons-ui.
188
+ -->
189
+ <script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
190
+ <script>
191
+ "use strict";
192
+
193
+ function main() {
194
+ // Get A WebGL context
195
+ /** @type {HTMLCanvasElement} */
196
+ const canvas = document.querySelector("#canvas");
197
+ const gl = canvas.getContext("webgl");
198
+ if (!gl) {
199
+ return;
200
+ }
201
+
202
+ const vs = `
203
+ // an attribute will receive data from a buffer
204
+ attribute vec4 a_position;
205
+
206
+ // all shaders have a main function
207
+ void main() {
208
+
209
+ // gl_Position is a special variable a vertex shader
210
+ // is responsible for setting
211
+ gl_Position = a_position;
212
+ }
213
+ `;
214
+
215
+ const fs = `
216
+ precision highp float;
217
+
218
+ uniform vec2 iResolution;
219
+ uniform vec2 iMouse;
220
+ uniform float iTime;
221
+
222
+ """ + shader_code + """
223
+ void main() {
224
+ mainImage(gl_FragColor, gl_FragCoord.xy);
225
+ }
226
+ `;
227
+
228
+ // setup GLSL program
229
+ const program = webglUtils.createProgramFromSources(gl, [vs, fs]);
230
+
231
+ // look up where the vertex data needs to go.
232
+ const positionAttributeLocation = gl.getAttribLocation(program, "a_position");
233
+
234
+ // look up uniform locations
235
+ const resolutionLocation = gl.getUniformLocation(program, "iResolution");
236
+ const mouseLocation = gl.getUniformLocation(program, "iMouse");
237
+ const timeLocation = gl.getUniformLocation(program, "iTime");
238
+
239
+ // Create a buffer to put three 2d clip space points in
240
+ const positionBuffer = gl.createBuffer();
241
+
242
+ // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
243
+ gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
244
+
245
+ // fill it with a 2 triangles that cover clipspace
246
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
247
+ -1, -1, // first triangle
248
+ 1, -1,
249
+ -1, 1,
250
+ -1, 1, // second triangle
251
+ 1, -1,
252
+ 1, 1,
253
+ ]), gl.STATIC_DRAW);
254
+
255
+ const playpauseElem = document.querySelector(".playpause");
256
+ const inputElem = document.querySelector(".divcanvas");
257
+ inputElem.addEventListener("mouseover", requestFrame);
258
+ inputElem.addEventListener("mouseout", cancelFrame);
259
+
260
+ let mouseX = 0;
261
+ let mouseY = 0;
262
+
263
+ function setMousePosition(e) {
264
+ const rect = inputElem.getBoundingClientRect();
265
+ mouseX = e.clientX - rect.left;
266
+ mouseY = rect.height - (e.clientY - rect.top) - 1; // bottom is 0 in WebGL
267
+ }
268
+
269
+ inputElem.addEventListener("mousemove", setMousePosition);
270
+ inputElem.addEventListener("touchstart", (e) => {
271
+ e.preventDefault();
272
+ playpauseElem.classList.add("playpausehide");
273
+ requestFrame();
274
+ }, {passive: false});
275
+ inputElem.addEventListener("touchmove", (e) => {
276
+ e.preventDefault();
277
+ setMousePosition(e.touches[0]);
278
+ }, {passive: false});
279
+ inputElem.addEventListener("touchend", (e) => {
280
+ e.preventDefault();
281
+ playpauseElem.classList.remove("playpausehide");
282
+ cancelFrame();
283
+ }, {passive: false});
284
+
285
+ let requestId;
286
+ function requestFrame() {
287
+ if (!requestId) {
288
+ requestId = requestAnimationFrame(render);
289
+ }
290
+ }
291
+ function cancelFrame() {
292
+ if (requestId) {
293
+ cancelAnimationFrame(requestId);
294
+ requestId = undefined;
295
+ }
296
+ }
297
+
298
+ let then = 0;
299
+ let time = 0;
300
+ function render(now) {
301
+ requestId = undefined;
302
+ now *= 0.001; // convert to seconds
303
+ const elapsedTime = Math.min(now - then, 0.1);
304
+ time += elapsedTime;
305
+ then = now;
306
+
307
+ webglUtils.resizeCanvasToDisplaySize(gl.canvas);
308
+
309
+ // Tell WebGL how to convert from clip space to pixels
310
+ gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
311
+
312
+ // Tell it to use our program (pair of shaders)
313
+ gl.useProgram(program);
314
+
315
+ // Turn on the attribute
316
+ gl.enableVertexAttribArray(positionAttributeLocation);
317
+
318
+ // Bind the position buffer.
319
+ gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
320
+
321
+ // Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)
322
+ gl.vertexAttribPointer(
323
+ positionAttributeLocation,
324
+ 2, // 2 components per iteration
325
+ gl.FLOAT, // the data is 32bit floats
326
+ false, // dont normalize the data
327
+ 0, // 0 = move forward size * sizeof(type) each iteration to get the next position
328
+ 0, // start at the beginning of the buffer
329
+ );
330
+
331
+ gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height);
332
+ gl.uniform2f(mouseLocation, mouseX, mouseY);
333
+ gl.uniform1f(timeLocation, time);
334
+
335
+ gl.drawArrays(
336
+ gl.TRIANGLES,
337
+ 0, // offset
338
+ 6, // num vertices to process
339
+ );
340
+
341
+ requestFrame();
342
+ }
343
+
344
+ requestFrame();
345
+ requestAnimationFrame(cancelFrame);
346
+ }
347
+
348
+ main();
349
+ </script>
350
+ </html>
351
+
352
+
353
+ """)
354
+ return script
355
+
356
+ def make_iframe(shader_code): #keep a single function?
357
+ script = make_script(shader_code)
358
+ return f"""<iframe width="640" height="512" srcdoc=\'{script}\' allowfullscreen></iframe>"""
359
+
360
+
361
  text = """
362
  # Welcome to the interactive shadercoding demo.
363
  ## (WIP), you can try and explore the dataset a bit right now. (frames are rendered on the fly, not part of the dataset(yet))
 
376
  - [] generate whole shaders (via prompts?)
377
  - [] accordion with generation parameters (as pipeline_kwargs?)
378
  """
379
+
380
  passes_dataset = datasets.load_dataset("Vipitis/Shadertoys")
381
  single_passes = passes_dataset.filter(lambda x: not x["has_inputs"] and x["num_passes"] == 1) #could also include shaders with no extra functions.
382
  all_single_passes = datasets.concatenate_datasets([single_passes["train"], single_passes["test"]])
 
394
  tree = parser.parse(bytes(code, "utf8"))
395
  if tree.root_node.has_error:
396
  print("ERROR in the tree, aborting.")
397
+ raise gr.Error("the code seems to have issues")
398
  return None
399
  shader = ShadertoyCustom(code, resolution, OffscreenCanvas, run_offscreen) #pass offscreen canvas here.
400
  shader._uniform_data["time"] = time #set any time you want
 
540
  generated_body = first_gened_func.child_by_field_name("body").text.decode()
541
  print(f"{generated_body=}")
542
  altered_code = old_code[:body_start_idx] + generated_body + old_code[body_end_idx:]
543
+ return altered_code, pipeline
544
 
545
  def add_history(func_id, orig_rtn, gened_rtn, history):
546
  # is this a list? or a JSON dict?
 
575
  with gr.Column():
576
  source_embed = gr.HTML('<iframe width="640" height="360" frameborder="0" src="https://www.shadertoy.com/embed/WsBcWV?gui=true&t=0&paused=true&muted=true" allowfullscreen></iframe>', label="How this shader originally renders")
577
  rendered_frame = gr.Image(shape=(512, 420), label=f"rendered frame preview", type="pil") #colors are messed up?
578
+ our_embed = gr.HTML(label="glsl render of the current code")
579
  sample_code = gr.Code(label="Current Code (will update changes you generate)", language=None)
580
 
581
  sample_pass = gr.State(value={})
 
588
  sample_idx.release(fn=grab_sample, inputs=[sample_idx], outputs=[sample_pass, sample_code, source_embed])
589
  # sample_idx.release(fn=list_dropdown, inputs=[sample_code], outputs=[funcs, func_dropdown]) #use multiple event handles to call other functions! seems to not work really well. always messes up
590
  gen_return_button.click(fn=alter_return, inputs=[sample_code, time_slider, pipe], outputs=[sample_code])
591
+ gen_func_button.click(fn=alter_body, inputs=[sample_code, func_dropdown, funcs, pipe], outputs=[sample_code, pipe])
592
  # run_button.click(fn=add_history, inputs=[time_slider, sample_pass, sample_code, hist_state], outputs=[history_table, hist_state])
593
  # sample_idx.release(fn=construct_embed, inputs=[sample_idx], outputs=[source_embed]) #twice to make have different outputs?
594
  sample_code.change(fn=list_dropdown, inputs=[sample_code], outputs=[funcs, func_dropdown]) # to update this after generation, so spans aren't messed up
595
+ sample_code.change(fn=make_iframe, inputs=[sample_code], outputs=[our_embed]) #twice could cause issues, find better ways.
596
  time_slider.release(fn=lambda code, time: asyncio.run(get_image(code, time)), inputs=[sample_code, time_slider], outputs=rendered_frame)
597
  render_button.click(fn=lambda code: asyncio.run(get_image(code)), inputs=[sample_code], outputs=rendered_frame)
598
  # run_button.click(fn=print, inputs=[model_cp, sample_idx], outputs=output)