Files
StarPilot/openpilot/starpilot/ui/tests/test_onroad_torque_draw.py
T
2026-10-26 21:55:36 -04:00

130 lines
6.1 KiB
Python

from contextlib import contextmanager
from types import SimpleNamespace as NS
from unittest.mock import patch
import pyray as rl
import pytest
from openpilot.starpilot.ui.onroad_torque_draw import TorqueBarPainter
@contextmanager
def record_gpu():
data = NS(uniforms={}, uploads=[], frames=[], loads=[], unloads=[])
names, vertices, coordinates = {}, [], []
def upload(shader, location, values, kind):
name = next(name for name, value in names.items() if value == location)
data.uniforms[name] = tuple(values[0:4])
data.uploads.append((name, int(rl.ffi.cast('uintptr_t', values))))
def finish():
data.frames.append(NS(uniforms=dict(data.uniforms), vertices=tuple(vertices), coordinates=tuple(coordinates)))
vertices.clear()
coordinates.clear()
with patch.object(rl, 'load_shader_from_memory', side_effect=lambda *_: data.loads.append(1) or NS(id=len(data.loads))), \
patch.object(rl, 'get_shader_location', side_effect=lambda _, name: names.setdefault(name, len(names))), \
patch.object(rl, 'set_shader_value', side_effect=upload), \
patch.object(rl, 'begin_shader_mode'), patch.object(rl, 'end_shader_mode'), \
patch.object(rl, 'rl_set_texture'), patch.object(rl, 'rl_begin'), \
patch.object(rl, 'rl_tex_coord2f', side_effect=lambda x, y: coordinates.append((x, y))), \
patch.object(rl, 'rl_vertex2f', side_effect=lambda x, y: vertices.append((x, y))), \
patch.object(rl, 'rl_end', side_effect=finish), \
patch.object(rl, 'unload_shader', side_effect=lambda shader: data.unloads.append(shader.id)), \
patch.object(rl, 'is_window_ready', return_value=True), patch.object(rl, 'rl_get_shader_id_default', return_value=0):
yield data
def test_small_torque_changes_reach_the_screen_without_angle_steps():
painter = TorqueBarPainter()
with record_gpu() as gpu:
rect = rl.Rectangle(0, 0, 476, 240)
painter.render(rect, .3, 1, 536, 7)
painter.render(rect, .3001, 1, 536, 7)
endpoints = [frame.uniforms['indicatorNormals'][2] for frame in gpu.frames]
assert 0 < endpoints[1] - endpoints[0] < .00002
assert all(frame.vertices == frame.coordinates for frame in gpu.frames)
@pytest.mark.parametrize('rect,width,cap,torque,expected', [
((0, 0, 476, 240), 536, 7, .3, (246, 1286.134328, 1071.888060, 6.216418)),
((0, 0, 476, 240), 536, 7, 1, (246, 1282.582090, 1090.537313, 24.865672)),
((30, 30, 2100, 1020), 2160, 21, .3, (1088, 5917.430556, 4987.256944, 28.923611)),
((30, 30, 2100, 1020), 2160, 21, 1, (1088, 5900.902778, 5074.027778, 115.694444)),
((30, 30, 1800, 1020), 2160, 21, .3, (938, 5222.083333, 4274.791667, 24.791667)),
((30, 30, 1800, 1020), 2160, 21, 1, (938, 5207.916667, 4349.166667, 99.166667)),
])
def test_native_arc_dimensions_and_opacities_match_the_original_renderer(rect, width, cap, torque, expected):
with record_gpu() as gpu:
TorqueBarPainter().render(rl.Rectangle(*rect), torque, 1, width, cap)
frame, = gpu.frames
assert frame.uniforms['geometry'] == pytest.approx(expected, abs=.001)
assert tuple(round(value * 255) for value in frame.uniforms['style'][1:]) == ((63, 229, 229) if torque == .3 else (127, 229, 0))
@pytest.mark.parametrize('rect,width,cap', [((0, 0, 476, 240), 536, 7), ((30, 30, 2100, 1020), 2160, 21)])
@pytest.mark.parametrize('torque,warmth', [(.5, 0), (.75, 0), (.875, .5), (1, 1)])
def test_gradient_colors_and_direction_for_both_ui_sizes(rect, width, cap, torque, warmth):
painter = TorqueBarPainter()
with record_gpu() as gpu:
painter.render(rl.Rectangle(*rect), torque, 1, width, cap)
painter.render(rl.Rectangle(*rect), -torque, 1, width, cap)
right, left = [frame.uniforms['gradient'] for frame in gpu.frames]
assert right[0] > 0
assert left[0] == pytest.approx(-right[0])
assert left[1:] == pytest.approx(right[1:])
assert (1, right[1], right[3]) == pytest.approx((1, 1 - warmth * 140 / 255, 1 - warmth))
assert (1, right[1] + right[2], right[3]) == pytest.approx((1, 1 - warmth * 55 / 255, 1 - warmth))
def test_gradient_anchor_follows_the_bar_when_repositioned():
painter = TorqueBarPainter()
with record_gpu() as gpu:
painter.render(rl.Rectangle(30, 30, 2100, 1020), 1, .6, 2160, 21)
painter.render(rl.Rectangle(130, 80, 2100, 1020), 1, .6, 2160, 21, (7, -4))
first, second = gpu.frames
assert second.uniforms['gradient'] == pytest.approx(first.uniforms['gradient'])
assert second.uniforms['geometry'][0] - first.uniforms['geometry'][0] == pytest.approx(107)
assert second.uniforms['geometry'][1] - first.uniforms['geometry'][1] == pytest.approx(46)
assert second.uniforms['style'] == pytest.approx(first.uniforms['style'])
def test_shader_and_uniform_buffers_are_reused_and_released_before_reload():
painter = TorqueBarPainter()
rect = rl.Rectangle(0, 0, 476, 240)
with record_gpu() as gpu:
painter.close()
painter.render(rect, .3, 1, 536, 7)
first_uploads = list(gpu.uploads)
painter.render(rect, .3, 1, 536, 7)
assert gpu.uploads == first_uploads
painter.render(rect, .3001, 1, 536, 7)
pointers = dict(first_uploads)
assert all(pointer == pointers[name] for name, pointer in gpu.uploads)
assert len(gpu.loads) == 1
painter.close()
painter.close()
assert gpu.unloads == [1]
painter.render(rect, .3, 1, 536, 7)
assert len(gpu.loads) == 2
assert len(gpu.uploads) == len(first_uploads) * 2 + 1
painter.close()
assert gpu.unloads == [1, 2]
def test_invisible_bar_does_not_allocate_graphics_resources():
with record_gpu() as gpu:
TorqueBarPainter().render(rl.Rectangle(0, 0, 476, 240), .3, 0, 536, 7)
assert not gpu.loads and not gpu.frames
@pytest.mark.parametrize('default_shader_id', [0, 1])
def test_shader_failure_never_paints_the_bounding_rectangle(default_shader_id):
with record_gpu() as gpu, patch.object(rl, 'get_shader_location', return_value=-1), \
patch.object(rl, 'rl_get_shader_id_default', return_value=default_shader_id):
with pytest.raises(RuntimeError, match='Torque bar shader unavailable'):
TorqueBarPainter().render(rl.Rectangle(0, 0, 476, 240), .3, 1, 536, 7)
assert not gpu.frames
assert gpu.unloads == ([1] if default_shader_id == 0 else [])