|
|
|
|
@ -41,6 +41,178 @@ function debug_panel_init(state, context) {
@@ -41,6 +41,178 @@ function debug_panel_init(state, context) {
|
|
|
|
|
schedule_draw(state, context); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
document.getElementById('debug-render').addEventListener('click', async (e) => { |
|
|
|
|
const encoded_chunks = []; |
|
|
|
|
let total_chunk_bytes = 0; |
|
|
|
|
|
|
|
|
|
const handle_encoded_chunk = (chunk, metadata) => { |
|
|
|
|
encoded_chunks.push(chunk); |
|
|
|
|
total_chunk_bytes += chunk.byteLength; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const canvas = document.getElementById('c'); |
|
|
|
|
|
|
|
|
|
const init = { |
|
|
|
|
output: handle_encoded_chunk, |
|
|
|
|
error: (e) => { |
|
|
|
|
console.log(e.message); |
|
|
|
|
}, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const render_framerate = 240; |
|
|
|
|
const render_frames = 720; |
|
|
|
|
const render_output_framerate = 60; |
|
|
|
|
const start_movement_frame = 240; |
|
|
|
|
const start_movement2_frame = 480; |
|
|
|
|
let updated_zoom = false; |
|
|
|
|
|
|
|
|
|
if (render_framerate % render_output_framerate !== 0) { |
|
|
|
|
console.error('Render framerate MUST be divisible by output framerate'); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const render_ratio = render_framerate / render_output_framerate; |
|
|
|
|
|
|
|
|
|
if (render_frames % render_ratio !== 0) { |
|
|
|
|
console.error('Render frames MUST be divisible render ratio'); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const render_output_frames = render_frames / render_ratio; |
|
|
|
|
|
|
|
|
|
const encoder_config = { |
|
|
|
|
codec: "vp8", |
|
|
|
|
width: canvas.width, |
|
|
|
|
height: canvas.height, |
|
|
|
|
displayWidth: canvas.width, |
|
|
|
|
displayHeight: canvas.height, |
|
|
|
|
bitrate: 100_000_000, |
|
|
|
|
framerate: render_output_framerate, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const { supported } = await VideoEncoder.isConfigSupported(encoder_config); |
|
|
|
|
if (supported) { |
|
|
|
|
const encoder = new VideoEncoder(init); |
|
|
|
|
encoder.configure(encoder_config); |
|
|
|
|
|
|
|
|
|
let zoom_level = 5; |
|
|
|
|
|
|
|
|
|
const dz = (zoom_level > 0 ? config.zoom_delta : -config.zoom_delta); |
|
|
|
|
state.canvas.zoom_screenp = {'x': canvas.width / 2, 'y': canvas.height / 2}; |
|
|
|
|
state.canvas.zoom_level = zoom_level; |
|
|
|
|
state.canvas.target_zoom = Math.pow(1.0 + dz, Math.abs(zoom_level)) |
|
|
|
|
|
|
|
|
|
const dst = new Uint8Array(canvas.width * canvas.height * 4); |
|
|
|
|
const accum = new Uint16Array(canvas.width * canvas.height * 4); // double the size to fit many samples
|
|
|
|
|
const accum_final = new Uint8Array(canvas.width * canvas.height * 4); |
|
|
|
|
|
|
|
|
|
for (let i = 0; i < render_output_frames; ++i) { |
|
|
|
|
const time_now = i / render_output_framerate; |
|
|
|
|
|
|
|
|
|
accum.fill(0); |
|
|
|
|
|
|
|
|
|
for (let subframe = 0; subframe < render_ratio; ++subframe) { |
|
|
|
|
await draw(state, context, i > 0 || subframe > 0, (time_now + (subframe / render_ratio) / render_output_framerate) * 1000, true); |
|
|
|
|
context.gl.readPixels(0, 0, canvas.width, canvas.height, context.gl.RGBA, context.gl.UNSIGNED_BYTE, dst); |
|
|
|
|
|
|
|
|
|
for (let j = 0; j < dst.byteLength; ++j) { |
|
|
|
|
accum[j] += dst[j]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (start_movement_frame <= i * render_ratio && i * render_ratio < start_movement2_frame) { |
|
|
|
|
state.canvas.offset.x += (1000 - state.canvas.offset.x) * 0.05; |
|
|
|
|
state.canvas.offset.y += (1000 - state.canvas.offset.y) * 0.05; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (i * render_ratio > start_movement2_frame) { |
|
|
|
|
if (!updated_zoom) { |
|
|
|
|
zoom_level = -25; |
|
|
|
|
const dz = (zoom_level > 0 ? config.zoom_delta : -config.zoom_delta); |
|
|
|
|
state.canvas.zoom_level = zoom_level; |
|
|
|
|
state.canvas.target_zoom = Math.pow(1.0 + dz, Math.abs(zoom_level)) |
|
|
|
|
updated_zoom = true; |
|
|
|
|
} |
|
|
|
|
//state.canvas.offset.x += (3500 - state.canvas.offset.x) * 0.05;
|
|
|
|
|
//state.canvas.offset.y += (5000 - state.canvas.offset.y) * 0.05;
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for (let y = 0; y < canvas.height; ++y) { |
|
|
|
|
for (let x = 0; x < canvas.width; ++x) { |
|
|
|
|
for (let c = 0; c < 4; ++c) { |
|
|
|
|
accum_final[(canvas.height - 1 - y) * canvas.width * 4 + x * 4 + c] = Math.round(accum[y * canvas.width * 4 + x * 4 + c] / render_ratio); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const frame = new VideoFrame(accum_final, { timestamp: time_now, codedWidth: canvas.width, codedHeight: canvas.height, format: 'RGBA'}); |
|
|
|
|
encoder.encode(frame, {keyFrame: true}); |
|
|
|
|
frame.close(); |
|
|
|
|
await encoder.flush(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const data = new Uint8Array(32 + render_frames * 12 + total_chunk_bytes); |
|
|
|
|
|
|
|
|
|
data[0] = 0x44; |
|
|
|
|
data[1] = 0x4B; |
|
|
|
|
data[2] = 0x49; |
|
|
|
|
data[3] = 0x46; |
|
|
|
|
|
|
|
|
|
data[6] = 32; |
|
|
|
|
data[8] = 0x56; |
|
|
|
|
data[9] = 0x50; |
|
|
|
|
data[10] = 0x38; |
|
|
|
|
data[11] = 0x30; |
|
|
|
|
|
|
|
|
|
data[12] = encoder_config.width & 0xFF; |
|
|
|
|
data[13] = (encoder_config.width >> 8) & 0xFF; |
|
|
|
|
|
|
|
|
|
data[14] = encoder_config.height & 0xFF; |
|
|
|
|
data[15] = (encoder_config.height >> 8) & 0xFF; |
|
|
|
|
|
|
|
|
|
data[16] = render_output_framerate; // timebase denom
|
|
|
|
|
|
|
|
|
|
data[20] = 1; // timebase numenator..?
|
|
|
|
|
|
|
|
|
|
data[24] = render_output_frames; // frame count
|
|
|
|
|
|
|
|
|
|
let offset = 32; |
|
|
|
|
for (let i = 0; i < render_output_frames; ++i) { |
|
|
|
|
const chunk = encoded_chunks[i]; |
|
|
|
|
// frame header
|
|
|
|
|
// frame length
|
|
|
|
|
data[offset + 0] = chunk.byteLength & 0xFF; |
|
|
|
|
data[offset + 1] = (chunk.byteLength >> 8) & 0xFF; |
|
|
|
|
data[offset + 2] = (chunk.byteLength >> 16) & 0xFF; |
|
|
|
|
data[offset + 3] = (chunk.byteLength >> 24) & 0xFF; |
|
|
|
|
|
|
|
|
|
// PTS
|
|
|
|
|
// set to frame number for now
|
|
|
|
|
data[offset + 4] = i & 0xFF; |
|
|
|
|
data[offset + 5] = (i >> 8) & 0xFF; |
|
|
|
|
data[offset + 6] = (i >> 16) & 0xFF; |
|
|
|
|
data[offset + 7] = (i >> 24) & 0xFF; |
|
|
|
|
|
|
|
|
|
chunk.copyTo(new Uint8Array(data.buffer, offset + 12)); |
|
|
|
|
|
|
|
|
|
offset += 12 + chunk.byteLength; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const blob = new Blob([data.buffer], { type: 'application/octet-stream' }); |
|
|
|
|
const url = URL.createObjectURL(blob); |
|
|
|
|
|
|
|
|
|
const a = document.createElement('a'); |
|
|
|
|
a.href = url; |
|
|
|
|
a.style.display = 'none'; |
|
|
|
|
a.download = 'rendered_stream.ivf'; // Set the suggested filename
|
|
|
|
|
document.body.appendChild(a); |
|
|
|
|
a.click(); |
|
|
|
|
} else { |
|
|
|
|
console.error('Encoding config not supported'); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
document.getElementById('debug-begin-benchmark').addEventListener('click', (e) => { |
|
|
|
|
state.canvas.zoom_level = config.benchmark.zoom_level; |
|
|
|
|
state.canvas.offset.x = config.benchmark.offset.x; |
|
|
|
|
|