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)
|