WebGPU renderer complete: dirty uploads, default limits, legacy removed, WebGPU required, warm-up (t88 phase 2d)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -42,8 +42,6 @@ def launch(*, gpu=None, browser_name='chromium', viewport=None, page_path=None,
|
||||
try:
|
||||
context = browser.new_context(viewport=viewport or {'width': 1280, 'height': 760})
|
||||
page = context.new_page()
|
||||
if gpu == 'soft':
|
||||
page.add_init_script('window.__allowSoftwareGL = true;')
|
||||
yield browser, page, base + '/' + quote(path.name)
|
||||
finally:
|
||||
browser.close()
|
||||
|
||||
+4
-10
@@ -41,13 +41,7 @@ async function perfRender(lighting, overlays) {
|
||||
draw += perfLast.drawWorld / n;
|
||||
for (const [key, value] of Object.entries(perfMarks)) marks[key] = (marks[key] || 0) + value / n;
|
||||
}
|
||||
let light = 0;
|
||||
if (!R.gpuOn) {
|
||||
for (let k = 0; k < 5; k++) computeLight();
|
||||
const l0 = performance.now();
|
||||
for (let k = 0; k < n; k++) computeLight();
|
||||
light = (performance.now() - l0) / n;
|
||||
}
|
||||
const light = 0;
|
||||
// Exercise the actual overlay calls and their arguments/conditions in frame().
|
||||
PERF_MARKS = false;
|
||||
for (let k = 0; k < 5; k++) frame(lastT + 1000 / 60);
|
||||
@@ -57,7 +51,7 @@ async function perfRender(lighting, overlays) {
|
||||
const frame_ms = (performance.now() - frameStart) / n;
|
||||
// One completed timestamp sample for this configuration, outside the CPU measurements.
|
||||
let gpu_ms = null;
|
||||
if (R.gpuOn && GPU.queries) {
|
||||
if (GPU.queries) {
|
||||
await GPU.timingPromise;
|
||||
PERF_MARKS = true; drawWorld();
|
||||
await GPU.timingPromise;
|
||||
@@ -66,7 +60,7 @@ async function perfRender(lighting, overlays) {
|
||||
return { frame_ms, drawWorld_ms: draw, computeLight_ms: light, marks_ms: marks,
|
||||
overlays: Object.fromEntries(Object.entries(data).map(([key, d]) => [key, { ms_per_frame: d.ms / n, calls: d.calls }])),
|
||||
overlay_ms: Object.values(data).reduce((sum, d) => sum + d.ms / n, 0),
|
||||
scale: R.gpuOn ? 1 : SM.scale, actual_look: SET.look, renderer: R.gpuOn ? 'webgpu' : 'legacy', override_cells: GPU.overrideCount || 0, gpu_ms };
|
||||
scale: 1, actual_look: SET.look, renderer: 'webgpu', override_cells: GPU.overrideCount || 0, gpu_ms };
|
||||
} finally { PERF_MARKS = false; }
|
||||
}
|
||||
function perfRun(seconds) {
|
||||
@@ -84,7 +78,7 @@ function perfRun(seconds) {
|
||||
requestAnimationFrame(frame);
|
||||
setTimeout(() => {
|
||||
window.__perfHalt(); PERF_FRAME = null; GAME.paused = true;
|
||||
resolve({ samples, end: perfState(), scale: R.gpuOn ? 1 : SM.scale, actual_look: SET.look, renderer: R.gpuOn ? 'webgpu' : 'legacy', override_cells: GPU.overrideCount || 0, gpu_ms: R.gpuOn ? GPU.gpuMs : null });
|
||||
resolve({ samples, end: perfState(), scale: 1, actual_look: SET.look, renderer: 'webgpu', override_cells: GPU.overrideCount || 0, gpu_ms: GPU.gpuMs });
|
||||
}, seconds * 1000);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -61,6 +61,6 @@ function perfView(look, zoom) {
|
||||
SET.look = look; SET.zoom = zoom; SET.lighting = 'full';
|
||||
resizeRender(); snapCamera();
|
||||
for (let k = 0; k < 5; k++) { R.frame++; drawWorld(); }
|
||||
if (look === 'smooth' && (!R.smoothOn || (!R.gpuOn && !SM.ok))) throw Error('Smooth look unavailable: ' + SM.why);
|
||||
return { grains: R.bw * R.bh, buffer: [R.bw, R.bh], scale: R.gpuOn ? 1 : SM.scale };
|
||||
if (look === 'smooth' && !R.smoothOn) throw Error('Smooth look unavailable');
|
||||
return { grains: R.bw * R.bh, buffer: [R.bw, R.bh], scale: 1 };
|
||||
}
|
||||
|
||||
+2
-4
@@ -162,10 +162,8 @@ def main():
|
||||
pg.add_script_tag(content=(ROOT / 'tools/perf-scenes.js').read_text())
|
||||
pg.add_script_tag(content=(ROOT / 'tools/perf-measure.js').read_text())
|
||||
result['machine'] = pg.evaluate('''() => {
|
||||
if (!smoothAvailable()) throw Error('Real GPU unavailable: ' + SM.why);
|
||||
const gl = SM.gl, dbg = gl.getExtension('WEBGL_debug_renderer_info');
|
||||
return { renderer: dbg ? gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : gl.getParameter(gl.RENDERER),
|
||||
vendor: dbg ? gl.getParameter(dbg.UNMASKED_VENDOR_WEBGL) : gl.getParameter(gl.VENDOR),
|
||||
return { renderer: 'WebGPU ' + CAPS.adapterInfo.vendor + ' ' + CAPS.adapterInfo.architecture,
|
||||
vendor: CAPS.adapterInfo.vendor,
|
||||
cores: navigator.hardwareConcurrency, crossOriginIsolated, webgpu: !!navigator.gpu,
|
||||
adapter: CAPS.adapterInfo, timestamp_query: !!GPU.queries,
|
||||
user_agent: navigator.userAgent, dpr: devicePixelRatio };
|
||||
|
||||
+3
-8
@@ -13,7 +13,6 @@ code and the bot is moved there before the shot. SX, SY are the bot's starting p
|
||||
|
||||
Options:
|
||||
--look pixel|smooth which look to draw (default: pixel; use smooth only to check graphics)
|
||||
--renderer webgpu|legacy pixel renderer (default: webgpu)
|
||||
--seed N world seed (default 424242)
|
||||
--size WxH world size in grains (default 2048x1024)
|
||||
--setup FILE.js code to run once the world is ready (see tools/scenes/ for examples)
|
||||
@@ -52,7 +51,6 @@ def main():
|
||||
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
ap.add_argument('out')
|
||||
ap.add_argument('--look', choices=['pixel', 'smooth'], default='pixel')
|
||||
ap.add_argument('--renderer', choices=['webgpu', 'legacy'], default='webgpu')
|
||||
ap.add_argument('--seed', type=int, default=424242)
|
||||
ap.add_argument('--size', default='2048x1024')
|
||||
ap.add_argument('--setup')
|
||||
@@ -78,7 +76,6 @@ def main():
|
||||
pg.goto(url)
|
||||
pg.evaluate("SET.fog = false")
|
||||
pg.evaluate('CAPS_CHECK')
|
||||
pg.evaluate('(r) => { SET.renderer = r; if (r === \'webgpu\' && !GPU.ok) throw Error(GPU.error || \'WebGPU unavailable\'); }', a.renderer)
|
||||
pg.wait_for_timeout(300)
|
||||
# pause the moment the world exists, so the shot depends only on the seed, the setup
|
||||
# code and --ticks (simTick is deterministic), not on how fast the browser ran
|
||||
@@ -93,7 +90,7 @@ def main():
|
||||
if code:
|
||||
pg.evaluate(f'() => {{ {code}\n}}')
|
||||
# Setup scenes can change surfY after the renderer cached the world's horizon.
|
||||
pg.evaluate('SM.surf = -1')
|
||||
pg.evaluate('SM.surf = -1; renderDirty.fill(1); GPU.world=null;')
|
||||
if a.ticks:
|
||||
pg.evaluate(f'for (let k = 0; k < {a.ticks}; k++) simTick();')
|
||||
pg.evaluate(f"""GAME.paused = true; $('hud').hidden = {'false' if a.hud else 'true'}; GAME.hidePreviews = {'false' if a.previews else 'true'};
|
||||
@@ -105,17 +102,15 @@ def main():
|
||||
pg.evaluate("() => { window.__frozenNow = 5000; window.requestAnimationFrame = () => 0; }")
|
||||
pg.wait_for_timeout(100)
|
||||
pg.evaluate("() => { R.frame = 999; if (typeof SM !== 'undefined') SM.t0 = 0; frame(lastRender + 1000); }")
|
||||
if a.renderer == 'webgpu' and not pg.evaluate('R.gpuOn'):
|
||||
raise RuntimeError('WebGPU fell back: ' + pg.evaluate('GPU.error'))
|
||||
kw = {}
|
||||
if a.clip:
|
||||
x, y, w, h = (float(v) for v in a.clip.split(','))
|
||||
kw['clip'] = {'x': x, 'y': y, 'width': w, 'height': h}
|
||||
pathlib.Path(a.out).parent.mkdir(parents=True, exist_ok=True)
|
||||
pg.screenshot(path=a.out, **kw)
|
||||
note = pg.evaluate("SET.look === 'smooth' ? (R.smoothOn ? 'smooth look' : 'smooth look FAILED: ' + SM.why) : 'pixel look'")
|
||||
note = pg.evaluate("SET.look === 'smooth' ? (R.smoothOn ? 'smooth look' : 'smooth look FAILED') : 'pixel look'")
|
||||
b.close()
|
||||
print(f"saved {a.out} ({note}, {a.renderer})")
|
||||
print(f'saved {a.out} ({note}, WebGPU)')
|
||||
for e in errors:
|
||||
print('ERROR', e)
|
||||
sys.exit(1 if errors else 0)
|
||||
|
||||
Reference in New Issue
Block a user