Bridge: frame coalescing -- the view can no longer fall behind real time
The main loop rendered EVERY draw_scene in arrival order, so any render slowdown played the mission slower than real time and the view drifted seconds behind the game (user: "renderer fell way out of sync"); the backpressure hides in the socket, so backlog= stayed 0. Now each pass slices all complete records, applies every state record in order, and presents only the newest frame; superseded presents are counted in the new skipped= report field. Live verdict over a full mission: frames=10169 skipped=3 backlog=0B -- locked to the wire. Also from the tuner session: seat trim granularity 0.1 (0.5 was too jumpy) and a HUD size trim (vrview_gl u_scale + VRVIEW_HUDSCALE env, live +/- keys) -- tuners stay enabled for pilot feedback; seat trim turns out to be per-mech (Mad Cat vs Thor want different values). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -201,6 +201,7 @@ void main() { // Division DAC gamma, once over the final image
|
||||
HUD_VS = """
|
||||
#version 330
|
||||
uniform vec4 u_rect; // view rect x0 y0 x1 y1 (dpl2d coordinate space)
|
||||
uniform float u_scale; // HUD size trim (VRVIEW_HUDSCALE / live +/- keys)
|
||||
in vec2 in_pos;
|
||||
void main() {
|
||||
vec2 p = vec2((in_pos.x - u_rect.x) / (u_rect.z - u_rect.x),
|
||||
@@ -209,10 +210,14 @@ void main() {
|
||||
// twist dial UNDER the reticle and the range bar filling top-to-bottom;
|
||||
// mapping straight onto GL's Y-up NDC rendered the whole HUD flipped.
|
||||
p.y = -p.y;
|
||||
gl_Position = vec4(p, 0.0, 1.0);
|
||||
gl_Position = vec4(p * u_scale, 0.0, 1.0);
|
||||
}
|
||||
"""
|
||||
|
||||
# HUD size trim, live-adjustable from the bridge (+/- keys); pin with
|
||||
# VRVIEW_HUDSCALE once the right size is found vs period footage.
|
||||
HUDSCALE = [float(os.environ.get('VRVIEW_HUDSCALE', '1.0'))]
|
||||
|
||||
HUD_FS = """
|
||||
#version 330
|
||||
uniform vec3 u_col;
|
||||
@@ -566,6 +571,7 @@ class GLRenderer(vrview.Renderer):
|
||||
self._set(self._hud_prog, 'u_rect',
|
||||
(float(vp['x0']), float(vp['y0']),
|
||||
float(vp['x1']), float(vp['y1'])))
|
||||
self._set(self._hud_prog, 'u_scale', float(HUDSCALE[0]))
|
||||
MODES = {'strip': mgl.LINE_STRIP, 'segs': mgl.LINES,
|
||||
'poly': mgl.TRIANGLE_FAN, 'points': mgl.POINTS}
|
||||
for kind, col, wd, pts in prims:
|
||||
|
||||
@@ -21,11 +21,14 @@ board.munga = False
|
||||
r = Renderer(w=832, h=512,
|
||||
title=f"dpl3-revive renderer (Dave) -- LIVE from our pod [{backend}]")
|
||||
r.fps = int(os.environ.get('BRIDGE_FPS', '60' if backend == 'GL' else '30'))
|
||||
# eye-height TRIM on top of the camera position (UP/DOWN arrows = +-5,
|
||||
# LEFT/RIGHT = +-1, live; render window must be focused). The chain camera
|
||||
# carries the true cockpit eye so the default trim is 0; the vehicle-root
|
||||
# fallback adds VEH_EYE below (12 was the hand-tuned value).
|
||||
# eye TRIM on top of the camera position (live; render window must be
|
||||
# focused): UP/DOWN = height +-1, LEFT/RIGHT = seat forward/back +-0.5
|
||||
# along the look direction (user: the canopy reads as "sitting too far
|
||||
# back" -- tune, then pin with FP_UPOFF / FP_FWDOFF). The chain camera
|
||||
# carries the true cockpit eye so the default trims are 0; the vehicle-
|
||||
# root fallback adds VEH_EYE below (12 was the hand-tuned value).
|
||||
UPOFF = [float(os.environ.get('FP_UPOFF', '0'))]
|
||||
FWDOFF = [float(os.environ.get('FP_FWDOFF', '0'))]
|
||||
VEH_EYE = 12.0
|
||||
# the un-overridden munga cam-chain method (fp_cam overrides r.cam_matrix
|
||||
# every frame, so grab the bound original once)
|
||||
@@ -137,6 +140,7 @@ def fp_cam(board, cache):
|
||||
cs, sn = math.cos(th), math.sin(th)
|
||||
fwd = np.array([cs * fwd[0] + sn * fwd[2], fwd[1],
|
||||
-sn * fwd[0] + cs * fwd[2]])
|
||||
eye = eye + fwd * FWDOFF[0] # seat forward/back trim
|
||||
back = -fwd
|
||||
right = np.cross(worldup, back)
|
||||
rn = np.linalg.norm(right)
|
||||
@@ -180,11 +184,26 @@ def render(board):
|
||||
if ev.type == pg.QUIT:
|
||||
raise KeyboardInterrupt
|
||||
if ev.type == pg.KEYDOWN:
|
||||
step = {pg.K_UP: 5, pg.K_DOWN: -5,
|
||||
pg.K_RIGHT: 1, pg.K_LEFT: -1}.get(ev.key)
|
||||
if step:
|
||||
UPOFF[0] = max(0.0, UPOFF[0] + step)
|
||||
print(f"eye height = {UPOFF[0]:.0f}", flush=True)
|
||||
hstep = {pg.K_UP: 1, pg.K_DOWN: -1}.get(ev.key)
|
||||
fstep = {pg.K_RIGHT: 0.1, pg.K_LEFT: -0.1}.get(ev.key)
|
||||
sstep = {pg.K_EQUALS: 0.05, pg.K_MINUS: -0.05,
|
||||
pg.K_KP_PLUS: 0.05, pg.K_KP_MINUS: -0.05}.get(ev.key)
|
||||
if hstep:
|
||||
UPOFF[0] += hstep
|
||||
if fstep:
|
||||
FWDOFF[0] += fstep
|
||||
if sstep:
|
||||
try:
|
||||
import vrview_gl
|
||||
vrview_gl.HUDSCALE[0] = max(
|
||||
0.2, vrview_gl.HUDSCALE[0] + sstep)
|
||||
print(f"HUD scale = {vrview_gl.HUDSCALE[0]:.2f}",
|
||||
flush=True)
|
||||
except Exception:
|
||||
pass
|
||||
if hstep or fstep:
|
||||
print(f"eye trim: height {UPOFF[0]:+.1f} "
|
||||
f"forward {FWDOFF[0]:+.1f}", flush=True)
|
||||
r.cache.maybe_rebuild(board)
|
||||
M = fp_cam(board, r.cache)
|
||||
if M is not None:
|
||||
@@ -270,11 +289,20 @@ print("tailing live wire -> Dave's renderer; drive the pod")
|
||||
|
||||
pending = b''
|
||||
frames = 0
|
||||
skipped = 0
|
||||
last_report = time.time()
|
||||
while True:
|
||||
chunk = read_chunk()
|
||||
if chunk:
|
||||
pending += chunk
|
||||
# Slice ALL complete records out of the buffer first, so we know which
|
||||
# draw_scene is the newest. Rendering every draw_scene in arrival order
|
||||
# means any render slowdown plays the mission slower than real time and
|
||||
# the view drifts seconds behind the game (backpressure hides in the
|
||||
# socket, not in len(pending)). State records all still apply, in order;
|
||||
# only superseded frame PRESENTS are skipped -- the view latches to the
|
||||
# freshest frame no matter how slow GL is.
|
||||
records = []
|
||||
off = 0
|
||||
n = len(pending)
|
||||
while n - off >= 8:
|
||||
@@ -284,22 +312,31 @@ while True:
|
||||
ln = struct.unpack_from('<I', pending, off + 4)[0]
|
||||
if n - off < 8 + ln:
|
||||
break # incomplete record: wait for more
|
||||
body = pending[off + 8:off + 8 + ln]
|
||||
records.append(pending[off + 8:off + 8 + ln])
|
||||
off += 8 + ln
|
||||
if len(body) >= 4:
|
||||
action = struct.unpack_from('<I', body, 0)[0]
|
||||
if action < 0x100:
|
||||
try:
|
||||
board.handle(Msg(False, 0xff, action, body[4:]))
|
||||
except Exception:
|
||||
pass
|
||||
if action == 0x1f: # torso/limb joint angles ride here
|
||||
try: track_joints(body[4:])
|
||||
except Exception: pass
|
||||
if action == 9: # draw_scene -> present a frame
|
||||
pending = pending[off:]
|
||||
last_draw = -1
|
||||
for i, body in enumerate(records):
|
||||
if len(body) >= 4 and struct.unpack_from('<I', body, 0)[0] == 9:
|
||||
last_draw = i
|
||||
for i, body in enumerate(records):
|
||||
if len(body) < 4:
|
||||
continue
|
||||
action = struct.unpack_from('<I', body, 0)[0]
|
||||
if action < 0x100:
|
||||
try:
|
||||
board.handle(Msg(False, 0xff, action, body[4:]))
|
||||
except Exception:
|
||||
pass
|
||||
if action == 0x1f: # torso/limb joint angles ride here
|
||||
try: track_joints(body[4:])
|
||||
except Exception: pass
|
||||
if action == 9: # draw_scene -> present a frame
|
||||
if i == last_draw:
|
||||
render(board)
|
||||
frames += 1
|
||||
pending = pending[off:]
|
||||
else:
|
||||
skipped += 1
|
||||
if not chunk:
|
||||
try: r.pump()
|
||||
except KeyboardInterrupt: break
|
||||
@@ -308,7 +345,7 @@ while True:
|
||||
time.sleep(0.02) # socket mode already waited in recv
|
||||
if time.time() - last_report > 4:
|
||||
last_report = time.time()
|
||||
print(f"frames={frames} nodes={len(board.nodes)} "
|
||||
print(f"frames={frames} skipped={skipped} nodes={len(board.nodes)} "
|
||||
f"uploads={len(board.uploads)} tex={len(board.tex)} "
|
||||
f"munga={board.munga} anim_abs={len(board.anim_abs)} "
|
||||
f"backlog={len(pending)}B", flush=True)
|
||||
|
||||
Reference in New Issue
Block a user