Spaces:
Runtime error
Runtime error
working iframe preview
Browse files
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)
|