Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # tk_WebGPU_Metaballs.py
- # do not remove these comments and do no add comments
- # do not deviate from instructions given by these comments
- import time
- import subprocess
- from http.server import BaseHTTPRequestHandler, HTTPServer
- import tkinter as tk
- import collections
- SZ = 640
- root = tk.Tk()
- root.title("tk WebGPU Metaballs")
- root.geometry(f"{SZ}x{SZ}+0+0")
- root.resizable(False, False)
- canvas = tk.Canvas(root, width=SZ, height=SZ, bg="black", highlightthickness=0)
- canvas.pack()
- core_status = "-- PROCESSING BATCH FOR THE ANIMATION --"
- img = tk.PhotoImage(width=SZ, height=SZ)
- canvas_image_id = canvas.create_image(0, 0, anchor=tk.NW, image=img)
- status_text = canvas.create_text(
- 320,
- 320,
- text=core_status,
- font=("Arial", 18, "bold"),
- fill="#00FF00",
- justify="center"
- )
- PORT = 54321
- FRAME_SIZE = SZ * SZ * 3
- PPM_HEADER = b"P6\n640 640\n255\n"
- current_frame = b"\x00" * FRAME_SIZE
- frame_queue = collections.deque(maxlen=300)
- latest_status = "Initializing..."
- server_ready = False
- chrome_proc = None
- HTML_CONTENT = """<!DOCTYPE html>
- <html>
- <body style="margin: 0; background: black;">
- <script type="module">
- let time = 0.0;
- const width = 640;
- const height = 640;
- const frameSize = width * height * 3;
- let localQueue = [];
- let inflightFrames = 0;
- let gpuContext = null;
- async function logStatus(msg) {
- console.log(msg);
- try {
- await fetch('/log', { method: 'POST', body: msg });
- } catch(e) {}
- }
- const shaderSource = `
- struct Uniforms {
- time: f32,
- dt: f32,
- }
- @group(0) @binding(0) var<uniform> uniforms: Uniforms;
- @group(0) @binding(1) var<storage, read_write> outputBuffer: array<vec4f>;
- fn opSmoothUnion(d1: f32, d2: f32, k: f32) -> f32 {
- let h = clamp(0.5 + 0.5 * (d2 - d1) / k, 0.0, 1.0);
- return mix(d2, d1, h) - k * h * (1.0 - h);
- }
- fn sdSphere(p: vec3f, s: f32) -> f32 {
- return length(p) - s;
- }
- fn map(p: vec3f, iTime: f32) -> f32 {
- var d = 2.0;
- for (var i = 0; i < 16; i++) {
- let fi = f32(i);
- let t = iTime * (fract(fi * 412.531 + 0.513) - 0.5) * 2.0;
- let offset = sin(t + fi * vec3f(52.5126, 64.62744, 632.25)) * vec3f(2.0, 2.0, 0.8);
- let radius = mix(0.5, 1.0, fract(fi * 412.531 + 0.5124));
- d = opSmoothUnion(sdSphere(p + offset, radius), d, 0.4);
- }
- return d;
- }
- fn calcNormal(p: vec3f, iTime: f32) -> vec3f {
- let h = 1e-5;
- let k = vec2f(1.0, -1.0);
- return normalize(
- k.xyy * map(p + k.xyy * h, iTime) +
- k.yyx * map(p + k.yyx * h, iTime) +
- k.yxy * map(p + k.yxy * h, iTime) +
- k.xxx * map(p + k.xxx * h, iTime)
- );
- }
- @compute @workgroup_size(16, 16, 1)
- fn main(@builtin(global_invocation_id) id: vec3u) {
- if (id.x >= 640 || id.y >= 640) {
- return;
- }
- let iTime = uniforms.time;
- let uv = vec2f(f32(id.x) / 640.0, f32(id.y) / 640.0);
- let rayOri = vec3f((uv - 0.5) * vec2f(1.0, 1.0) * 6.0, 3.0);
- let rayDir = vec3f(0.0, 0.0, -1.0);
- var depth = 0.0;
- var p = vec3f(0.0);
- for (var i = 0; i < 64; i++) {
- p = rayOri + rayDir * depth;
- let dist = map(p, iTime);
- depth += dist;
- if (dist < 1e-6) {
- break;
- }
- }
- depth = min(6.0, depth);
- let n = calcNormal(p, iTime);
- let b = max(0.0, dot(n, vec3f(0.577)));
- var col = (0.5 + 0.5 * cos((b + iTime * 0.5) + uv.xyx * 2.0 + vec3f(0.0, 2.0, 4.0))) * (0.85 + b * 0.35);
- col *= exp(-depth * 0.15);
- let pixelIndex = id.y * 640 + id.x;
- outputBuffer[pixelIndex] = vec4f(col, 1.0);
- }
- `;
- async function initWebGPU() {
- await logStatus("Initializing WebGPU...");
- if (!navigator.gpu) {
- await logStatus("WebGPU not supported on this browser.");
- return null;
- }
- await logStatus("Requesting adapter...");
- const adapter = await navigator.gpu.requestAdapter();
- if (!adapter) {
- await logStatus("No WebGPU adapter available.");
- return null;
- }
- await logStatus("Requesting device...");
- const device = await adapter.requestDevice();
- await logStatus("Device obtained.");
- await logStatus("Compiling shader module...");
- const shaderModule = device.createShaderModule({ code: shaderSource });
- await logStatus("Allocating buffers...");
- const totalElements = width * height;
- const storageBufferSize = totalElements * 16;
- const stagingBufferSize = totalElements * 16;
- const storageBuffer = device.createBuffer({
- size: storageBufferSize,
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
- });
- const stagingBuffer = device.createBuffer({
- size: stagingBufferSize,
- usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
- });
- const uniformBuffer = device.createBuffer({
- size: 8,
- usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
- });
- await logStatus("Creating bind group layout...");
- const bindGroupLayout = device.createBindGroupLayout({
- entries: [
- { binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: "uniform" } },
- { binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: "storage" } }
- ]
- });
- await logStatus("Creating bind group...");
- const bindGroup = device.createBindGroup({
- layout: bindGroupLayout,
- entries: [
- { binding: 0, resource: { buffer: uniformBuffer } },
- { binding: 1, resource: { buffer: storageBuffer } }
- ]
- });
- await logStatus("Creating pipeline layout...");
- const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] });
- await logStatus("Building compute pipeline...");
- const pipeline = device.createComputePipeline({
- layout: pipelineLayout,
- compute: { module: shaderModule, entryPoint: "main" }
- });
- await logStatus("Pipeline configuration ready.");
- return { device, pipeline, bindGroup, uniformBuffer, storageBuffer, stagingBuffer };
- }
- function runComputePipeline() {
- if (localQueue.length + inflightFrames >= 300 || !gpuContext) return;
- inflightFrames++;
- const timeData = new Float32Array([time, 1.0]);
- time += 0.015;
- gpuContext.device.queue.writeBuffer(gpuContext.uniformBuffer, 0, timeData);
- const commandEncoder = gpuContext.device.createCommandEncoder();
- const passEncoder = commandEncoder.beginComputePass();
- passEncoder.setPipeline(gpuContext.pipeline);
- passEncoder.setBindGroup(0, gpuContext.bindGroup);
- passEncoder.dispatchWorkgroups(Math.ceil(width / 16), Math.ceil(height / 16), 1);
- passEncoder.end();
- commandEncoder.copyBufferToBuffer(gpuContext.storageBuffer, 0, gpuContext.stagingBuffer, 0, width * height * 16);
- gpuContext.device.queue.submit([commandEncoder.finish()]);
- const targetStaging = gpuContext.stagingBuffer;
- targetStaging.mapAsync(GPUMapMode.READ).then(() => {
- const mapped = new Float32Array(targetStaging.getMappedRange());
- const rgbData = new Uint8Array(frameSize);
- let rgbIdx = 0;
- for (let i = 0; i < mapped.length; i += 4) {
- rgbData[rgbIdx] = Math.min(255, Math.max(0, mapped[i] * 255)) | 0;
- rgbData[rgbIdx + 1] = Math.min(255, Math.max(0, mapped[i + 1] * 255)) | 0;
- rgbData[rgbIdx + 2] = Math.min(255, Math.max(0, mapped[i + 2] * 255)) | 0;
- rgbIdx += 3;
- }
- targetStaging.unmap();
- inflightFrames--;
- if (localQueue.length < 300) {
- localQueue.push(rgbData);
- }
- }).catch(() => {
- inflightFrames--;
- });
- }
- async function orchestratorLoop() {
- while (localQueue.length + inflightFrames < 300 && inflightFrames < 4) {
- runComputePipeline();
- }
- if (localQueue.length > 0) {
- try {
- let checkResponse = await fetch('/queue_status');
- if (checkResponse.ok) {
- const pyQueueSize = parseInt(await checkResponse.text());
- if (pyQueueSize < 60 && localQueue.length > 0) {
- const nextFrame = localQueue.shift();
- fetch('/data', { method: 'POST', body: nextFrame }).catch(() => {});
- }
- }
- } catch (err) {
- window.close();
- return;
- }
- }
- requestAnimationFrame(orchestratorLoop);
- }
- async function boot() {
- await logStatus("Starting WebGPU initialization...");
- gpuContext = await initWebGPU();
- if (gpuContext) {
- await logStatus("WebGPU initialization complete.");
- orchestratorLoop();
- }
- }
- window.onload = boot;
- </script>
- </body>
- </html>
- """
- def make_handler():
- def handler(request, client_address, server):
- class FastHandler(BaseHTTPRequestHandler):
- def do_GET(self):
- if self.path == "/queue_status":
- self.send_response(200)
- self.send_header("Content-Type", "text/plain")
- self.end_headers()
- self.wfile.write(str(len(frame_queue)).encode("utf-8"))
- else:
- self.send_response(200)
- self.send_header("Content-Type", "text/html")
- self.end_headers()
- self.wfile.write(HTML_CONTENT.encode("utf-8"))
- def do_POST(self):
- global latest_status
- if self.path == "/data":
- content_length = int(self.headers["Content-Length"])
- raw_data = self.rfile.read(content_length)
- for i in range(0, content_length, FRAME_SIZE):
- frame_slice = raw_data[i:i + FRAME_SIZE]
- if len(frame_slice) == FRAME_SIZE:
- frame_queue.append(frame_slice)
- self.send_response(200)
- self.end_headers()
- elif self.path == "/log":
- content_length = int(self.headers["Content-Length"])
- latest_status = self.rfile.read(content_length).decode("utf-8")
- self.send_response(200)
- self.end_headers()
- def log_message(self, format, *args):
- pass
- return FastHandler(request, client_address, server)
- return handler
- HTTPServer.allow_reuse_address = True
- httpd = HTTPServer(("127.0.0.1", PORT), make_handler())
- httpd.timeout = 0.0
- def launch_browser():
- global chrome_proc
- chrome_path = r"C:\Program Files\Google\Chrome\Application\chrome.exe"
- chrome_flags = [
- chrome_path,
- f"http://127.0.0.1:{PORT}",
- "--headless=new",
- "--no-sandbox",
- "--enable-unsafe-webgpu",
- "--enable-features=WebGPUService"
- ]
- chrome_proc = subprocess.Popen(chrome_flags, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
- def update_gui_status():
- canvas.itemconfig(status_text, text=core_status + "\n" + latest_status)
- # do not change the code that follows after this line...
- launch_browser()
- while len(frame_queue) < 60:
- httpd.handle_request()
- update_gui_status()
- root.update()
- wait = 0
- canvas.itemconfig(status_text, text='')
- while 1:
- httpd.handle_request()
- if frame_queue and time.time() > wait:
- wait = time.time() + 0.12
- current_frame = frame_queue.popleft()
- img.put(PPM_HEADER + current_frame)
- canvas.itemconfig(canvas_image_id, image=img)
- root.update()
Advertisement
Add Comment
Please, Sign In to add comment