system/ui: GPU-accelerated polygon rendering with anti-aliasing and gradients (#35357)

* Add GPU-accelerated polygon rendering with anti-aliased edges and gradient support

* use np array

* update ModelRenderer

* ndarray

* cleanup

* improve shader

* Revert "improve shader"

This reverts commit 992247617a9947bceb365f7b056fed6ebed3793d.

* improve shader for smoother edges
This commit is contained in:
Dean Lee
2025-05-28 12:47:55 +08:00
committed by GitHub
parent feaef58188
commit 6c28575573
2 changed files with 397 additions and 42 deletions
+59 -42
View File
@@ -4,6 +4,7 @@ import numpy as np
import pyray as rl
from cereal import messaging, car
from openpilot.common.params import Params
from openpilot.system.ui.lib.shader_polygon import draw_polygon
CLIP_MARGIN = 500
@@ -30,14 +31,14 @@ class ModelRenderer:
self._experimental_mode = False
self._blend_factor = 1.0
self._prev_allow_throttle = True
self._lane_line_probs = [0.0] * 4
self._road_edge_stds = [0.0] * 2
self._lane_line_probs = np.zeros(4, dtype=np.float32)
self._road_edge_stds = np.zeros(2, dtype=np.float32)
self._path_offset_z = 1.22
# Initialize empty polygon vertices
self._track_vertices = []
self._lane_line_vertices = [[] for _ in range(4)]
self._road_edge_vertices = [[] for _ in range(2)]
self._track_vertices = np.empty((0, 2), dtype=np.float32)
self._lane_line_vertices = [np.empty((0, 2), dtype=np.float32) for _ in range(4)]
self._road_edge_vertices = [np.empty((0, 2), dtype=np.float32) for _ in range(2)]
self._lead_vertices = [None, None]
# Transform matrix (3x3 for car space to screen space)
@@ -145,29 +146,29 @@ class ModelRenderer:
def _draw_lane_lines(self):
"""Draw lane lines and road edges"""
for i in range(4):
for i, vertices in enumerate(self._lane_line_vertices):
# Skip if no vertices
if not self._lane_line_vertices[i]:
if vertices.size == 0:
continue
# Draw lane line
alpha = np.clip(self._lane_line_probs[i], 0.0, 0.7)
color = rl.Color(255, 255, 255, int(alpha * 255))
self._draw_polygon(self._lane_line_vertices[i], color)
draw_polygon(vertices, color)
for i in range(2):
for i, vertices in enumerate(self._road_edge_vertices):
# Skip if no vertices
if not self._road_edge_vertices[i]:
if vertices.size == 0:
continue
# Draw road edge
alpha = np.clip(1.0 - self._road_edge_stds[i], 0.0, 1.0)
color = rl.Color(255, 0, 0, int(alpha * 255))
self._draw_polygon(self._road_edge_vertices[i], color)
draw_polygon(vertices, color)
def _draw_path(self, sm, model, height):
"""Draw the path polygon with gradient based on acceleration"""
if not self._track_vertices:
if self._track_vertices.size == 0:
return
if self._experimental_mode:
@@ -175,16 +176,29 @@ class ModelRenderer:
acceleration = model.acceleration.x
max_len = min(len(self._track_vertices) // 2, len(acceleration))
# Create gradient colors for path sections
for i in range(max_len):
# Find midpoint index for polygon
mid_point = len(self._track_vertices) // 2
# For acceleration-based coloring, process segments separately
left_side = self._track_vertices[:mid_point]
right_side = self._track_vertices[mid_point:][::-1] # Reverse for proper winding
# Create segments for gradient coloring
segment_colors = []
gradient_stops = []
for i in range(max_len - 1):
if i >= len(left_side) - 1 or i >= len(right_side) - 1:
break
track_idx = max_len - i - 1 # flip idx to start from bottom right
track_y = self._track_vertices[track_idx][1]
# Skip points out of frame
if track_y < 0 or track_y > height:
if left_side[track_idx][1] < 0 or left_side[track_idx][1] > height:
continue
# Calculate color based on acceleration
lin_grad_point = (height - track_y) / height
lin_grad_point = (height - left_side[track_idx][1]) / height
# speed up: 120, slow down: 0
path_hue = max(min(60 + acceleration[i] * 35, 120), 0)
@@ -197,12 +211,22 @@ class ModelRenderer:
# Use HSL to RGB conversion
color = self._hsla_to_color(path_hue / 360.0, saturation, lightness, alpha)
# TODO: This is simplified - a full implementation would create a gradient fill
segment = self._track_vertices[track_idx : track_idx + 2] + self._track_vertices[-track_idx - 2 : -track_idx]
self._draw_polygon(segment, color)
# Create quad segment
gradient_stops.append(lin_grad_point)
segment_colors.append(color)
# Skip a point, unless next is last
i += 1 if i + 2 < max_len else 0
if len(segment_colors) < 2:
draw_polygon(self._track_vertices, rl.Color(255, 255, 255, 30))
return
# Create gradient specification
gradient = {
'start': (0.0, 1.0), # Bottom of path
'end': (0.0, 0.0), # Top of path
'colors': segment_colors,
'stops': gradient_stops,
}
draw_polygon(self._track_vertices, gradient=gradient)
else:
# Draw with throttle/no throttle gradient
allow_throttle = sm['longitudinalPlan'].allowThrottle or not self._longitudinal_control
@@ -226,7 +250,13 @@ class ModelRenderer:
self._blend_colors(begin_colors[2], end_colors[2], self._blend_factor),
]
self._draw_polygon(self._track_vertices, colors[0])
gradient = {
'start': (0.0, 1.0), # Bottom of path
'end': (0.0, 0.0), # Top of path
'colors': colors,
'stops': [0.0, 1.0],
}
draw_polygon(self._track_vertices, gradient=gradient)
def _draw_lead(self, lead_data, vd, rect):
"""Draw lead vehicle indicator"""
@@ -284,14 +314,14 @@ class ModelRenderer:
return (x, y)
def _map_line_to_polygon(self, line, y_off, z_off, max_idx, allow_invert=True):
def _map_line_to_polygon(self, line, y_off, z_off, max_idx, allow_invert=True)-> np.ndarray:
"""Convert a 3D line to a 2D polygon for drawing"""
line_x = line.x
line_y = line.y
line_z = line.z
left_points = []
right_points = []
left_points: list[tuple[float, float]] = []
right_points: list[tuple[float, float]] = []
for i in range(max_idx + 1):
# Skip points with negative x (behind camera)
@@ -309,23 +339,10 @@ class ModelRenderer:
left_points.append(left)
right_points.append(right)
if not left_points:
return []
if not left_points or not right_points:
return np.empty((0, 2), dtype=np.float32)
return left_points + right_points[::-1]
def _draw_polygon(self, points, color):
# TODO: Enhance polygon drawing to support even-odd fill rule efficiently, as Raylib lacks native support.
# Use a faster triangulation algorithm (e.g., ear clipping) or GPU shader for
# efficient rendering of lane lines, road edges, and path polygons.
if len(points) <= 8:
rl.draw_triangle_fan(points, len(points), color)
else:
for i in range(1, len(points) - 1):
rl.draw_triangle(points[0], points[i], points[i + 1], color)
for i in range(len(points)):
rl.draw_line_ex(points[i], points[(i + 1) % len(points)], 1.5, color)
return np.array(left_points + right_points[::-1], dtype=np.float32)
@staticmethod
def _map_val(x, x0, x1, y0, y1):