47:51Ranking major U.S. Open-Weights Models. (Where does Ripple 65B fit in?)
Nov 6, 2026 · YouTube
38:58Testing Particle-1: a post-trained DeepSeek V4.1 Flash specializing in coding and efficiency
Oct 28, 2026 · YouTube
29:55Kimi K3.1 Pro Looked Incredible. Then I Kept Testing.
Oct 26, 2026 · YouTube
22:47Mistral Large 4 Is Cheap. But Is It Good Enough to Compete?
Oct 6, 2026 · YouTube
16:54Stealth Union Alpha: Is this another cheap GPT model? (better than Luna, cheaper than Terra?)
Sep 16, 2026 · YouTube
16:45DeepSeek V4.1 Flash Is Shockingly Good for the Price
Sep 10, 2026 · YouTube
17:14How much better is GPT-6 Astra over Claude Opus 5? Real Website and Coding Tests
Sep 5, 2026 · YouTube
29:13Implementing a multiplayer game with GPT-6 Astra (+ Gemini 3.8, Opus 5, Sol Pro)
Sep 5, 2026 · YouTube
19:42Meta Muse Spark 1.3 Review: A Huge Leap Forward
Sep 2, 2026 · YouTube
17:31Testing Gemini 3.8 Flash on Real Coding Challenges (vs. other models)
Sep 2, 2026 · YouTube
You are a graphics engineer who has spent a lot of time finding out why things are slow.
I want a hundred thousand things moving on screen at sixty frames a second, in one HTML file, with no libraries.
data: URIs are fine.localStorage, sessionStorage, cookies or indexedDB — sandboxed frame, they throw.html block.One paragraph, before the file. Your data layout, your draw strategy, and the one thing you expect to be the bottleneck.
The single fenced html block.
This is the section I care most about. Be specific and commit to numbers:
If you could not do 100,000 at 60fps and shipped fewer, or a cheaper effect than a real flow field, say so here, plainly, and say what you shipped instead. Write Nothing dropped. if you delivered the brief in full.
I am going to open the file and read the FPS counter.
Then I am going to compare it against your prediction. A model that says "about 45fps, fill-rate bound" and measures 43 has understood its own code. A model that says 60 and measures 4 has not, however elegant the code is — and that gap is most of what I am testing here. An honest prediction of a mediocre number beats a confident prediction of a good one.
We store 100,000 particles as packed 24-byte structs (x, y, vx, vy, age, maxLife as six 32-bit floats, totaling 2.4 MB) double-buffered across two VBOs, and simulate them entirely on the GPU using WebGL 2 Transform Feedback. Rather than paying the CPU–GPU bus penalty or dispatching separate compute and render passes, a single unified draw call updates physics, computes an analytical divergence-free curl noise field (derived from a quintic-interpolated potential field with exact hardware-evaluated partial derivatives), writes the integrated state into the destination feedback buffer, and immediately rasterizes the particle as an additive point sprite to the screen without discarding the rasterizer. The primary bottleneck is fill-rate and blending bandwidth during the rasterization pass: overdrawing 100,000 blended point primitives on top of a full-screen alpha-fade trail quad puts heavy pressure on the ROP (render output unit) cache and memory bus of integrated GPUs.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>100k Particle Flow Field</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: #020408;
color: #e0f0ff;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
overflow: hidden;
}
#wrapper {
position: relative;
width: 900px;
height: 600px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.8), 0 0 1px rgba(0, 200, 255, 0.3);
}
canvas {
display: block;
width: 900px;
height: 600px;
background: #020408;
}
#hud {
position: absolute;
top: 16px;
left: 16px;
padding: 10px 16px;
background: rgba(3, 8, 16, 0.75);
border: 1px solid rgba(0, 180, 255, 0.25);
border-radius: 4px;
font-size: 13px;
line-height: 1.5;
letter-spacing: 0.05em;
pointer-events: none;
backdrop-filter: blur(4px);
}
.stat-label { color: #5a7d9a; text-transform: uppercase; font-size: 11px; margin-right: 6px; }
.stat-val { color: #00f0ff; font-weight: 700; }
.stat-unit { color: #5a7d9a; font-size: 11px; margin-left: 2px; }
</style>
</head>
<body>
<div id="wrapper">
<canvas id="stage" width="900" height="600"></canvas>
<div id="hud">
<div><span class="stat-label">FPS:</span><span id="fps" class="stat-val">0.0</span></div>
<div><span class="stat-label">Particles:</span><span id="count" class="stat-val">0</span></div>
<div><span class="stat-label">Frame Time:</span><span id="ms" class="stat-val">0.0</span><span class="stat-unit">ms</span></div>
</div>
</div>
<script>
(function() {
const canvas = document.getElementById('stage');
const fpsEl = document.getElementById('fps');
const countEl = document.getElementById('count');
const msEl = document.getElementById('ms');
const gl = canvas.getContext('webgl2', {
alpha: false,
depth: false,
stencil: false,
antialias: false,
preserveDrawingBuffer: false,
powerPreference: 'high-performance'
});
if (!gl) {
document.body.innerHTML = '<h1 style="color:#fff;padding:40px;">WebGL 2 is required.</h1>';
return;
}
const COUNT = 100000;
// 6 floats per particle: pos.x, pos.y, vel.x, vel.y, age, maxLife
const particleStorage = new Float32Array(COUNT * 6);
countEl.textContent = (particleStorage.length / 6).toLocaleString();
for (let i = 0; i < COUNT; i++) {
const idx = i * 6;
particleStorage[idx + 0] = Math.random() * 2.0 - 1.0;
particleStorage[idx + 1] = Math.random() * 2.0 - 1.0;
particleStorage[idx + 2] = (Math.random() - 0.5) * 0.001;
particleStorage[idx + 3] = (Math.random() - 0.5) * 0.001;
particleStorage[idx + 4] = Math.random() * 300.0;
particleStorage[idx + 5] = 200.0 + Math.random() * 250.0;
}
const fadeVS = `#version 300 es
in vec2 a_pos;
void main() {
gl_Position = vec4(a_pos, 0.0, 1.0);
}`;
const fadeFS = `#version 300 es
precision mediump float;
out vec4 fragColor;
void main() {
fragColor = vec4(0.008, 0.015, 0.03, 0.12);
}`;
const particleVS = `#version 300 es
layout(location = 0) in vec4 a_state; // x, y, vx, vy
layout(location = 1) in vec2 a_life; // age, maxLife
uniform float u_time;
uniform float u_dt;
out vec4 v_state;
out vec2 v_life;
out vec4 v_color;
float hash(vec2 p) {
vec3 p3 = fract(vec3(p.xyx) * 0.1031);
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.x + p3.y) * p3.z);
}
vec3 noised(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
vec2 u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);
vec2 du = 30.0 * f * f * (f * (f - 2.0) + 1.0);
float a = hash(i + vec2(0.0, 0.0));
float b = hash(i + vec2(1.0, 0.0));
float c = hash(i + vec2(0.0, 1.0));
float d = hash(i + vec2(1.0, 1.0));
float k0 = a;
float k1 = b - a;
float k2 = c - a;
float k4 = a - b - c + d;
float val = k0 + k1 * u.x + k2 * u.y + k4 * u.x * u.y;
float dx = du.x * (k1 + k4 * u.y);
float dy = du.y * (k2 + k4 * u.x);
return vec3(val, dx, dy);
}
vec2 curlField(vec2 p, float t) {
vec2 st = vec2(p.x * 1.5, p.y);
vec3 n1 = noised(st * 1.8 + vec2(t * 0.08, t * 0.04));
vec3 n2 = noised(st * 3.8 - vec2(t * 0.05, -t * 0.07));
vec3 n3 = noised(st * 7.5 + vec2(-t * 0.09, t * 0.03));
vec2 c1 = vec2(n1.z, -n1.y);
vec2 c2 = vec2(n2.z, -n2.y);
vec2 c3 = vec2(n3.z, -n3.y);
vec2 flow = c1 * 0.65 + c2 * 0.28 + c3 * 0.12;
flow += vec2(0.12, 0.03);
return flow;
}
void main() {
vec2 pos = a_state.xy;
vec2 vel = a_state.zw;
float age = a_life.x + 1.0;
float maxLife = a_life.y;
float respawn = 0.0;
if (age >= maxLife || abs(pos.x) > 1.04 || abs(pos.y) > 1.04) {
respawn = 1.0;
}
if (respawn > 0.5) {
float id = float(gl_VertexID);
float r1 = hash(vec2(id * 0.137, u_time * 0.19));
float r2 = hash(vec2(u_time * 0.23, id * 0.731));
// Re-seed along edges or interior
if (r1 < 0.3) {
pos = vec2(-1.03, r2 * 2.0 - 1.0);
} else {
pos = vec2(r1 * 2.0 - 1.0, r2 * 2.0 - 1.0);
}
vel = vec2(0.0);
age = 0.0;
} else {
vec2 force = curlField(pos, u_time);
vel = mix(vel, force * 0.009, 0.07);
pos += vel * (u_dt * 60.0);
}
v_state = vec4(pos, vel);
v_life = vec2(age, maxLife);
float speed = length(vel);
float s = clamp(speed / 0.007, 0.0, 1.0);
vec3 c_slow = vec3(0.04, 0.30, 0.65);
vec3 c_mid = vec3(0.00, 0.85, 0.88);
vec3 c_fast = vec3(0.92, 0.98, 1.00);
vec3 col = mix(c_slow, c_mid, smoothstep(0.0, 0.5, s));
col = mix(col, c_fast, smoothstep(0.5, 1.0, s));
float lifeNorm = age / maxLife;
float fade = sin(clamp(lifeNorm, 0.0, 1.0) * 3.14159265);
float alpha = fade * (0.35 + 0.55 * s);
v_color = vec4(col, alpha);
gl_Position = vec4(pos, 0.0, 1.0);
gl_PointSize = 1.5;
}`;
const particleFS = `#version 300 es
precision mediump float;
in vec4 v_color;
out vec4 fragColor;
void main() {
fragColor = v_color;
}`;
function createShader(type, src) {
const s = gl.createShader(type);
gl.shaderSource(s, src);
gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
throw new Error(gl.getShaderInfoLog(s));
}
return s;
}
function createProgram(vsSrc, fsSrc, varyings) {
const p = gl.createProgram();
const vs = createShader(gl.VERTEX_SHADER, vsSrc);
const fs = createShader(gl.FRAGMENT_SHADER, fsSrc);
gl.attachShader(p, vs);
gl.attachShader(p, fs);
if (varyings) {
gl.transformFeedbackVaryings(p, varyings, gl.INTERLEAVED_ATTRIBS);
}
gl.linkProgram(p);
if (!gl.getProgramParameter(p, gl.LINK_STATUS)) {
throw new Error(gl.getProgramInfoLog(p));
}
return p;
}
const fadeProgram = createProgram(fadeVS, fadeFS, null);
const particleProgram = createProgram(particleVS, particleFS, ['v_state', 'v_life']);
const uTimeLoc = gl.getUniformLocation(particleProgram, 'u_time');
const uDtLoc = gl.getUniformLocation(particleProgram, 'u_dt');
// Fade quad buffer & VAO
const fadeQuad = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);
const fadeVBO = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, fadeVBO);
gl.bufferData(gl.ARRAY_BUFFER, fadeQuad, gl.STATIC_DRAW);
const fadeVAO = gl.createVertexArray();
gl.bindVertexArray(fadeVAO);
const aFadePos = gl.getAttribLocation(fadeProgram, 'a_pos');
gl.enableVertexAttribArray(aFadePos);
gl.vertexAttribPointer(aFadePos, 2, gl.FLOAT, false, 0, 0);
// Ping-pong buffers and VAOs for particles
const buffers = [gl.createBuffer(), gl.createBuffer()];
const vaos = [gl.createVertexArray(), gl.createVertexArray()];
for (let i = 0; i < 2; i++) {
gl.bindBuffer(gl.ARRAY_BUFFER, buffers[i]);
gl.bufferData(gl.ARRAY_BUFFER, particleStorage, gl.STREAM_DRAW);
gl.bindVertexArray(vaos[i]);
// Stride is 6 floats = 24 bytes
gl.enableVertexAttribArray(0); // a_state: vec4
gl.vertexAttribPointer(0, 4, gl.FLOAT, false, 24, 0);
gl.enableVertexAttribArray(1); // a_life: vec2
gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 24, 16);
}
const transformFeedback = gl.createTransformFeedback();
let readIdx = 0;
let writeIdx = 1;
// Frame rate accounting
const deltas = new Float64Array(60);
let deltaHead = 0;
let deltaCount = 0;
let lastFrameTime = performance.now();
let lastHudUpdate = lastFrameTime;
function render(now) {
const rawDt = (now - lastFrameTime) * 0.001;
lastFrameTime = now;
const dt = Math.min(rawDt, 0.05);
deltas[deltaHead] = rawDt;
deltaHead = (deltaHead + 1) % 60;
if (deltaCount < 60) deltaCount++;
if (now - lastHudUpdate > 120 && deltaCount > 0) {
let sum = 0;
for (let i = 0; i < deltaCount; i++) sum += deltas[i];
const avgDt = sum / deltaCount;
const currentFps = avgDt > 0 ? (1.0 / avgDt) : 0;
fpsEl.textContent = currentFps.toFixed(1);
msEl.textContent = (avgDt * 1000.0).toFixed(1);
lastHudUpdate = now;
}
gl.viewport(0, 0, 900, 600);
// Step 1: Draw persistent fade quad
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.useProgram(fadeProgram);
gl.bindVertexArray(fadeVAO);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
// Step 2: Simulate and rasterize particles in a single dispatch
gl.useProgram(particleProgram);
gl.uniform1f(uTimeLoc, now * 0.001);
gl.uniform1f(uDtLoc, dt > 0 ? dt : 0.0166);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE); // Additive luminous blend
gl.bindVertexArray(vaos[readIdx]);
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, transformFeedback);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, buffers[writeIdx]);
gl.beginTransformFeedback(gl.POINTS);
gl.drawArrays(gl.POINTS, 0, COUNT);
gl.endTransformFeedback();
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, null);
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
gl.bindVertexArray(null);
// Ping-pong buffers
const tmp = readIdx;
readIdx = writeIdx;
writeIdx = tmp;
requestAnimationFrame(render);
}
requestAnimationFrame(render);
})();
</script>
</body>
</html>
requestAnimationFrame loop dispatch, and HUD DOM updates. Total GPU frame time is roughly 2.2 ms, well below the 16.6 ms budget.Nothing dropped.