# include "ui.hpp"
# include <assert.h>
# include <map>
# include <cmath>
# include <iostream>
# include "common/util.h"
# include "common/timing.h"
# include <algorithm>
# define NANOVG_GLES3_IMPLEMENTATION
# include "nanovg_gl.h"
# include "nanovg_gl_utils.h"
# include "paint.hpp"
# include "sidebar.hpp"
// TODO: this is also hardcoded in common/transformations/camera.py
// TODO: choose based on frame input size
# ifdef QCOM2
const float y_offset = 150.0 ;
const float zoom = 1.1 ;
const mat3 intrinsic_matrix = ( mat3 ) { {
2648.0 , 0.0 , 1928.0 / 2 ,
0.0 , 2648.0 , 1208.0 / 2 ,
0.0 , 0.0 , 1.0
} } ;
# else
const float y_offset = 0.0 ;
const float zoom = 2.35 ;
const mat3 intrinsic_matrix = ( mat3 ) { {
910. , 0. , 1164.0 / 2 ,
0. , 910. , 874.0 / 2 ,
0. , 0. , 1.
} } ;
# endif
// Projects a point in car to space to the corresponding point in full frame
// image space.
bool car_space_to_full_frame ( const UIState * s , float in_x , float in_y , float in_z , vertex_data * out , float margin ) {
const vec4 car_space_projective = ( vec4 ) { { in_x , in_y , in_z , 1. } } ;
// We'll call the car space point p.
// First project into normalized image coordinates with the extrinsics matrix.
const vec4 Ep4 = matvecmul ( s - > scene . extrinsic_matrix , car_space_projective ) ;
// The last entry is zero because of how we store E (to use matvecmul).
const vec3 Ep = { { Ep4 . v [ 0 ] , Ep4 . v [ 1 ] , Ep4 . v [ 2 ] } } ;
const vec3 KEp = matvecmul3 ( intrinsic_matrix , Ep ) ;
// Project.
float x = KEp . v [ 0 ] / KEp . v [ 2 ] ;
float y = KEp . v [ 1 ] / KEp . v [ 2 ] ;
nvgTransformPoint ( & out - > x , & out - > y , s - > car_space_transform , x , y ) ;
return out - > x > = - margin & & out - > x < = s - > fb_w + margin & & out - > y > = - margin & & out - > y < = s - > fb_h + margin ;
}
static void ui_draw_text ( NVGcontext * vg , float x , float y , const char * string , float size , NVGcolor color , int font ) {
nvgFontFaceId ( vg , font ) ;
nvgFontSize ( vg , size ) ;
nvgFillColor ( vg , color ) ;
nvgText ( vg , x , y , string , NULL ) ;
}
static void draw_chevron ( UIState * s , float x_in , float y_in , float sz ,
NVGcolor fillColor , NVGcolor glowColor ) {
vertex_data out = { } ;
car_space_to_full_frame ( s , x_in , y_in , 0.0 , & out ) ;
auto [ x , y ] = out ;
sz = std : : clamp ( ( sz * 30 ) / ( x_in / 3 + 30 ) , 15.0f , 30.0f ) * zoom ;
y = std : : fmin ( s - > scene . viz_rect . bottom ( ) - sz * .6 , y ) ;
x = std : : clamp ( x , 0.f , s - > scene . viz_rect . right ( ) - sz / 2 ) ;
// glow
float g_xo = sz / 5 ;
float g_yo = sz / 10 ;
nvgBeginPath ( s - > vg ) ;
nvgMoveTo ( s - > vg , x + ( sz * 1.35 ) + g_xo , y + sz + g_yo ) ;
nvgLineTo ( s - > vg , x , y - g_xo ) ;
nvgLineTo ( s - > vg , x - ( sz * 1.35 ) - g_xo , y + sz + g_yo ) ;
nvgClosePath ( s - > vg ) ;
nvgFillColor ( s - > vg , glowColor ) ;
nvgFill ( s - > vg ) ;
// chevron
nvgBeginPath ( s - > vg ) ;
nvgMoveTo ( s - > vg , x + ( sz * 1.25 ) , y + sz ) ;
nvgLineTo ( s - > vg , x , y ) ;
nvgLineTo ( s - > vg , x - ( sz * 1.25 ) , y + sz ) ;
nvgClosePath ( s - > vg ) ;
nvgFillColor ( s - > vg , fillColor ) ;
nvgFill ( s - > vg ) ;
}
static void ui_draw_circle_image ( NVGcontext * vg , int x , int y , int size , int image , NVGcolor color , float img_alpha , int img_y = 0 ) {
const int img_size = size * 1.5 ;
nvgBeginPath ( vg ) ;
nvgCircle ( vg , x , y + ( bdr_s * 1.5 ) , size ) ;
nvgFillColor ( vg , color ) ;
nvgFill ( vg ) ;
ui_draw_image ( vg , { x - ( img_size / 2 ) , img_y ? img_y : y - ( size / 4 ) , img_size , img_size } , image , img_alpha ) ;
}
static void ui_draw_circle_image ( NVGcontext * vg , int x , int y , int size , int image , bool active ) {
float bg_alpha = active ? 0.3f : 0.1f ;
float img_alpha = active ? 1.0f : 0.15f ;
ui_draw_circle_image ( vg , x , y , size , image , nvgRGBA ( 0 , 0 , 0 , ( 255 * bg_alpha ) ) , img_alpha ) ;
}
static void draw_lead ( UIState * s , const cereal : : RadarState : : LeadData : : Reader & lead ) {
// Draw lead car indicator
float fillAlpha = 0 ;
float speedBuff = 10. ;
float leadBuff = 40. ;
float d_rel = lead . getDRel ( ) ;
float v_rel = lead . getVRel ( ) ;
if ( d_rel < leadBuff ) {
fillAlpha = 255 * ( 1.0 - ( d_rel / leadBuff ) ) ;
if ( v_rel < 0 ) {
fillAlpha + = 255 * ( - 1 * ( v_rel / speedBuff ) ) ;
}
fillAlpha = ( int ) ( fmin ( fillAlpha , 255 ) ) ;
}
draw_chevron ( s , d_rel , lead . getYRel ( ) , 25 , nvgRGBA ( 201 , 34 , 49 , fillAlpha ) , COLOR_YELLOW ) ;
}
static void ui_draw_line ( UIState * s , const vertex_data * v , const int cnt , NVGcolor * color , NVGpaint * paint ) {
if ( cnt = = 0 ) return ;
nvgBeginPath ( s - > vg ) ;
nvgMoveTo ( s - > vg , v [ 0 ] . x , v [ 0 ] . y ) ;
for ( int i = 1 ; i < cnt ; i + + ) {
nvgLineTo ( s - > vg , v [ i ] . x , v [ i ] . y ) ;
}
nvgClosePath ( s - > vg ) ;
if ( color ) {
nvgFillColor ( s - > vg , * color ) ;
} else if ( paint ) {
nvgFillPaint ( s - > vg , * paint ) ;
}
nvgFill ( s - > vg ) ;
}
static void draw_frame ( UIState * s ) {
mat4 * out_mat ;
if ( s - > scene . frontview ) {
glBindVertexArray ( s - > frame_vao [ 1 ] ) ;
out_mat = & s - > front_frame_mat ;
} else {
glBindVertexArray ( s - > frame_vao [ 0 ] ) ;
out_mat = & s - > rear_frame_mat ;
}
glActiveTexture ( GL_TEXTURE0 ) ;
if ( s - > last_frame ) {
glBindTexture ( GL_TEXTURE_2D , s - > texture [ s - > last_frame - > idx ] - > frame_tex ) ;
# ifndef QCOM
// this is handled in ion on QCOM
glTexImage2D ( GL_TEXTURE_2D , 0 , GL_RGB , s - > last_frame - > width , s - > last_frame - > height ,
0 , GL_RGB , GL_UNSIGNED_BYTE , s - > last_frame - > addr ) ;
# endif
}
glUseProgram ( s - > gl_shader - > prog ) ;
glUniform1i ( s - > gl_shader - > getUniformLocation ( " uTexture " ) , 0 ) ;
glUniformMatrix4fv ( s - > gl_shader - > getUniformLocation ( " uTransform " ) , 1 , GL_TRUE , out_mat - > v ) ;
assert ( glGetError ( ) = = GL_NO_ERROR ) ;
glEnableVertexAttribArray ( 0 ) ;
glDrawElements ( GL_TRIANGLES , 6 , GL_UNSIGNED_BYTE , ( const void * ) 0 ) ;
glDisableVertexAttribArray ( 0 ) ;
glBindVertexArray ( 0 ) ;
}
static void ui_draw_vision_lane_lines ( UIState * s ) {
const UIScene & scene = s - > scene ;
// paint lanelines
for ( int i = 0 ; i < std : : size ( scene . lane_line_vertices ) ; i + + ) {
NVGcolor color = nvgRGBAf ( 1.0 , 1.0 , 1.0 , scene . lane_line_probs [ i ] ) ;
ui_draw_line ( s , scene . lane_line_vertices [ i ] . v , scene . lane_line_vertices [ i ] . cnt , & color , nullptr ) ;
}
// paint road edges
for ( int i = 0 ; i < std : : size ( scene . road_edge_vertices ) ; i + + ) {
NVGcolor color = nvgRGBAf ( 1.0 , 0.0 , 0.0 , std : : clamp < float > ( 1.0 - scene . road_edge_stds [ i ] , 0.0 , 1.0 ) ) ;
ui_draw_line ( s , scene . road_edge_vertices [ i ] . v , scene . road_edge_vertices [ i ] . cnt , & color , nullptr ) ;
}
// paint path
NVGpaint track_bg = nvgLinearGradient ( s - > vg , s - > fb_w , s - > fb_h , s - > fb_w , s - > fb_h * .4 ,
COLOR_WHITE , COLOR_WHITE_ALPHA ( 0 ) ) ;
ui_draw_line ( s , scene . track_vertices . v , scene . track_vertices . cnt , nullptr , & track_bg ) ;
}
// Draw all world space objects.
static void ui_draw_world ( UIState * s ) {
const UIScene * scene = & s - > scene ;
// Don't draw on top of sidebar
nvgScissor ( s - > vg , scene - > viz_rect . x , scene - > viz_rect . y , scene - > viz_rect . w , scene - > viz_rect . h ) ;
// Draw lane edges and vision/mpc tracks
ui_draw_vision_lane_lines ( s ) ;
// Draw lead indicators if openpilot is handling longitudinal
if ( s - > longitudinal_control ) {
if ( scene - > lead_data [ 0 ] . getStatus ( ) ) {
draw_lead ( s , scene - > lead_data [ 0 ] ) ;
}
if ( scene - > lead_data [ 1 ] . getStatus ( ) & & ( std : : abs ( scene - > lead_data [ 0 ] . getDRel ( ) - scene - > lead_data [ 1 ] . getDRel ( ) ) > 3.0 ) ) {
draw_lead ( s , scene - > lead_data [ 1 ] ) ;
}
}
nvgResetScissor ( s - > vg ) ;
}
static void ui_draw_vision_maxspeed ( UIState * s ) {
float maxspeed = s - > scene . controls_state . getVCruise ( ) ;
const bool is_cruise_set = maxspeed ! = 0 & & maxspeed ! = SET_SPEED_NA ;
if ( is_cruise_set & & ! s - > is_metric ) { maxspeed * = 0.6225 ; }
const Rect rect = { s - > scene . viz_rect . x + ( bdr_s * 2 ) , int ( s - > scene . viz_rect . y + ( bdr_s * 1.5 ) ) , 184 , 202 } ;
ui_fill_rect ( s - > vg , rect , COLOR_BLACK_ALPHA ( 100 ) , 30. ) ;
ui_draw_rect ( s - > vg , rect , COLOR_WHITE_ALPHA ( 100 ) , 10 , 20. ) ;
nvgTextAlign ( s - > vg , NVG_ALIGN_CENTER | NVG_ALIGN_BASELINE ) ;
ui_draw_text ( s - > vg , rect . centerX ( ) , 148 , " MAX " , 26 * 2.5 , COLOR_WHITE_ALPHA ( is_cruise_set ? 200 : 100 ) , s - > font_sans_regular ) ;
if ( is_cruise_set ) {
const std : : string maxspeed_str = std : : to_string ( ( int ) std : : nearbyint ( maxspeed ) ) ;
ui_draw_text ( s - > vg , rect . centerX ( ) , 242 , maxspeed_str . c_str ( ) , 48 * 2.5 , COLOR_WHITE , s - > font_sans_bold ) ;
} else {
ui_draw_text ( s - > vg , rect . centerX ( ) , 242 , " N/A " , 42 * 2.5 , COLOR_WHITE_ALPHA ( 100 ) , s - > font_sans_semibold ) ;
}
}
static void ui_draw_vision_speed ( UIState * s ) {
const float speed = std : : max ( 0.0 , s - > scene . controls_state . getVEgo ( ) * ( s - > is_metric ? 3.6 : 2.2369363 ) ) ;
const std : : string speed_str = std : : to_string ( ( int ) std : : nearbyint ( speed ) ) ;
nvgTextAlign ( s - > vg , NVG_ALIGN_CENTER | NVG_ALIGN_BASELINE ) ;
ui_draw_text ( s - > vg , s - > scene . viz_rect . centerX ( ) , 240 , speed_str . c_str ( ) , 96 * 2.5 , COLOR_WHITE , s - > font_sans_bold ) ;
ui_draw_text ( s - > vg , s - > scene . viz_rect . centerX ( ) , 320 , s - > is_metric ? " km/h " : " mph " , 36 * 2.5 , COLOR_WHITE_ALPHA ( 200 ) , s - > font_sans_regular ) ;
}
static void ui_draw_vision_event ( UIState * s ) {
const int viz_event_w = 220 ;
const int viz_event_x = s - > scene . viz_rect . right ( ) - ( viz_event_w + bdr_s * 2 ) ;
const int viz_event_y = s - > scene . viz_rect . y + ( bdr_s * 1.5 ) ;
if ( s - > scene . controls_state . getDecelForModel ( ) & & s - > scene . controls_state . getEnabled ( ) ) {
// draw winding road sign
const int img_turn_size = 160 * 1.5 ;
const Rect rect = { viz_event_x - ( img_turn_size / 4 ) , viz_event_y + bdr_s - 25 , img_turn_size , img_turn_size } ;
ui_draw_image ( s - > vg , rect , s - > img_turn , 1.0f ) ;
} else if ( s - > scene . controls_state . getEngageable ( ) ) {
// draw steering wheel
const int bg_wheel_size = 96 ;
const int bg_wheel_x = viz_event_x + ( viz_event_w - bg_wheel_size ) ;
const int bg_wheel_y = viz_event_y + ( bg_wheel_size / 2 ) ;
const NVGcolor color = bg_colors [ s - > status ] ;
ui_draw_circle_image ( s - > vg , bg_wheel_x , bg_wheel_y , bg_wheel_size , s - > img_wheel , color , 1.0f , bg_wheel_y - 25 ) ;
}
}
static void ui_draw_vision_face ( UIState * s ) {
const int face_size = 96 ;
const int face_x = ( s - > scene . viz_rect . x + face_size + ( bdr_s * 2 ) ) ;
const int face_y = ( s - > scene . viz_rect . bottom ( ) - footer_h + ( ( footer_h - face_size ) / 2 ) ) ;
ui_draw_circle_image ( s - > vg , face_x , face_y , face_size , s - > img_face , s - > scene . dmonitoring_state . getFaceDetected ( ) ) ;
}
static void ui_draw_driver_view ( UIState * s ) {
s - > scene . sidebar_collapsed = true ;
const bool is_rhd = s - > scene . is_rhd ;
const Rect & viz_rect = s - > scene . viz_rect ;
const int width = 3 * viz_rect . w / 4 ;
const Rect rect = { viz_rect . centerX ( ) - width / 2 , viz_rect . y , width , viz_rect . h } ; // x, y, w, h
const Rect valid_rect = { is_rhd ? rect . right ( ) - rect . h / 2 : rect . x , rect . y , rect . h / 2 , rect . h } ;
// blackout
const int blackout_x = is_rhd ? rect . x : valid_rect . right ( ) ;
const int blackout_w = rect . w - valid_rect . w ;
NVGpaint gradient = nvgLinearGradient ( s - > vg , blackout_x , rect . y , blackout_x + blackout_w , rect . y ,
COLOR_BLACK_ALPHA ( is_rhd ? 255 : 0 ) , COLOR_BLACK_ALPHA ( is_rhd ? 0 : 255 ) ) ;
ui_fill_rect ( s - > vg , { blackout_x , rect . y , blackout_w , rect . h } , gradient ) ;
ui_fill_rect ( s - > vg , { blackout_x , rect . y , blackout_w , rect . h } , COLOR_BLACK_ALPHA ( 144 ) ) ;
// border
ui_draw_rect ( s - > vg , rect , bg_colors [ STATUS_OFFROAD ] , 1 ) ;
const bool face_detected = s - > scene . dmonitoring_state . getFaceDetected ( ) ;
if ( face_detected ) {
auto fxy_list = s - > scene . driver_state . getFacePosition ( ) ;
float face_x = fxy_list [ 0 ] ;
float face_y = fxy_list [ 1 ] ;
int fbox_x = valid_rect . centerX ( ) + ( is_rhd ? face_x : - face_x ) * valid_rect . w ;
int fbox_y = valid_rect . centerY ( ) + face_y * valid_rect . h ;
float alpha = 0.2 ;
if ( face_x = std : : abs ( face_x ) , face_y = std : : abs ( face_y ) ; face_x < = 0.35 & & face_y < = 0.4 )
alpha = 0.8 - ( face_x > face_y ? face_x : face_y ) * 0.6 / 0.375 ;
const int box_size = 0.6 * rect . h / 2 ;
ui_draw_rect ( s - > vg , { fbox_x - box_size / 2 , fbox_y - box_size / 2 , box_size , box_size } , nvgRGBAf ( 1.0 , 1.0 , 1.0 , alpha ) , 10 , 35. ) ;
}
// draw face icon
const int face_size = 85 ;
const int icon_x = is_rhd ? rect . right ( ) - face_size - bdr_s * 2 : rect . x + face_size + bdr_s * 2 ;
const int icon_y = rect . bottom ( ) - face_size - bdr_s * 2.5 ;
ui_draw_circle_image ( s - > vg , icon_x , icon_y , face_size , s - > img_face , face_detected ) ;
}
static void ui_draw_vision_header ( UIState * s ) {
const Rect & viz_rect = s - > scene . viz_rect ;
NVGpaint gradient = nvgLinearGradient ( s - > vg , viz_rect . x ,
viz_rect . y + ( header_h - ( header_h / 2.5 ) ) ,
viz_rect . x , viz_rect . y + header_h ,
nvgRGBAf ( 0 , 0 , 0 , 0.45 ) , nvgRGBAf ( 0 , 0 , 0 , 0 ) ) ;
ui_fill_rect ( s - > vg , { viz_rect . x , viz_rect . y , viz_rect . w , header_h } , gradient ) ;
ui_draw_vision_maxspeed ( s ) ;
ui_draw_vision_speed ( s ) ;
ui_draw_vision_event ( s ) ;
}
static void ui_draw_vision_footer ( UIState * s ) {
ui_draw_vision_face ( s ) ;
}
static float get_alert_alpha ( float blink_rate ) {
return 0.375 * cos ( ( millis_since_boot ( ) / 1000 ) * 2 * M_PI * blink_rate ) + 0.625 ;
}
static void ui_draw_vision_alert ( UIState * s ) {
static std : : map < cereal : : ControlsState : : AlertSize , const int > alert_size_map = {
{ cereal : : ControlsState : : AlertSize : : SMALL , 241 } ,
{ cereal : : ControlsState : : AlertSize : : MID , 390 } ,
{ cereal : : ControlsState : : AlertSize : : FULL , s - > fb_h } } ;
const UIScene * scene = & s - > scene ;
bool longAlert1 = scene - > alert_text1 . length ( ) > 15 ;
NVGcolor color = bg_colors [ s - > status ] ;
color . a * = get_alert_alpha ( scene - > alert_blinking_rate ) ;
const int alr_h = alert_size_map [ scene - > alert_size ] + bdr_s ;
const Rect rect = { . x = scene - > viz_rect . x - bdr_s ,
. y = s - > fb_h - alr_h ,
. w = scene - > viz_rect . w + ( bdr_s * 2 ) ,
. h = alr_h } ;
ui_fill_rect ( s - > vg , rect , color ) ;
ui_fill_rect ( s - > vg , rect , nvgLinearGradient ( s - > vg , rect . x , rect . y , rect . x , rect . bottom ( ) ,
nvgRGBAf ( 0.0 , 0.0 , 0.0 , 0.05 ) , nvgRGBAf ( 0.0 , 0.0 , 0.0 , 0.35 ) ) ) ;
nvgFillColor ( s - > vg , COLOR_WHITE ) ;
nvgTextAlign ( s - > vg , NVG_ALIGN_CENTER | NVG_ALIGN_BASELINE ) ;
if ( scene - > alert_size = = cereal : : ControlsState : : AlertSize : : SMALL ) {
ui_draw_text ( s - > vg , rect . centerX ( ) , rect . centerY ( ) + 15 , scene - > alert_text1 . c_str ( ) , 40 * 2.5 , COLOR_WHITE , s - > font_sans_semibold ) ;
} else if ( scene - > alert_size = = cereal : : ControlsState : : AlertSize : : MID ) {
ui_draw_text ( s - > vg , rect . centerX ( ) , rect . centerY ( ) - 45 , scene - > alert_text1 . c_str ( ) , 48 * 2.5 , COLOR_WHITE , s - > font_sans_bold ) ;
ui_draw_text ( s - > vg , rect . centerX ( ) , rect . centerY ( ) + 75 , scene - > alert_text2 . c_str ( ) , 36 * 2.5 , COLOR_WHITE , s - > font_sans_regular ) ;
} else if ( scene - > alert_size = = cereal : : ControlsState : : AlertSize : : FULL ) {
nvgFontSize ( s - > vg , ( longAlert1 ? 72 : 96 ) * 2.5 ) ;
nvgFontFaceId ( s - > vg , s - > font_sans_bold ) ;
nvgTextAlign ( s - > vg , NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE ) ;
nvgTextBox ( s - > vg , rect . x , rect . y + ( longAlert1 ? 360 : 420 ) , rect . w - 60 , scene - > alert_text1 . c_str ( ) , NULL ) ;
nvgFontSize ( s - > vg , 48 * 2.5 ) ;
nvgFontFaceId ( s - > vg , s - > font_sans_regular ) ;
nvgTextAlign ( s - > vg , NVG_ALIGN_CENTER | NVG_ALIGN_BOTTOM ) ;
nvgTextBox ( s - > vg , rect . x , rect . h - ( longAlert1 ? 300 : 360 ) , rect . w - 60 , scene - > alert_text2 . c_str ( ) , NULL ) ;
}
}
static void ui_draw_vision_frame ( UIState * s ) {
const UIScene * scene = & s - > scene ;
const Rect & viz_rect = scene - > viz_rect ;
// Draw video frames
glEnable ( GL_SCISSOR_TEST ) ;
glViewport ( s - > video_rect . x , s - > video_rect . y , s - > video_rect . w , s - > video_rect . h ) ;
glScissor ( viz_rect . x , viz_rect . y , viz_rect . w , viz_rect . h ) ;
draw_frame ( s ) ;
glDisable ( GL_SCISSOR_TEST ) ;
glViewport ( 0 , 0 , s - > fb_w , s - > fb_h ) ;
}
static void ui_draw_vision ( UIState * s ) {
const UIScene * scene = & s - > scene ;
if ( ! scene - > frontview ) {
// Draw augmented elements
if ( scene - > world_objects_visible ) {
ui_draw_world ( s ) ;
}
// Set Speed, Current Speed, Status/Events
ui_draw_vision_header ( s ) ;
if ( scene - > alert_size = = cereal : : ControlsState : : AlertSize : : NONE ) {
ui_draw_vision_footer ( s ) ;
}
} else {
ui_draw_driver_view ( s ) ;
}
}
static void ui_draw_background ( UIState * s ) {
const NVGcolor color = bg_colors [ s - > status ] ;
glClearColor ( color . r , color . g , color . b , 1.0 ) ;
glClear ( GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT ) ;
}
void ui_draw ( UIState * s ) {
s - > scene . viz_rect = Rect { bdr_s , bdr_s , s - > fb_w - 2 * bdr_s , s - > fb_h - 2 * bdr_s } ;
if ( ! s - > scene . sidebar_collapsed ) {
s - > scene . viz_rect . x + = sbr_w ;
s - > scene . viz_rect . w - = sbr_w ;
}
const bool draw_alerts = s - > started & & s - > status ! = STATUS_OFFROAD & &
s - > active_app = = cereal : : UiLayoutState : : App : : NONE ;
const bool draw_vision = draw_alerts & & s - > vipc_client - > connected ;
// GL drawing functions
ui_draw_background ( s ) ;
if ( draw_vision ) {
ui_draw_vision_frame ( s ) ;
}
glEnable ( GL_BLEND ) ;
glBlendFunc ( GL_SRC_ALPHA , GL_ONE_MINUS_SRC_ALPHA ) ;
glViewport ( 0 , 0 , s - > fb_w , s - > fb_h ) ;
// NVG drawing functions - should be no GL inside NVG frame
nvgBeginFrame ( s - > vg , s - > fb_w , s - > fb_h , 1.0f ) ;
ui_draw_sidebar ( s ) ;
if ( draw_vision ) {
ui_draw_vision ( s ) ;
}
if ( draw_alerts & & s - > scene . alert_size ! = cereal : : ControlsState : : AlertSize : : NONE ) {
ui_draw_vision_alert ( s ) ;
}
nvgEndFrame ( s - > vg ) ;
glDisable ( GL_BLEND ) ;
}
void ui_draw_image ( NVGcontext * vg , const Rect & r , int image , float alpha ) {
nvgBeginPath ( vg ) ;
NVGpaint imgPaint = nvgImagePattern ( vg , r . x , r . y , r . w , r . h , 0 , image , alpha ) ;
nvgRect ( vg , r . x , r . y , r . w , r . h ) ;
nvgFillPaint ( vg , imgPaint ) ;
nvgFill ( vg ) ;
}
void ui_draw_rect ( NVGcontext * vg , const Rect & r , NVGcolor color , int width , float radius ) {
nvgBeginPath ( vg ) ;
radius > 0 ? nvgRoundedRect ( vg , r . x , r . y , r . w , r . h , radius ) : nvgRect ( vg , r . x , r . y , r . w , r . h ) ;
nvgStrokeColor ( vg , color ) ;
nvgStrokeWidth ( vg , width ) ;
nvgStroke ( vg ) ;
}
static inline void fill_rect ( NVGcontext * vg , const Rect & r , const NVGcolor * color , const NVGpaint * paint , float radius ) {
nvgBeginPath ( vg ) ;
radius > 0 ? nvgRoundedRect ( vg , r . x , r . y , r . w , r . h , radius ) : nvgRect ( vg , r . x , r . y , r . w , r . h ) ;
if ( color ) nvgFillColor ( vg , * color ) ;
if ( paint ) nvgFillPaint ( vg , * paint ) ;
nvgFill ( vg ) ;
}
void ui_fill_rect ( NVGcontext * vg , const Rect & r , const NVGcolor & color , float radius ) {
fill_rect ( vg , r , & color , nullptr , radius ) ;
}
void ui_fill_rect ( NVGcontext * vg , const Rect & r , const NVGpaint & paint , float radius ) {
fill_rect ( vg , r , nullptr , & paint , radius ) ;
}
static const char frame_vertex_shader [ ] =
# ifdef NANOVG_GL3_IMPLEMENTATION
" #version 150 core \n "
# else
" #version 300 es \n "
# endif
" in vec4 aPosition; \n "
" in vec4 aTexCoord; \n "
" uniform mat4 uTransform; \n "
" out vec4 vTexCoord; \n "
" void main() { \n "
" gl_Position = uTransform * aPosition; \n "
" vTexCoord = aTexCoord; \n "
" } \n " ;
static const char frame_fragment_shader [ ] =
# ifdef NANOVG_GL3_IMPLEMENTATION
" #version 150 core \n "
# else
" #version 300 es \n "
# endif
" precision mediump float; \n "
" uniform sampler2D uTexture; \n "
" in vec4 vTexCoord; \n "
" out vec4 colorOut; \n "
" void main() { \n "
" colorOut = texture(uTexture, vTexCoord.xy); \n "
" } \n " ;
static const mat4 device_transform = { {
1.0 , 0.0 , 0.0 , 0.0 ,
0.0 , 1.0 , 0.0 , 0.0 ,
0.0 , 0.0 , 1.0 , 0.0 ,
0.0 , 0.0 , 0.0 , 1.0 ,
} } ;
// frame from 4/3 to 16/9 display
static const mat4 full_to_wide_frame_transform = { {
.75 , 0.0 , 0.0 , 0.0 ,
0.0 , 1.0 , 0.0 , 0.0 ,
0.0 , 0.0 , 1.0 , 0.0 ,
0.0 , 0.0 , 0.0 , 1.0 ,
} } ;
void ui_nvg_init ( UIState * s ) {
// init drawing
# ifdef QCOM
// on QCOM, we enable MSAA
s - > vg = nvgCreate ( 0 ) ;
# else
s - > vg = nvgCreate ( NVG_ANTIALIAS | NVG_STENCIL_STROKES | NVG_DEBUG ) ;
# endif
assert ( s - > vg ) ;
s - > font_sans_regular = nvgCreateFont ( s - > vg , " sans-regular " , " ../assets/fonts/opensans_regular.ttf " ) ;
assert ( s - > font_sans_regular > = 0 ) ;
s - > font_sans_semibold = nvgCreateFont ( s - > vg , " sans-semibold " , " ../assets/fonts/opensans_semibold.ttf " ) ;
assert ( s - > font_sans_semibold > = 0 ) ;
s - > font_sans_bold = nvgCreateFont ( s - > vg , " sans-bold " , " ../assets/fonts/opensans_bold.ttf " ) ;
assert ( s - > font_sans_bold > = 0 ) ;
s - > img_wheel = nvgCreateImage ( s - > vg , " ../assets/img_chffr_wheel.png " , 1 ) ;
assert ( s - > img_wheel ! = 0 ) ;
s - > img_turn = nvgCreateImage ( s - > vg , " ../assets/img_trafficSign_turn.png " , 1 ) ;
assert ( s - > img_turn ! = 0 ) ;
s - > img_face = nvgCreateImage ( s - > vg , " ../assets/img_driver_face.png " , 1 ) ;
assert ( s - > img_face ! = 0 ) ;
s - > img_button_settings = nvgCreateImage ( s - > vg , " ../assets/images/button_settings.png " , 1 ) ;
assert ( s - > img_button_settings ! = 0 ) ;
s - > img_button_home = nvgCreateImage ( s - > vg , " ../assets/images/button_home.png " , 1 ) ;
assert ( s - > img_button_home ! = 0 ) ;
s - > img_battery = nvgCreateImage ( s - > vg , " ../assets/images/battery.png " , 1 ) ;
assert ( s - > img_battery ! = 0 ) ;
s - > img_battery_charging = nvgCreateImage ( s - > vg , " ../assets/images/battery_charging.png " , 1 ) ;
assert ( s - > img_battery_charging ! = 0 ) ;
for ( int i = 0 ; i < = 5 ; + + i ) {
char network_asset [ 32 ] ;
snprintf ( network_asset , sizeof ( network_asset ) , " ../assets/images/network_%d.png " , i ) ;
s - > img_network [ i ] = nvgCreateImage ( s - > vg , network_asset , 1 ) ;
assert ( s - > img_network [ i ] ! = 0 ) ;
}
// init gl
s - > gl_shader = std : : make_unique < GLShader > ( frame_vertex_shader , frame_fragment_shader ) ;
GLint frame_pos_loc = glGetAttribLocation ( s - > gl_shader - > prog , " aPosition " ) ;
GLint frame_texcoord_loc = glGetAttribLocation ( s - > gl_shader - > prog , " aTexCoord " ) ;
glViewport ( 0 , 0 , s - > fb_w , s - > fb_h ) ;
glDisable ( GL_DEPTH_TEST ) ;
assert ( glGetError ( ) = = GL_NO_ERROR ) ;
for ( int i = 0 ; i < 2 ; i + + ) {
float x1 , x2 , y1 , y2 ;
if ( i = = 1 ) {
// flip horizontally so it looks like a mirror
x1 = 0.0 ;
x2 = 1.0 ;
y1 = 1.0 ;
y2 = 0.0 ;
} else {
x1 = 1.0 ;
x2 = 0.0 ;
y1 = 1.0 ;
y2 = 0.0 ;
}
const uint8_t frame_indicies [ ] = { 0 , 1 , 2 , 0 , 2 , 3 } ;
const float frame_coords [ 4 ] [ 4 ] = {
{ - 1.0 , - 1.0 , x2 , y1 } , //bl
{ - 1.0 , 1.0 , x2 , y2 } , //tl
{ 1.0 , 1.0 , x1 , y2 } , //tr
{ 1.0 , - 1.0 , x1 , y1 } , //br
} ;
glGenVertexArrays ( 1 , & s - > frame_vao [ i ] ) ;
glBindVertexArray ( s - > frame_vao [ i ] ) ;
glGenBuffers ( 1 , & s - > frame_vbo [ i ] ) ;
glBindBuffer ( GL_ARRAY_BUFFER , s - > frame_vbo [ i ] ) ;
glBufferData ( GL_ARRAY_BUFFER , sizeof ( frame_coords ) , frame_coords , GL_STATIC_DRAW ) ;
glEnableVertexAttribArray ( frame_pos_loc ) ;
glVertexAttribPointer ( frame_pos_loc , 2 , GL_FLOAT , GL_FALSE ,
sizeof ( frame_coords [ 0 ] ) , ( const void * ) 0 ) ;
glEnableVertexAttribArray ( frame_texcoord_loc ) ;
glVertexAttribPointer ( frame_texcoord_loc , 2 , GL_FLOAT , GL_FALSE ,
sizeof ( frame_coords [ 0 ] ) , ( const void * ) ( sizeof ( float ) * 2 ) ) ;
glGenBuffers ( 1 , & s - > frame_ibo [ i ] ) ;
glBindBuffer ( GL_ELEMENT_ARRAY_BUFFER , s - > frame_ibo [ i ] ) ;
glBufferData ( GL_ELEMENT_ARRAY_BUFFER , sizeof ( frame_indicies ) , frame_indicies , GL_STATIC_DRAW ) ;
glBindBuffer ( GL_ARRAY_BUFFER , 0 ) ;
glBindVertexArray ( 0 ) ;
}
s - > video_rect = Rect { bdr_s , bdr_s , s - > fb_w - 2 * bdr_s , s - > fb_h - 2 * bdr_s } ;
float zx = zoom * 2 * intrinsic_matrix . v [ 2 ] / s - > video_rect . w ;
float zy = zoom * 2 * intrinsic_matrix . v [ 5 ] / s - > video_rect . h ;
const mat4 frame_transform = { {
zx , 0.0 , 0.0 , 0.0 ,
0.0 , zy , 0.0 , - y_offset / s - > video_rect . h * 2 ,
0.0 , 0.0 , 1.0 , 0.0 ,
0.0 , 0.0 , 0.0 , 1.0 ,
} } ;
s - > front_frame_mat = matmul ( device_transform , full_to_wide_frame_transform ) ;
s - > rear_frame_mat = matmul ( device_transform , frame_transform ) ;
// Apply transformation such that video pixel coordinates match video
// 1) Put (0, 0) in the middle of the video
nvgTranslate ( s - > vg , s - > video_rect . x + s - > video_rect . w / 2 , s - > video_rect . y + s - > video_rect . h / 2 + y_offset ) ;
// 2) Apply same scaling as video
nvgScale ( s - > vg , zoom , zoom ) ;
// 3) Put (0, 0) in top left corner of video
nvgTranslate ( s - > vg , - intrinsic_matrix . v [ 2 ] , - intrinsic_matrix . v [ 5 ] ) ;
nvgCurrentTransform ( s - > vg , s - > car_space_transform ) ;
nvgResetTransform ( s - > vg ) ;
}