summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/config.cpp180
-rw-r--r--src/config.h23
-rw-r--r--src/gauge.frag4
-rw-r--r--src/gauge.vert9
-rw-r--r--src/gauges.cpp621
-rw-r--r--src/gauges.h99
-rw-r--r--src/hooks.cpp275
-rw-r--r--src/overlay.cpp269
-rw-r--r--src/overlay.h46
-rw-r--r--src/spv.h137
-rw-r--r--src/typedef.h64
-rw-r--r--src/udp.cpp106
-rw-r--r--src/udp.h44
-rw-r--r--src/util.h27
-rw-r--r--src/vulkan.cpp618
-rw-r--r--src/vulkan.h134
16 files changed, 2656 insertions, 0 deletions
diff --git a/src/config.cpp b/src/config.cpp
new file mode 100644
index 0000000..22ea4aa
--- /dev/null
+++ b/src/config.cpp
@@ -0,0 +1,180 @@
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "config.h"
+
+const char DEFAULT_CONFIG[] =
+ "# fhvkov - forza horizon overlay (vulkan layer)\n"
+ "# positions and sizes are fractions of the game window;\n"
+ "# _x/_y are gauge centers. this file is re-read every second,\n"
+ "# so you can edit and save while driving.\n"
+ "port=9999\n"
+ "units=kmh\n"
+ "keep=0\n"
+ "scale=1.0\n"
+ "# needle motion blur: seconds of smear (try 0.06-0.15), 0 = off\n"
+ "blur=0.10\n"
+ "\n"
+ "tach_x=0.878\n"
+ "tach_y=0.830\n"
+ "tach_r=0.130\n"
+ "flash_needle=1\n"
+ "# whether to scale max rpm based on rpm reached by car\n"
+ "autoscale_rpm=1\n"
+ "# offset from max rpm at which needle starts flashing red\n"
+ "rpm_off=800.0\n"
+ "\n"
+ "show_boost=1\n"
+ "boost_x=0.775\n"
+ "boost_y=0.907\n"
+ "boost_r=0.060\n"
+ "\n"
+ "# lamps: abs, tcs (wheel slip) and tcs launch-control flash\n"
+ "show_abs=1\n"
+ "show_tcs=0\n"
+ "# enabling LC shows the \"tcs\" lamp\n"
+ "show_lc=1\n"
+ "show_tires=1\n"
+ "tire_x=0.970\n"
+ "tire_y=0.710\n"
+ "tire_sc=1.0\n";
+
+void config_defaults( CONFIG* c ) {
+ c->port = 9999;
+ c->units = 0;
+ c->keep = 0;
+ c->tach_x = 0.878; c->tach_y = 0.830; c->tach_r = 0.130;
+ c->scale = 1.0;
+ c->blur = 0.10;
+ c->show_boost = 1;
+ c->boost_x = 0.775; c->boost_y = 0.907; c->boost_r = 0.060;
+ c->show_abs = 1; c->show_tcs = 1; c->show_lc = 1;
+ c->show_tires = 1;
+ c->tire_x = 0.97; c->tire_y = 0.71, c->tire_sc = 1.0;
+ c->flash_needle = 1;
+ c->autoscale_rpm = 1;
+ c->rpm_off = 800.0;
+}
+
+void config_resolve_path( CONFIG* c ) {
+ const char* env = getenv( "FHRPM_CONFIG" );
+ if( env ) {
+ snprintf( c->path, sizeof( c->path ), "%s", env );
+ return;
+ }
+ const char* home = getenv( "HOME" );
+ if( !home )
+ home = ".";
+ snprintf( c->path, sizeof( c->path ), "%s/.config/fhvkov/fhvkov.conf", home );
+}
+
+void config_write_default( const CONFIG* c ) {
+ char dir[512];
+ const char* home = getenv( "HOME" );
+ if( home ) {
+ snprintf( dir, sizeof( dir ), "%s/.config", home );
+ mkdir( dir, 0755 );
+ snprintf( dir, sizeof( dir ), "%s/.config/fhvkov", home );
+ mkdir( dir, 0755 );
+ }
+
+ FILE* f = fopen( c->path, "w" );
+ if( !f )
+ return;
+ defer( fclose( f ) );
+
+ fwrite( DEFAULT_CONFIG, 1, sizeof( DEFAULT_CONFIG ) - 1, f );
+ dlog( "fhvkov: wrote default config to %s\n", c->path );
+}
+
+void config_parse_line( CONFIG* c, char* key, char* val ) {
+ if( !strcmp( key, "port" ) ) c->port = atoi( val );
+ else if( !strcmp( key, "units" ) ) c->units = !strcmp( val, "mph" );
+ else if( !strcmp( key, "keep" ) ) c->keep = atoi( val );
+ else if( !strcmp( key, "tach_x" ) ) c->tach_x = atof( val );
+ else if( !strcmp( key, "tach_y" ) ) c->tach_y = atof( val );
+ else if( !strcmp( key, "tach_r" ) ) c->tach_r = atof( val );
+ else if( !strcmp( key, "show_boost" ) ) c->show_boost = atoi( val );
+ else if( !strcmp( key, "boost_x" ) ) c->boost_x = atof( val );
+ else if( !strcmp( key, "boost_y" ) ) c->boost_y = atof( val );
+ else if( !strcmp( key, "boost_r" ) ) c->boost_r = atof( val );
+ else if( !strcmp( key, "scale" ) ) c->scale = atof( val );
+ else if( !strcmp( key, "blur" ) ) c->blur = atof( val );
+ else if( !strcmp( key, "show_abs" ) ) c->show_abs = atoi( val );
+ else if( !strcmp( key, "show_tcs" ) ) c->show_tcs = atoi( val );
+ else if( !strcmp( key, "show_lc" ) ) c->show_lc = atoi( val );
+ else if( !strcmp( key, "show_tires" ) ) c->show_tires = atoi( val );
+ else if( !strcmp( key, "tire_x" ) ) c->tire_x = atof( val );
+ else if( !strcmp( key, "tire_y" ) ) c->tire_y = atof( val );
+ else if( !strcmp( key, "tire_sc" ) ) c->tire_sc = atof( val );
+ else if( !strcmp( key, "flash_needle" ) ) c->flash_needle = atoi( val );
+ else if( !strcmp( key, "autoscale_rpm" ) ) c->autoscale_rpm = atoi( val );
+ else if( !strcmp( key, "rpm_off" ) ) c->rpm_off = atof( val );
+}
+
+STAT config_load( CONFIG* c ) {
+ FILE* f = fopen( c->path, "r" );
+ if( !f )
+ return STAT_ERR;
+ defer( fclose( f ) );
+
+ char line[256];
+ while( fgets( line, sizeof( line ), f ) ) {
+ char* p = line;
+ while( *p == ' ' || *p == '\t' )
+ p++;
+ if( *p == '#' || *p == '\n' ||* p == 0 )
+ continue;
+
+ char* eq = strchr( p, '=' );
+ if( !eq )
+ continue;
+ *eq = 0;
+
+ char* key = p, *val = eq + 1;
+ char* nl = strchr( val, '\n' );
+ if( nl )
+ *nl = 0;
+ char* e = key + strlen( key );
+ while( e > key && ( e[-1] == ' ' || e[-1] == '\t' ) )
+ *--e = 0;
+
+ config_parse_line( c, key, val );
+ }
+
+ struct stat st;
+ if( stat( c->path, &st ) == 0 )
+ c->mtime = st.st_mtime;
+ return STAT_OK;
+}
+
+void config_init( CONFIG* c ) {
+ config_defaults( c );
+ config_resolve_path( c );
+ if( !OK( config_load( c ) ) ) {
+ config_write_default( c );
+ config_load( c );
+ }
+}
+
+U8 config_poll( CONFIG* c ) {
+ struct stat st;
+ if( stat( c->path, &st ) != 0 )
+ return 0;
+ if( st.st_mtime == c->mtime )
+ return 0;
+
+ CONFIG fresh;
+ config_defaults( &fresh );
+ memcpy( fresh.path, c->path, sizeof( fresh.path ) );
+ if( !OK( config_load( &fresh ) ) )
+ return 0;
+
+ fresh.port = c->port;
+ *c = fresh;
+ dlog( "fhvkov: config reloaded\n" );
+ return 1;
+}
diff --git a/src/config.h b/src/config.h
new file mode 100644
index 0000000..cca2d78
--- /dev/null
+++ b/src/config.h
@@ -0,0 +1,23 @@
+#pragma once
+#include "util.h"
+
+struct CONFIG {
+ I32 port;
+ I32 units; // 0 km/h, 1 mph
+ I32 keep;
+ F64 tach_x, tach_y, tach_r; // center + radius, fractions
+ F64 scale;
+ F64 blur;
+ I32 show_boost;
+ I32 flash_needle;
+ F64 boost_x, boost_y, boost_r;
+ I32 show_abs, show_tcs, show_lc, show_tires;
+ F64 tire_x, tire_y, tire_sc;
+ I32 autoscale_rpm;
+ F64 rpm_off;
+ char path[512];
+ time_t mtime;
+};
+
+void config_init( CONFIG *c );
+U8 config_poll( CONFIG *c );
diff --git a/src/gauge.frag b/src/gauge.frag
new file mode 100644
index 0000000..1c3f17a
--- /dev/null
+++ b/src/gauge.frag
@@ -0,0 +1,4 @@
+#version 450
+layout(location = 0) in vec4 v_col;
+layout(location = 0) out vec4 o_col;
+void main() { o_col = v_col; }
diff --git a/src/gauge.vert b/src/gauge.vert
new file mode 100644
index 0000000..66179be
--- /dev/null
+++ b/src/gauge.vert
@@ -0,0 +1,9 @@
+#version 450
+layout(location = 0) in vec2 in_pos;
+layout(location = 1) in vec4 in_col;
+layout(push_constant) uniform PC { vec2 scale; } pc;
+layout(location = 0) out vec4 v_col;
+void main() {
+ gl_Position = vec4( in_pos * pc.scale - 1.0, 0.0, 1.0 );
+ v_col = in_col;
+}
diff --git a/src/gauges.cpp b/src/gauges.cpp
new file mode 100644
index 0000000..a15b65c
--- /dev/null
+++ b/src/gauges.cpp
@@ -0,0 +1,621 @@
+#include <cstdio>
+#include <math.h>
+
+#include "gauges.h"
+
+// 7-seg masks, bit order A B C D E F G; >=10 are letters
+const U8 SEG7[G_COUNT] = {
+ 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F,
+ 0x50, // r
+ 0x54, // n
+ 0x40, // -
+ 0x77, // A
+ 0x7C, // b
+ 0x6D, // S
+ 0x78, // t
+ 0x39, // C
+};
+
+F32 gauge_angle( F32 t ) {
+ const F32 A0 = -90.0f * (F32)M_PI / 180.0f; // south
+ const F32 A1 = -360.0f * (F32)M_PI / 180.0f; // east, clockwise
+ if( t < 0.0f ) t = 0.0f;
+ if( t > 1.0f ) t = 1.0f;
+ return A0 + ( A1 - A0 ) * t;
+}
+
+GAUGE_RENDERER::GAUGE_RENDERER() {
+ nverts = 0;
+ fade = 1.0f;
+}
+
+void GAUGE_RENDERER::clear() {
+ nverts = 0;
+ fade = 1.0f;
+}
+
+void GAUGE_RENDERER::set_fade( F32 f ) {
+ if( f < 0.0f ) f = 0.0f;
+ if( f > 1.0f ) f = 1.0f;
+ fade = f;
+}
+
+U32 GAUGE_RENDERER::faded( U32 c ) const {
+ if( fade >= 0.999f )
+ return c;
+ U32 a = (U32)( ( c >> 24 ) * fade );
+ return ( c & 0x00ffffffu ) | ( a << 24 );
+}
+
+void GAUGE_RENDERER::rect( F32 x, F32 y, F32 w, F32 h, U32 c ) {
+ c = faded( c );
+ if( w <= 0.0f || h <= 0.0f )
+ return;
+ if( nverts + 6 > MAX_VERTS )
+ return;
+
+ VERTEX *v = verts + nverts;
+ v[0].x = x; v[0].y = y;
+ v[1].x = x + w; v[1].y = y;
+ v[2].x = x; v[2].y = y + h;
+ v[3].x = x + w; v[3].y = y;
+ v[4].x = x + w; v[4].y = y + h;
+ v[5].x = x; v[5].y = y + h;
+ for( I32 i = 0; i < 6; i++ )
+ v[i].col = c;
+ nverts += 6;
+}
+
+void GAUGE_RENDERER::line( F32 x0, F32 y0, F32 x1, F32 y1, F32 t, U32 c ) {
+ c = faded( c );
+ F32 dx = x1 - x0, dy = y1 - y0;
+ F32 len = sqrtf( dx * dx + dy * dy );
+ if( len < 1e-3f || nverts + 6 > MAX_VERTS )
+ return;
+
+ F32 nx = -dy / len * t * 0.5f;
+ F32 ny = dx / len * t * 0.5f;
+ VERTEX *v = verts + nverts;
+ v[0].x = x0 + nx; v[0].y = y0 + ny;
+ v[1].x = x1 + nx; v[1].y = y1 + ny;
+ v[2].x = x0 - nx; v[2].y = y0 - ny;
+ v[3].x = x1 + nx; v[3].y = y1 + ny;
+ v[4].x = x1 - nx; v[4].y = y1 - ny;
+ v[5].x = x0 - nx; v[5].y = y0 - ny;
+ for( I32 i = 0; i < 6; i++ )
+ v[i].col = c;
+ nverts += 6;
+}
+
+void GAUGE_RENDERER::arc(
+ F32 cx, F32 cy, F32 r0, F32 r1,
+ F32 a0, F32 a1, U32 c
+) {
+ c = faded( c );
+ F32 span = a1 - a0;
+ I32 segs = (I32)( fabsf( span ) / 0.05f ) + 1;
+ if( segs > 256 )
+ segs = 256;
+ F32 step = span / (F32)segs;
+
+ F32 ca = cosf( a0 ), sa = sinf( a0 );
+ for( I32 i = 0; i < segs; i++ ) {
+ if( nverts + 6 > MAX_VERTS )
+ return;
+
+ F32 a = a0 + step * (F32)( i + 1 );
+ F32 cb = cosf( a ), sb = sinf( a );
+ VERTEX *v = verts + nverts;
+ v[0].x = cx + r0 * ca; v[0].y = cy - r0 * sa;
+ v[1].x = cx + r1 * ca; v[1].y = cy - r1 * sa;
+ v[2].x = cx + r0 * cb; v[2].y = cy - r0 * sb;
+ v[3].x = cx + r1 * ca; v[3].y = cy - r1 * sa;
+ v[4].x = cx + r1 * cb; v[4].y = cy - r1 * sb;
+ v[5].x = cx + r0 * cb; v[5].y = cy - r0 * sb;
+ for( I32 k = 0; k < 6; k++ )
+ v[k].col = c;
+ nverts += 6;
+ ca = cb; sa = sb;
+ }
+}
+
+void GAUGE_RENDERER::arc_grad(
+ F32 cx, F32 cy, F32 r0, F32 r1,
+ F32 a0, F32 a1, U32 c_in, U32 c_out
+) {
+ c_in = faded( c_in );
+ c_out = faded( c_out );
+ F32 span = a1 - a0;
+ I32 segs = (I32)( fabsf( span ) / 0.05f ) + 1;
+ if( segs > 256 )
+ segs = 256;
+ F32 step = span / (F32)segs;
+
+ F32 ca = cosf( a0 ), sa = sinf( a0 );
+ for( I32 i = 0; i < segs; i++ ) {
+ if( nverts + 6 > MAX_VERTS )
+ return;
+
+ F32 a = a0 + step * (F32)( i + 1 );
+ F32 cb = cosf( a ), sb = sinf( a );
+ VERTEX *v = verts + nverts;
+ v[0].x = cx + r0 * ca; v[0].y = cy - r0 * sa; v[0].col = c_in;
+ v[1].x = cx + r1 * ca; v[1].y = cy - r1 * sa; v[1].col = c_out;
+ v[2].x = cx + r0 * cb; v[2].y = cy - r0 * sb; v[2].col = c_in;
+ v[3].x = cx + r1 * ca; v[3].y = cy - r1 * sa; v[3].col = c_out;
+ v[4].x = cx + r1 * cb; v[4].y = cy - r1 * sb; v[4].col = c_out;
+ v[5].x = cx + r0 * cb; v[5].y = cy - r0 * sb; v[5].col = c_in;
+ nverts += 6;
+ ca = cb; sa = sb;
+ }
+}
+
+void GAUGE_RENDERER::disc( F32 cx, F32 cy, F32 r, U32 c ) {
+ arc( cx, cy, 0.0f, r, 0.0f, 2.0f * (F32)M_PI, c );
+}
+
+void GAUGE_RENDERER::disc_grad( F32 cx, F32 cy, F32 r, U32 c_in, U32 c_out ) {
+ arc_grad( cx, cy, 0.0f, r, 0.0f, 2.0f * (F32)M_PI, c_in, c_out );
+}
+
+void GAUGE_RENDERER::tick(
+ F32 cx, F32 cy, F32 a,
+ F32 rin, F32 rout,
+ F32 t, U32 c
+) {
+ F32 dx = cosf( a ), dy = -sinf( a );
+ line( cx + dx * rin, cy + dy * rin,
+ cx + dx * rout, cy + dy * rout, t, c );
+}
+
+void GAUGE_RENDERER::needle(
+ F32 cx, F32 cy, F32 a,
+ F32 len, F32 back,
+ F32 w, U32 c
+) {
+ c = faded( c );
+ if( nverts + 6 > MAX_VERTS )
+ return;
+
+ F32 dx = cosf( a ), dy = -sinf( a );
+ F32 px = -dy, py = dx;
+ F32 bx = cx - dx * back, by = cy - dy * back;
+ F32 tx = cx + dx * len, ty = cy + dy * len;
+ F32 wt = w * 0.18f;
+ VERTEX *v = verts + nverts;
+ v[0].x = bx + px * w * 0.5f; v[0].y = by + py * w * 0.5f;
+ v[1].x = tx + px * wt; v[1].y = ty + py * wt;
+ v[2].x = bx - px * w * 0.5f; v[2].y = by - py * w * 0.5f;
+ v[3].x = tx + px * wt; v[3].y = ty + py * wt;
+ v[4].x = tx - px * wt; v[4].y = ty - py * wt;
+ v[5].x = bx - px * w * 0.5f; v[5].y = by - py * w * 0.5f;
+ for( I32 i = 0; i < 6; i++ )
+ v[i].col = c;
+ nverts += 6;
+}
+
+void GAUGE_RENDERER::needle_trail(
+ F32 cx, F32 cy,
+ F32 a0, F32 a1,
+ F32 r0, F32 r1,
+ U32 c
+) {
+ c = faded( c );
+ F32 span = a1 - a0;
+ if( fabsf( span ) < 1e-3f )
+ return;
+
+ I32 segs = (I32)( fabsf( span ) / 0.03f ) + 1;
+ if( segs > 96 )
+ segs = 96;
+ F32 step = span / (F32)segs;
+ U32 base_a = c >> 24;
+ U32 rgb = c & 0x00ffffffu;
+
+ F32 ca = cosf( a0 ), sa = sinf( a0 );
+ for( I32 i = 0; i < segs; i++ ) {
+ if( nverts + 6 > MAX_VERTS )
+ return;
+
+ F32 f0 = (F32)i / (F32)segs;
+ F32 f1 = (F32)( i + 1 ) / (F32)segs;
+ U32 c0 = rgb | ( (U32)( base_a * f0 * f0 ) << 24 );
+ U32 c1 = rgb | ( (U32)( base_a * f1 * f1 ) << 24 );
+
+ F32 a = a0 + step * (F32)( i + 1 );
+ F32 cb = cosf( a ), sb = sinf( a );
+ VERTEX *v = verts + nverts;
+ v[0].x = cx + r0 * ca; v[0].y = cy - r0 * sa; v[0].col = c0;
+ v[1].x = cx + r1 * ca; v[1].y = cy - r1 * sa; v[1].col = c0;
+ v[2].x = cx + r0 * cb; v[2].y = cy - r0 * sb; v[2].col = c1;
+ v[3].x = cx + r1 * ca; v[3].y = cy - r1 * sa; v[3].col = c1;
+ v[4].x = cx + r1 * cb; v[4].y = cy - r1 * sb; v[4].col = c1;
+ v[5].x = cx + r0 * cb; v[5].y = cy - r0 * sb; v[5].col = c0;
+ nverts += 6;
+ ca = cb; sa = sb;
+ }
+}
+
+void GAUGE_RENDERER::glyph( F32 x, F32 y, F32 dw, F32 dh, I32 g, U32 c ) {
+ if( g < 0 || g >= G_COUNT )
+ return;
+
+ U32 m = SEG7[g];
+ F32 t = dh * 0.13f;
+ if( t < 1.0f )
+ t = 1.0f;
+ F32 half = ( dh - 3.0f * t ) * 0.5f;
+
+ if( m & 0x01 ) rect( x + t, y, dw - 2 * t, t, c );
+ if( m & 0x02 ) rect( x + dw - t, y + t, t, half, c );
+ if( m & 0x04 ) rect( x + dw - t, y + 2 * t + half, t, half, c );
+ if( m & 0x08 ) rect( x + t, y + dh - t, dw - 2 * t, t, c );
+ if( m & 0x10 ) rect( x, y + 2 * t + half, t, half, c );
+ if( m & 0x20 ) rect( x, y + t, t, half, c );
+ if( m & 0x40 ) rect( x + t, y + ( dh - t ) * 0.5f, dw - 2 * t, t, c );
+}
+
+void GAUGE_RENDERER::glyph_c( F32 cx, F32 cy, F32 dw, F32 dh, I32 g, U32 c ) {
+ glyph( cx - dw * 0.5f, cy - dh * 0.5f, dw, dh, g, c );
+}
+
+void GAUGE_RENDERER::number(
+ F32 cx, F32 cy,
+ F32 dw, F32 dh,
+ I32 val, U32 c
+) {
+ if( val < 0 )
+ val = 0;
+ if( val > 99999 )
+ val = 99999;
+
+ I32 nd = 1, v = val;
+ while( v >= 10 ) {
+ nd++;
+ v /= 10;
+ }
+
+ F32 adv = dw * 1.35f;
+ F32 x = cx + ( adv * (F32)nd - ( adv - dw ) ) - dw;
+ v = val;
+ for( I32 i = 0; i < nd; i++ ) {
+ glyph( x, cy - dh * 0.5f, dw, dh, v % 10, c );
+ v /= 10;
+ x -= adv;
+ }
+}
+
+void GAUGE_RENDERER::number_r(
+ F32 cx, F32 cy,
+ F32 dw, F32 dh,
+ I32 val, U32 c
+) {
+ if( val < 0 )
+ val = 0;
+ if( val > 99999 )
+ val = 99999;
+
+ I32 nd = 1, v = val;
+ while( v >= 10 ) {
+ nd++;
+ v /= 10;
+ }
+
+ F32 adv = dw * 1.35f;
+ F32 x = cx;
+ v = val;
+ for( I32 i = 0; i < nd; i++ ) {
+ glyph( x, cy - dh * 0.5f, dw, dh, v % 10, c );
+ v /= 10;
+ x -= adv;
+ }
+}
+
+void GAUGE_RENDERER::number_c(
+ F32 cx, F32 cy,
+ F32 dw, F32 dh,
+ I32 val, U32 c
+) {
+ if( val < 0 )
+ val = 0;
+ if( val > 99999 )
+ val = 99999;
+
+ I32 nd = 1, v = val;
+ while( v >= 10 ) {
+ nd++;
+ v /= 10;
+ }
+
+ F32 adv = dw * 1.35f;
+ F32 x = cx + ( adv * (F32)nd - ( adv - dw ) ) * 0.5f - dw;
+ v = val;
+ for( I32 i = 0; i < nd; i++ ) {
+ glyph( x, cy - dh * 0.5f, dw, dh, v % 10, c );
+ v /= 10;
+ x -= adv;
+ }
+}
+
+void GAUGE_RENDERER::lamp( F32 cx, F32 cy, F32 h, const I32 g[3], U32 c ) {
+ F32 dw = h * 0.62f;
+ F32 adv = dw * 1.35f;
+ F32 x = cx - adv;
+ for( I32 i = 0; i < 3; i++ ) {
+ glyph_c( x, cy, dw, h, g[i], c );
+ x += adv;
+ }
+}
+
+
+void GAUGE_RENDERER::build_tach_bg(
+ F32 cx, F32 cy, F32 r,
+ F32 rpm_max, F32 red_ang, F32 rpm_off
+) {
+ disc_grad( cx, cy, r * 1.10f, C_HALO_IN, C_HALO_OUT );
+ disc_grad( cx, cy, r, C_FACE_MID, C_FACE_EDGE );
+
+ arc( cx, cy, r * 0.20f, r * 0.54f,
+ 40.0f * (F32)M_PI / 180.0f, 270.0f * (F32)M_PI / 180.0f, C_SWIRL );
+ arc( cx, cy, r - 2.0f, r, 0.0f, 2.0f * (F32)M_PI, C_RIM );
+
+ if( rpm_off > 0.f ) {
+ arc( cx, cy, r * 0.84f, r * 0.965f,
+ gauge_angle( red_ang ), gauge_angle( red_ang + rpm_off ), C_REDLINE2 );
+ }
+ arc( cx, cy, r * 0.84f, r * 0.965f,
+ gauge_angle( red_ang + rpm_off ), gauge_angle( 1.0f ), C_REDLINE );
+
+
+ I32 kmax = (I32)( rpm_max / 1000.0f + 0.999f );
+ for( I32 k1000 = 0; k1000 <= kmax; k1000++ ) {
+ F32 t = (F32)k1000 * 1000.0f / rpm_max;
+ if( t > 1.0f )
+ break;
+
+ F32 a = gauge_angle( t );
+ tick( cx, cy, a, r * 0.85f, r - 2.0f, r * 0.020f, C_TICK );
+ F32 lx = cx + cosf( a ) * r * 0.74f;
+ F32 ly = cy - sinf( a ) * r * 0.74f;
+ if( k1000 < 10 )
+ glyph_c( lx, ly, r * 0.09f, r * 0.15f, k1000, C_LABEL );
+ else
+ number_c( lx, ly, r * 0.075f, r * 0.13f, k1000, C_LABEL );
+ }
+
+ for( I32 rr = 250; rr <= (I32)rpm_max; rr += 250 ) {
+ if( rr % 1000 == 0 )
+ continue;
+ F32 t = (F32)rr / rpm_max;
+ if( t > 1.0f )
+ break;
+ tick( cx, cy, gauge_angle( t ),
+ r * 0.87f, r * 0.93f, r * 0.010f, C_TICKMIN );
+ }
+}
+
+void GAUGE_RENDERER::build_tach_gear_speed(
+ F32 cx, F32 cy, F32 r,
+ F32 rpm, F32 rpm_max,
+ I32 gear, F32 speed,
+ F32 red_ang
+) {
+ F32 now = t_nows();
+ U8 flash = fmodf( now, 0.2f ) > 0.1f;
+
+ I32 g = gear == 0 ? G_R : ( gear >= 11 ? G_N : gear );
+ glyph_c( cx + r * 0.45f, cy - r * 0.11f, r * 0.19f, r * 0.29f,
+ 0, C_LAMPBG );
+
+ U32 gearc = C_GEAR;
+ if( gear != G_N && rpm > rpm_max * red_ang && !flash )
+ gearc = rgba( 255, 0, 0, 255 );
+ else if( gear == 0 && flash )
+ gearc = rgba( 0, 255, 255, 255 );
+
+ glyph_c( cx + r * 0.45f, cy - r * 0.11f, r * 0.19f, r * 0.29f,
+ g, gearc );
+ I32 spx = cx + r * 0.37f;
+ I32 spy = cy + r * 0.24f;
+ for( U32 i = 0; i < 3; ++i )
+ number( spx + i * r * 0.13f * 1.35f, spy, r * 0.13f, r * 0.24f, 0, C_LAMPBG );
+ number_r( spx + r * 0.13f * 1.35f * 2, spy, r * 0.13f, r * 0.24f, (I32)( speed + 0.5f ), C_SPEED );
+}
+
+void GAUGE_RENDERER::build_tach(
+ F32 cx, F32 cy, F32 r, F32 trail,
+ F32 rpm, F32 rpm_max, F32 red_ang, F32 rpm_off, F32 peak,
+ I32 gear, F32 speed, U8 have_ext, U8 flash_needle
+) {
+ F32 now = t_nows();
+ U8 flash = fmodf( now, 0.2f ) > 0.1f;
+ if( rpm_max < 100.0f )
+ rpm_max = 8000.0f;
+
+ build_tach_bg( cx, cy, r, rpm_max, red_ang, rpm_off );
+
+ if( have_ext ) {
+ build_tach_gear_speed( cx, cy, r, rpm, rpm_max, gear, speed, red_ang );
+ }
+
+ F32 pt = peak / rpm_max;
+ if( pt > 0.01f && pt <= 1.0f ) {
+ tick( cx, cy, gauge_angle( pt ),
+ r * 0.84f, r * 0.945f, r * 0.016f, C_PEAK );
+ }
+
+ U32 trailc = C_TRAIL;
+ U32 needlec = C_NEEDLE;
+ if( flash_needle ) {
+ if( flash && rpm > rpm_max * red_ang )
+ needlec = trailc = rgba( 255, 0, 0, 255 );
+ else if( !flash && gear == 0 )
+ needlec = trailc = C_LAMP;
+ }
+
+ F32 t = rpm / rpm_max;
+ if( t < 0.0f ) t = 0.0f;
+ if( t > 1.0f ) t = 1.0f;
+ if( fabsf( trail ) > 0.002f ) {
+ F32 t0 = t - trail;
+ if( t0 < 0.0f ) t0 = 0.0f;
+ if( t0 > 1.0f ) t0 = 1.0f;
+ needle_trail( cx, cy, gauge_angle( t0 ), gauge_angle( t ),
+ r * 0.10f, r * 0.82f, trailc );
+ }
+ needle( cx, cy, gauge_angle( t ),
+ r * 0.82f, r * 0.16f, r * 0.05f, needlec );
+ disc( cx, cy, r * 0.065f, C_HUB );
+ disc( cx, cy, r * 0.026f, C_HUBDOT );
+}
+
+
+void GAUGE_RENDERER::build_boost_bg( F32 cx, F32 cy, F32 r, F32 range ) {
+ F32 span = range + 1.0f;
+
+ disc_grad( cx, cy, r * 1.12f, C_HALO_IN, C_HALO_OUT );
+ disc_grad( cx, cy, r, C_FACE_MID, C_FACE_EDGE );
+ arc( cx, cy, r - 2.0f, r, 0.0f, 2.0f * (F32)M_PI, C_RIM );
+
+ arc(
+ cx, cy, r * 0.80f, r * 0.92f,
+ gauge_angle( 0.0f ), gauge_angle( 0.10f / span ),
+ C_REDLINE
+ );
+ arc(
+ cx, cy, r * 0.80f, r * 0.92f,
+ gauge_angle( ( span - 0.25f ) / span ), gauge_angle( 1.0f ),
+ C_REDLINE
+ );
+ arc(
+ cx, cy, r * 0.86f, r * 0.90f,
+ gauge_angle( 0.10f / span ), gauge_angle( 1.0f / span ),
+ C_VACUUM
+ );
+
+ for( I32 q = 0; q <= (I32)( span / 0.25f + 0.5f ); q++ ) {
+ F32 v = -1.0f + 0.25f * (F32)q;
+ F32 t = ( v + 1.0f ) / span;
+ if( t > 1.001f )
+ break;
+
+ U8 whole = fabsf( v - roundf( v ) ) < 0.01f;
+ U8 zero = fabsf( v ) < 0.01f;
+ tick( cx, cy, gauge_angle( t ),
+ whole ? r * 0.74f : r * 0.85f, r - 2.0f,
+ zero ? r * 0.05f : ( whole ? r * 0.03f : r * 0.018f ),
+ zero ? C_ZERO : ( whole ? C_TICK : C_TICKMIN )
+ );
+
+ if( whole ) {
+ F32 a = gauge_angle( t );
+ F32 lx = cx + cosf( a ) * r * 0.58f;
+ F32 ly = cy - sinf( a ) * r * 0.58f;
+ I32 av = (I32)fabsf( roundf( v ) );
+ if( v < -0.01f ) {
+ glyph_c( lx - r * 0.09f, ly, r * 0.09f, r * 0.16f, G_DASH, C_LABEL );
+ glyph_c( lx + r * 0.05f, ly, r * 0.09f, r * 0.16f, av, C_LABEL );
+ } else {
+ glyph_c( lx, ly, r * 0.10f, r * 0.17f, av, C_LABEL );
+ }
+ }
+ }
+}
+
+void GAUGE_RENDERER::build_boost( F32 cx, F32 cy, F32 r, F32 bar, F32 range, F32 trail ) {
+ build_boost_bg( cx, cy, r, range );
+
+ F32 span = range + 1.0f;
+ F32 t = ( bar + 1.0f ) / span;
+ if( fabsf( trail ) > 0.002f ) {
+ F32 t0 = t - trail;
+ if( t0 < 0.0f ) t0 = 0.0f;
+ if( t0 > 1.0f ) t0 = 1.0f;
+ needle_trail( cx, cy, gauge_angle( t0 ), gauge_angle( t ), r * 0.12f, r * 0.76f, C_TRAIL );
+ }
+ needle( cx, cy, gauge_angle( t ), r * 0.78f, r * 0.16f, r * 0.06f, C_NEEDLE );
+ disc( cx, cy, r * 0.075f, C_HUB );
+ disc( cx, cy, r * 0.030f, C_HUBDOT );
+}
+
+void GAUGE_RENDERER::build_lamps(
+ F32 cx, F32 cy, F32 r, F32 h,
+ U8 hb, U8 abs_on, U8 tcs_on, U8 abs_flash, U8 tcs_flash
+) {
+ const I32 ABS[3] = { G_A, G_B, G_S };
+ const I32 TCS[3] = { G_T, G_C, G_S };
+
+ F32 hbx = cx + r * 0.48f;
+ F32 hby = cy + r * 0.53f;
+ U32 col = !hb ? C_LAMPBG : rgba( 255, 0, 0, 255 );
+ arc( hbx, hby, r * 0.1f, r * 0.117f, 0, M_PI * 2.f, col );
+ line( hbx, hby - r * 0.075, hbx, hby + r * 0.04f, r * 0.03, col );
+ line( hbx, hby + r * 0.05, hbx, hby + r * 0.085f, r * 0.03, col );
+
+ if( abs_on )
+ lamp( cx + h * 1.6f, cy + r * 0.35f, h, ABS, C_LAMPBG );
+ if( abs_flash )
+ lamp( cx + h * 1.6f, cy + r * 0.35f, h, ABS, C_LAMP );
+ if( tcs_on )
+ lamp( cx + h * 1.6f, cy + r * 0.35f + h * 1.1f, h, TCS, C_LAMPBG );
+ if( tcs_flash )
+ lamp( cx + h * 1.6f, cy + r * 0.35f + h * 1.1f, h, TCS, C_LAMP );
+}
+
+U32 get_tire_color( F32 temp ) {
+ U32 a = 200;
+ temp -= 32.f;
+ temp /= 1.8f;
+ if( temp < 30.f )
+ return rgba( 0, 0, 255, a );
+ if( temp < 45.f )
+ return rgba( 0, 255, 255, a );
+ if( temp < 75.f )
+ return rgba( 0, 255, 0, a );
+ if( temp < 90.f )
+ return rgba( 0, 170, 0, a );
+ if( temp < 120.f )
+ return rgba( 255, 170, 0, a );
+ if( temp < 150.f )
+ return rgba( 255, 100, 0, a );
+ return rgba( 255, 0, 0, a );
+}
+
+void GAUGE_RENDERER::build_tires( F32 cx, F32 cy, F32 h, F32 fl, F32 fr, F32 rl, F32 rr ) {
+ const F32 tire_w = h * 0.023f;
+ const F32 tire_h = h * 0.037f;
+ const F32 rect_w = tire_w * 0.53f;
+ const F32 rect_h = tire_h * 0.3f;
+
+ line( cx - tire_w, cy - tire_h, cx + tire_w, cy - tire_h, 2, C_TICK );
+ line( cx - tire_w, cy + tire_h, cx + tire_w, cy + tire_h, 2, C_TICK );
+ line( cx, cy - tire_h, cx, cy + tire_h, 2, C_TICK );
+
+ // fl
+ rect( cx - tire_w - rect_w, cy - tire_h - rect_h, rect_w, rect_h * 2.f, get_tire_color( fl ) );
+ line( cx - tire_w, cy - tire_h - rect_h, cx - tire_w, cy - tire_h + rect_h, 2, C_TICK );
+ line( cx - tire_w, cy - tire_h - rect_h, cx - tire_w - rect_w, cy - tire_h - rect_h, 2, C_TICK );
+ line( cx - tire_w, cy - tire_h + rect_h, cx - tire_w - rect_w, cy - tire_h + rect_h, 2, C_TICK );
+ line( cx - tire_w - rect_w, cy - tire_h - rect_h, cx - tire_w - rect_w, cy - tire_h + rect_h, 2, C_TICK );
+
+ // fr
+ rect( cx + tire_w, cy - tire_h - rect_h, rect_w, rect_h * 2.f, get_tire_color( fr ) );
+ line( cx + tire_w, cy - tire_h - rect_h, cx + tire_w, cy - tire_h + rect_h, 2, C_TICK );
+ line( cx + tire_w, cy - tire_h - rect_h, cx + tire_w + rect_w, cy - tire_h - rect_h, 2, C_TICK );
+ line( cx + tire_w, cy - tire_h + rect_h, cx + tire_w + rect_w, cy - tire_h + rect_h, 2, C_TICK );
+ line( cx + tire_w + rect_w, cy - tire_h - rect_h, cx + tire_w + rect_w, cy - tire_h + rect_h, 2, C_TICK );
+
+ // rl
+ rect( cx - tire_w - rect_w, cy + tire_h - rect_h, rect_w, rect_h * 2.f, get_tire_color( rl ) );
+ line( cx - tire_w, cy + tire_h - rect_h, cx - tire_w, cy + tire_h + rect_h, 2, C_TICK );
+ line( cx - tire_w, cy + tire_h - rect_h, cx - tire_w - rect_w, cy + tire_h - rect_h, 2, C_TICK );
+ line( cx - tire_w, cy + tire_h + rect_h, cx - tire_w - rect_w, cy + tire_h + rect_h, 2, C_TICK );
+ line( cx - tire_w - rect_w, cy + tire_h - rect_h, cx - tire_w - rect_w, cy + tire_h + rect_h, 2, C_TICK );
+
+ // rr
+ rect( cx + tire_w, cy + tire_h - rect_h, rect_w, rect_h * 2.f, get_tire_color( rr ) );
+ line( cx + tire_w, cy + tire_h - rect_h, cx + tire_w, cy + tire_h + rect_h, 2, C_TICK );
+ line( cx + tire_w, cy + tire_h - rect_h, cx + tire_w + rect_w, cy + tire_h - rect_h, 2, C_TICK );
+ line( cx + tire_w, cy + tire_h + rect_h, cx + tire_w + rect_w, cy + tire_h + rect_h, 2, C_TICK );
+ line( cx + tire_w + rect_w, cy + tire_h - rect_h, cx + tire_w + rect_w, cy + tire_h + rect_h, 2, C_TICK );
+}
diff --git a/src/gauges.h b/src/gauges.h
new file mode 100644
index 0000000..9889690
--- /dev/null
+++ b/src/gauges.h
@@ -0,0 +1,99 @@
+#pragma once
+#include "util.h"
+
+#define MAX_VERTS 16384
+
+struct VERTEX {
+ F32 x, y;
+ U32 col;
+};
+
+inline constexpr U32 rgba( U32 r, U32 g, U32 b, U32 a ) {
+ return r | ( g << 8 ) | ( b << 16 ) | ( a << 24 );
+}
+
+const U32 C_HALO_IN = rgba( 0, 0, 0, 10 );
+const U32 C_HALO_OUT = rgba( 0, 0, 0, 0 );
+const U32 C_FACE_MID = rgba( 96, 100, 110, 155 );
+const U32 C_FACE_EDGE = rgba( 24, 26, 32, 20 );
+const U32 C_SWIRL = rgba( 165, 170, 180, 60 );
+const U32 C_RIM = rgba( 255, 255, 255, 205 );
+const U32 C_TICK = rgba( 235, 240, 255, 180 );
+const U32 C_TICKMIN = rgba( 235, 240, 255, 120 );
+const U32 C_LABEL = rgba( 245, 247, 252, 235 );
+const U32 C_REDLINE = rgba( 210, 40, 40, 230 );
+const U32 C_REDLINE2 = rgba( 255, 70, 70, 230 );
+const U32 C_NEEDLE = rgba( 225, 228, 236, 250 );
+const U32 C_TRAIL = rgba( 225, 228, 236, 130 );
+const U32 C_HUB = rgba( 70, 74, 84, 255 );
+const U32 C_HUBDOT = rgba( 210, 214, 222, 255 );
+const U32 C_GEAR = rgba( 245, 247, 252, 245 );
+const U32 C_SPEED = rgba( 245, 247, 252, 245 );
+const U32 C_PEAK = rgba( 255, 255, 255, 210 );
+const U32 C_VACUUM = rgba( 160, 170, 190, 110 );
+const U32 C_ZERO = rgba( 255, 255, 255, 235 );
+const U32 C_LAMP = rgba( 0, 255, 255, 250 );
+const U32 C_LAMPBG = rgba( 20, 20, 20, 130 );
+
+#define ZONE_RED 0.86f
+
+// 7-seg glyph indices; 0-9 are the digits
+enum GLYPH {
+ G_R = 10, G_N, G_DASH, G_A, G_B, G_S, G_T, G_C,
+ G_COUNT,
+};
+
+F32 gauge_angle( F32 t );
+
+class GAUGE_RENDERER {
+public:
+ VERTEX verts[MAX_VERTS];
+ I32 nverts;
+ F32 fade;
+
+ GAUGE_RENDERER();
+
+ void clear();
+ void set_fade( F32 f );
+ U32 faded( U32 c ) const;
+
+ void rect( F32 x, F32 y, F32 w, F32 h, U32 c );
+ void line( F32 x0, F32 y0, F32 x1, F32 y1, F32 t, U32 c );
+ void arc( F32 cx, F32 cy, F32 r0, F32 r1, F32 a0, F32 a1, U32 c );
+ void arc_grad( F32 cx, F32 cy, F32 r0, F32 r1, F32 a0, F32 a1, U32 c_in, U32 c_out );
+ void disc( F32 cx, F32 cy, F32 r, U32 c );
+ void disc_grad( F32 cx, F32 cy, F32 r, U32 c_in, U32 c_out );
+ void tick( F32 cx, F32 cy, F32 a, F32 rin, F32 rout, F32 t, U32 c );
+ void needle( F32 cx, F32 cy, F32 a, F32 len, F32 back, F32 w, U32 c );
+ void needle_trail( F32 cx, F32 cy, F32 a0, F32 a1, F32 r0, F32 r1, U32 c );
+
+ // 7-seg text
+ void glyph( F32 x, F32 y, F32 dw, F32 dh, I32 g, U32 c );
+ void glyph_c( F32 cx, F32 cy, F32 dw, F32 dh, I32 g, U32 c );
+ void number( F32 x, F32 y, F32 dw, F32 dh, I32 val, U32 c );
+ void number_c( F32 cx, F32 cy, F32 dw, F32 dh, I32 val, U32 c );
+ void number_r( F32 x, F32 y, F32 dw, F32 dh, I32 val, U32 c );
+ void lamp( F32 cx, F32 cy, F32 h, const I32 g[3], U32 c );
+
+ void build_tach_bg(
+ F32 cx, F32 cy, F32 r,
+ F32 rpm_max, F32 red_ang, F32 rpm_off
+ );
+ void build_tach_gear_speed(
+ F32 cx, F32 cy, F32 r,
+ F32 rpm, F32 rpm_max,
+ I32 gear, F32 speed,
+ F32 red_ang
+ );
+ void build_tach(
+ F32 cx, F32 cy, F32 r, F32 trail,
+ F32 rpm, F32 rpm_max, F32 red_ang, F32 rpm_off, F32 peak,
+ I32 gear, F32 speed, U8 have_ext, U8 flash_needle
+ );
+
+ void build_boost_bg( F32 cx, F32 cy, F32 r, F32 range );
+ void build_boost( F32 cx, F32 cy, F32 r, F32 bar, F32 range, F32 trail );
+ void build_lamps( F32 cx, F32 cy, F32 r, F32 h, U8 hb, U8 abs_on, U8 tcs_on, U8 abs_flash, U8 tcs_flash );
+
+ void build_tires( F32 cx, F32 cy, F32 h, F32 fl, F32 fr, F32 rl, F32 rr );
+};
diff --git a/src/hooks.cpp b/src/hooks.cpp
new file mode 100644
index 0000000..25883b3
--- /dev/null
+++ b/src/hooks.cpp
@@ -0,0 +1,275 @@
+#include <vulkan/vulkan.h>
+#include <vulkan/vk_layer.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <new>
+
+#include "overlay.h"
+#include "vulkan.h"
+
+#define FH_EXPORT __attribute__(( visibility( "default" ) ))
+#define LAYER_NAME "VK_LAYER_fhvkov_overlay"
+
+VkLayerInstanceCreateInfo* find_instance_link( const VkInstanceCreateInfo* ci ) {
+ VkLayerInstanceCreateInfo* li = (VkLayerInstanceCreateInfo*)ci->pNext;
+ while( li && !(li->sType == VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO && li->function == VK_LAYER_LINK_INFO) )
+ li = (VkLayerInstanceCreateInfo*)li->pNext;
+ return li;
+}
+
+VkLayerDeviceCreateInfo* find_device_link( const VkDeviceCreateInfo* ci ) {
+ VkLayerDeviceCreateInfo* li = (VkLayerDeviceCreateInfo*)ci->pNext;
+ while( li && !(li->sType == VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO && li->function == VK_LAYER_LINK_INFO) )
+ li = (VkLayerDeviceCreateInfo*)li->pNext;
+ return li;
+}
+
+VKAPI_ATTR VkResult VKAPI_CALL hook_create_instance(
+ const VkInstanceCreateInfo* ci,
+ const VkAllocationCallbacks* ac,
+ VkInstance* out
+) {
+ VkLayerInstanceCreateInfo* li = find_instance_link( ci );
+ if( !li )
+ return VK_ERROR_INITIALIZATION_FAILED;
+
+ PFN_vkGetInstanceProcAddr gipa = li->u.pLayerInfo->pfnNextGetInstanceProcAddr;
+ li->u.pLayerInfo = li->u.pLayerInfo->pNext;
+
+ PFN_vkCreateInstance create = (PFN_vkCreateInstance)gipa( 0, "vkCreateInstance" );
+ if( !create )
+ return VK_ERROR_INITIALIZATION_FAILED;
+
+ VkResult r = create( ci, ac, out );
+ if( r != VK_SUCCESS )
+ return r;
+
+ vk_instance_register(* out, gipa );
+ return r;
+}
+
+VKAPI_ATTR void VKAPI_CALL hook_destroy_instance( VkInstance inst, const VkAllocationCallbacks* ac ) {
+ INSTANCE_DATA* id = vk_find_instance( GET_KEY( inst ) );
+ PFN_vkDestroyInstance down = id ? id->destroy_instance : 0;
+ vk_instance_forget( inst );
+ if( down )
+ down( inst, ac );
+}
+
+VKAPI_ATTR VkResult VKAPI_CALL hook_create_device(
+ VkPhysicalDevice phys,
+ const VkDeviceCreateInfo* ci,
+ const VkAllocationCallbacks* ac,
+ VkDevice* out
+) {
+ VkLayerDeviceCreateInfo* li = find_device_link( ci );
+ if( !li )
+ return VK_ERROR_INITIALIZATION_FAILED;
+
+ PFN_vkGetInstanceProcAddr gipa = li->u.pLayerInfo->pfnNextGetInstanceProcAddr;
+ PFN_vkGetDeviceProcAddr gdpa = li->u.pLayerInfo->pfnNextGetDeviceProcAddr;
+ li->u.pLayerInfo = li->u.pLayerInfo->pNext;
+
+ PFN_vkCreateDevice create = (PFN_vkCreateDevice)gipa( 0, "vkCreateDevice" );
+ if( !create )
+ return VK_ERROR_INITIALIZATION_FAILED;
+
+ VkResult r = create( phys, ci, ac, out );
+ if( r != VK_SUCCESS )
+ return r;
+
+ DEVICE_DATA* d = (DEVICE_DATA*)calloc( 1, sizeof(DEVICE_DATA) );
+ OVERLAY_DATA* od = overlay_inst();
+ d->overlay = od;
+ new( &d->udp ) UDP_LISTENER();
+ new( &d->gauge ) GAUGE_RENDERER();
+
+ d->key = GET_KEY(* out );
+ d->dev =* out;
+ d->phys = phys;
+ d->gdpa = gdpa;
+ d->inst = vk_find_instance( GET_KEY( phys ) );
+
+ od->t_prev = t_nows();
+ vk_resolve_device_procs( d,* out, gdpa );
+
+ config_init( &od->cfg );
+ const char* e = getenv( "FHVKOV_PORT" );
+ if( e )
+ od->cfg.port = atoi( e );
+
+ if( !OK( udp_open( &d->udp, od->cfg.port ) ) )
+ dlog( "fhvkov: cannot bind udp/%d (%s), overlay will stay hidden\n", od->cfg.port, strerror(errno) );
+ else
+ printf( "fhvkov: active, udp/%d, config %s\n", od->cfg.port, od->cfg.path );
+
+ vk_device_register( d );
+ return r;
+}
+
+VKAPI_ATTR void VKAPI_CALL hook_destroy_device(
+ VkDevice dev,
+ const VkAllocationCallbacks* ac
+) {
+ DEVICE_DATA* d = vk_find_device( GET_KEY( dev ) );
+ if( !d )
+ return;
+
+ for( I32 i = 0; i < MAX_SWAPS; i++ ) {
+ if( d->swaps[i] ) {
+ vk_swap_destroy( d, d->swaps[i] );
+ d->swaps[i] = 0;
+ }
+ }
+ vk_device_unregister( d );
+
+ PFN_vkDestroyDevice down = d->destroy_device;
+ d->udp.~UDP_LISTENER();
+ free( d );
+ down( dev, ac );
+}
+
+VKAPI_ATTR void VKAPI_CALL hook_get_device_queue(
+ VkDevice dev,
+ U32 family,
+ U32 index,
+ VkQueue* q
+) {
+ DEVICE_DATA* d = vk_find_device( GET_KEY( dev ) );
+ d->get_device_queue( dev, family, index, q );
+ vk_device_add_queue( d,* q, family );
+}
+
+VKAPI_ATTR void VKAPI_CALL hook_get_device_queue2(
+ VkDevice dev,
+ const VkDeviceQueueInfo2* qi,
+ VkQueue* q
+) {
+ DEVICE_DATA* d = vk_find_device( GET_KEY( dev ) );
+ d->get_device_queue2( dev, qi, q );
+ vk_device_add_queue( d,* q, qi->queueFamilyIndex );
+}
+
+VKAPI_ATTR VkResult VKAPI_CALL hook_create_swapchain(
+ VkDevice dev,
+ const VkSwapchainCreateInfoKHR* ci,
+ const VkAllocationCallbacks* ac,
+ VkSwapchainKHR* out
+) {
+ DEVICE_DATA* d = vk_find_device( GET_KEY( dev ) );
+ if( !d )
+ return VK_ERROR_INITIALIZATION_FAILED;
+
+ VkSwapchainCreateInfoKHR ci2 =* ci;
+ ci2.imageUsage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+
+ VkResult r = d->create_swapchain( dev, &ci2, ac, out );
+ if( r != VK_SUCCESS )
+ return r;
+
+ vk_swap_create( d,* out, ci );
+ return r;
+}
+
+VKAPI_ATTR void VKAPI_CALL hook_destroy_swapchain(
+ VkDevice dev,
+ VkSwapchainKHR sc,
+ const VkAllocationCallbacks* ac
+) {
+ DEVICE_DATA* d = vk_find_device( GET_KEY( dev ) );
+ if( !d )
+ return;
+
+ vk_swap_forget( d, sc );
+ d->destroy_swapchain( dev, sc, ac );
+}
+
+VKAPI_ATTR VkResult VKAPI_CALL hook_queue_present(
+ VkQueue queue,
+ const VkPresentInfoKHR* pi
+) {
+ DEVICE_DATA* d = vk_find_device( GET_KEY( queue ) );
+ if( !d )
+ return VK_ERROR_DEVICE_LOST;
+ OVERLAY_DATA* od = d->overlay;
+
+ udp_parse( &d->udp, &od->telem );
+ if( ++od->frame % 60 == 0 ) {
+ if( config_poll( &od->cfg ) )
+ od->valid_redline = 0.86;
+ }
+
+ F64 now = t_nows();
+ F64 dt = now - od->t_prev;
+ od->t_prev = now;
+ if( dt < 0.0 ) dt = 0.0;
+ if( dt > 0.1 ) dt = 0.1;
+ overlay_update( od, now, dt );
+
+ if( !overlay_visible( od, now ) || pi->swapchainCount != 1 )
+ return d->queue_present( queue, pi );
+
+ pthread_mutex_lock( &g_lock );
+ SWAP_DATA* s = vk_find_swap( d, pi->pSwapchains[0] );
+ bool ok = s && !s->broken;
+ if( ok && !s->frames_ready && !OK( vk_swap_setup_frames( d, s, queue ) ) ) {
+ s->broken = true;
+ ok = false;
+ }
+ pthread_mutex_unlock( &g_lock );
+
+ if( !ok )
+ return d->queue_present( queue, pi );
+ return vk_present( d, s, queue, pi );
+}
+
+#define HOOK( vk_name, fn_name ) \
+ if( !strcmp( name, #vk_name ) ) \
+ return (PFN_vkVoidFunction)fn_name
+
+PFN_vkVoidFunction hooked( const char* name ) {
+ HOOK( vkCreateInstance, hook_create_instance );
+ HOOK( vkDestroyInstance, hook_destroy_instance );
+ HOOK( vkCreateDevice, hook_create_device );
+ HOOK( vkDestroyDevice, hook_destroy_device );
+ HOOK( vkGetDeviceQueue, hook_get_device_queue );
+ HOOK( vkGetDeviceQueue2, hook_get_device_queue2 );
+ HOOK( vkCreateSwapchainKHR, hook_create_swapchain );
+ HOOK( vkDestroySwapchainKHR, hook_destroy_swapchain );
+ HOOK( vkQueuePresentKHR, hook_queue_present );
+ HOOK( vkGetInstanceProcAddr, vkGetInstanceProcAddr );
+ HOOK( vkGetDeviceProcAddr, vkGetDeviceProcAddr );
+ return 0;
+}
+
+extern "C" {
+ FH_EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL
+ vkGetDeviceProcAddr( VkDevice dev, const char* name );
+
+ FH_EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL
+ vkGetInstanceProcAddr( VkInstance inst, const char* name );
+
+
+ FH_EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL
+ vkGetDeviceProcAddr( VkDevice dev, const char* name ) {
+ PFN_vkVoidFunction f = hooked( name );
+ if( f )
+ return f;
+
+ DEVICE_DATA* d = dev ? vk_find_device( GET_KEY( dev ) ) : 0;
+ return d ? d->gdpa( dev, name ) : 0;
+ }
+
+ FH_EXPORT VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL
+ vkGetInstanceProcAddr( VkInstance inst, const char* name ) {
+ PFN_vkVoidFunction f = hooked( name );
+ if( f )
+ return f;
+
+ INSTANCE_DATA* id = inst ? vk_find_instance( GET_KEY( inst ) ) : 0;
+ return id ? id->gipa( inst, name ) : 0;
+ }
+}
+
diff --git a/src/overlay.cpp b/src/overlay.cpp
new file mode 100644
index 0000000..75a9194
--- /dev/null
+++ b/src/overlay.cpp
@@ -0,0 +1,269 @@
+#include "overlay.h"
+#include "gauges.h"
+
+const F32 TRAIL_TACH_MAX = 0.14f;
+const F32 TRAIL_BOOST_MAX = 0.16f;
+
+OVERLAY_DATA* overlay_inst() {
+ static OVERLAY_DATA d;
+ return &d;
+}
+
+void overlay_handle_teleport( OVERLAY_DATA* d ) {
+ TELEMETRY *t = &d->telem;
+
+ U8 iszero = fabsf( t->pos_x ) < 0.01f && fabsf( t->pos_y ) < 0.01f && fabsf( t->pos_z ) < 0.01f;
+ U8 iszero_last = fabsf( d->last_pos_x ) < 0.01f && fabsf( d->last_pos_y ) < 0.01f && fabsf( d->last_pos_z ) < 0.01f;
+ F32 dist = vec_dist( t->pos_x, t->pos_y, t->pos_z, d->last_pos_x, d->last_pos_y, d->last_pos_z );
+ U8 unteleported = 0;
+
+ if( d->teleported && !iszero ) {
+ F32 tdist = vec_dist( t->pos_x, t->pos_y, t->pos_z, d->tele_pos_x, d->tele_pos_y, d->tele_pos_z );
+ if( tdist > 1.f ) {
+ d->teleported = 0;
+ unteleported = 1;
+ }
+ }
+
+ U8 iszero_vel = fabsf( t->vel_z ) < 3.f;
+ if( !iszero_vel ) {
+ if( d->teleported )
+ unteleported = 1;
+ d->teleported = 0;
+ }
+
+ // teleported
+ if( !unteleported && !d->teleported && !iszero && !iszero_last && dist > 10.f ) {
+ d->teleported = 1;
+ d->tele_pos_x = t->pos_x;
+ d->tele_pos_y = t->pos_y;
+ d->tele_pos_z = t->pos_z;
+ }
+
+ d->last_brake = t->brake;
+ d->last_hb = t->handbrake;
+ d->last_pos_x = t->pos_x;
+ d->last_pos_y = t->pos_y;
+ d->last_pos_z = t->pos_z;
+
+ if( !d->was_race_on && t->race_on ) {
+ d->teleported = 0;
+ }
+}
+
+void overlay_handle_redline( OVERLAY_DATA* d ) {
+ TELEMETRY* t = &d->telem;
+
+ if( !d->cfg.autoscale_rpm || d->valid_redline < 0.86f ) {
+ d->valid_redline = 0.86f;
+ return;
+ }
+
+ if( t->gear != d->last_gear ) {
+ if( t->gear != 0 && t->gear != 11 && t->gear > d->last_valid_gear )
+ d->valid_shift = 1;
+ else
+ d->valid_shift = 0;
+ }
+
+ if( d->valid_shift ) {
+ F64 rpmr = (F64)(t->rpm) / t->rpm_max;
+ if( rpmr >= 0.99f )
+ d->valid_redline = 0.86f;
+ rpmr = (F64)(t->rpm - (I32)d->cfg.rpm_off) / t->rpm_max;
+ if( rpmr > d->valid_redline ) {
+ d->valid_redline = rpmr;
+ }
+ }
+
+ d->last_gear = t->gear;
+ if( t->gear != 11 )
+ d->last_valid_gear = t->gear;
+}
+
+void overlay_handle_car_changed( OVERLAY_DATA* d ) {
+ TELEMETRY* t = &d->telem;
+ if( !t->extended )
+ return;
+
+ U8 car_changed = ( t->car_ordinal != d->car_ordinal || fabsf( t->rpm_max - d->car_rpm_max ) > 1.0f );
+ if( car_changed ) {
+ d->car_ordinal = t->car_ordinal;
+ d->car_rpm_max = t->rpm_max;
+ d->peak_boost_bar = 0.0;
+ d->boost_since = 0.0;
+ d->valid_redline = 0.86;
+ }
+}
+
+void overlay_update_rpm_boost( OVERLAY_DATA* d, U8 live, F64 dt, F64 now ) {
+ TELEMETRY* t = &d->telem;
+
+ F64 k = dt * 20.0;
+ if( k > 1.0 )
+ k = 1.0;
+ d->disp_rpm += ( (F64)t->rpm - d->disp_rpm ) * k;
+ if( d->disp_rpm > d->peak_rpm )
+ d->peak_rpm = d->disp_rpm;
+ else
+ d->peak_rpm -= 2500.0 * dt;
+ if( d->peak_rpm < 0.0 )
+ d->peak_rpm = 0.0;
+
+ F64 bar = (F64)t->boost_psi * PSI_TO_BAR;
+ F64 kb = dt * 25.0;
+ if( kb > 1.0 )
+ kb = 1.0;
+ d->disp_boost_bar += ( bar - d->disp_boost_bar ) * kb;
+ if( live && bar > d->peak_boost_bar )
+ d->peak_boost_bar = bar;
+
+ d->was_race_on = t->race_on;
+ F32 kmh = t->speed_ms * 3.6f;
+ d->lc_armed = 0;
+ if( t->handbrake > 30 && t->accel > 30 && kmh < 1.0f )
+ d->lc_armed = 1;
+
+ F64 idt = dt > 1e-4 ? 1.0 / dt : 0.0;
+ F64 sm = dt * 15.0;
+ if( sm > 1.0 )
+ sm = 1.0;
+ d->rpm_vel += ( ( d->disp_rpm - d->prev_disp_rpm ) * idt - d->rpm_vel ) * sm;
+ d->bar_vel += ( ( d->disp_boost_bar - d->prev_disp_bar ) * idt - d->bar_vel ) * sm;
+ d->prev_disp_rpm = d->disp_rpm;
+ d->prev_disp_bar = d->disp_boost_bar;
+
+ if( d->peak_boost_bar > 0.10 && d->boost_since <= 0.0 )
+ d->boost_since = now;
+}
+
+void overlay_handle_assists( OVERLAY_DATA* d, F64 now ) {
+ TELEMETRY* t = &d->telem;
+
+ I32 w0 = 0, w1 = 3; // AWD
+ if( t->drivetrain == 0 ) { w0 = 0; w1 = 1; } // FWD
+ else if( t->drivetrain == 1 ) { w0 = 2; w1 = 3; } // RWD
+
+ if( t->gear == 11 )
+ d->tcs_until = 0;
+ else if( t->accel > 30 && t->rpm < t->rpm_max - t->rpm_max * 0.1f && t->rpm > d->last_rpm ) {
+ for( I32 i = w0; i <= w1; i++ ) {
+ if( t->combined_slip[i] > 1.35f && t->power * 1.02f < d->last_power && t->accel >= d->last_acc )
+ d->tcs_until = now + 0.05;
+ }
+ }
+
+ if( t->brake > 30 ) {
+ for( I32 i = 0; i < 4; i++ ) {
+ if( t->slip_ratio[i] < -1.f )
+ d->abs_until = now + 0.02;
+ }
+ }
+}
+
+void overlay_update( OVERLAY_DATA* d, F64 now, F64 dt ) {
+ TELEMETRY* t = &d->telem;
+
+ overlay_handle_teleport( d );
+
+ U8 live = overlay_live( d, now );
+ if( live && !d->was_live )
+ d->live_since = now;
+ d->was_live = live;
+
+ overlay_handle_car_changed( d );
+ overlay_handle_redline( d );
+ overlay_update_rpm_boost( d, live, dt, now );
+
+ if( !t->extended )
+ return;
+
+ overlay_handle_assists( d, now );
+
+ d->prev_race_on = t->race_on;
+ d->last_power = t->power;
+ d->last_acc = t->accel;
+ d->last_rpm = t->rpm;
+}
+
+U8 overlay_visible( OVERLAY_DATA* d, F64 now ) {
+ return overlay_live( d, now ) && fade_alpha( d->live_since, now ) > 0.0f;
+}
+
+U8 overlay_live( OVERLAY_DATA* d, F64 now ) {
+ U8 live = d->telem.race_on && !d->teleported;
+ return now - d->telem.stamp < 3.0 && ( live || d->cfg.keep );
+}
+
+void overlay_build_cluster( OVERLAY_DATA* d, GAUGE_RENDERER* gauge, F32 w, F32 h, F64 now ) {
+ TELEMETRY* t = &d->telem;
+ CONFIG* c = &d->cfg;
+
+ F32 sc = (F32)c->scale;
+ if( sc < 0.2f || sc > 4.0f )
+ sc = 1.0f;
+ F32 a_main = fade_alpha( d->live_since, now );
+
+ gauge->clear();
+ gauge->set_fade( a_main );
+
+ F32 blur = (F32)c->blur;
+ if( blur < 0.0f || blur > 0.5f )
+ blur = 0.0f;
+ F32 rpm_max_safe = t->rpm_max < 100.0f ? 8000.0f : t->rpm_max;
+ F32 trail_tach = blur > 0.0f ? (F32)( d->rpm_vel * blur ) / rpm_max_safe : 0.0f;
+ if( trail_tach > TRAIL_TACH_MAX ) trail_tach = TRAIL_TACH_MAX;
+ if( trail_tach < -TRAIL_TACH_MAX ) trail_tach = -TRAIL_TACH_MAX;
+
+ F32 speed = t->speed_ms * ( c->units ? 2.23694f : 3.6f );
+ F32 kmh = t->speed_ms * 3.6f;
+ F32 rpmoff = c->autoscale_rpm ? (F32)(c->rpm_off - 100.f) / rpm_max_safe : 0.f;
+ gauge->build_tach(
+ (F32)( c->tach_x * w ), (F32)( c->tach_y * h ), (F32)( c->tach_r * h ) * sc, trail_tach,
+ (F32)d->disp_rpm, t->rpm_max, d->valid_redline, rpmoff, (F32)d->peak_rpm,
+ t->gear, speed, t->extended, c->flash_needle
+ );
+
+ F32 a_boost = a_main * fade_alpha( d->boost_since, now );
+ if( c->show_boost && t->extended && a_boost > 0.0f ) {
+ F32 need = (F32)d->peak_boost_bar + 0.25f;
+ F32 range = ceilf( need / 0.25f ) * 0.25f;
+ if( range < 0.5f ) range = 0.5f;
+
+ F32 shown = (F32)d->disp_boost_bar;
+ if( shown < -1.0f ) shown = -1.0f;
+ if( shown > range ) shown = range;
+
+ F32 span = range + 1.0f;
+ F32 trail_boost = blur > 0.0f ? (F32)( d->bar_vel * blur ) / span : 0.0f;
+ if( trail_boost > TRAIL_BOOST_MAX ) trail_boost = TRAIL_BOOST_MAX;
+ if( trail_boost < -TRAIL_BOOST_MAX ) trail_boost = -TRAIL_BOOST_MAX;
+ gauge->set_fade( a_boost );
+ gauge->build_boost(
+ (F32)( c->boost_x * w ), (F32)( c->boost_y * h ), (F32)( c->boost_r * h ) * sc,
+ shown, range, trail_boost
+ );
+ gauge->set_fade( a_main );
+ }
+
+ if( t->extended && ( c->show_abs || c->show_tcs || c->show_lc ) ) {
+ U8 abs_show = c->show_abs;
+ U8 tcs_show = c->show_tcs;
+ U8 lc_show = c->show_lc;
+ U8 abs_lit = abs_show && now < d->abs_until;
+ U8 tcs_lit = ( tcs_show && now < d->tcs_until );
+ U8 lc_lit = (now < d->tcs_until && kmh < 50);
+ U8 lc_flash = c->show_lc && ( ( d->lc_armed && fmod( now, 0.30 ) < 0.20 ) || lc_lit );
+ gauge->build_lamps(
+ (F32)( c->tach_x * w ), (F32)( c->tach_y * h ),
+ (F32)( c->tach_r * h * sc ), (F32)( c->tach_r * h * sc * 0.1f ),
+ t->handbrake > 0,
+ abs_show, tcs_show || lc_show, abs_lit, tcs_lit || lc_flash
+ );
+ }
+
+ if( t->extended && c->show_tires ) {
+ gauge->build_tires( (F32)( c->tire_x * w ), (F32)( c->tire_y * h ), (F32)( c->tire_sc * h ),
+ d->telem.tire_temp[0], d->telem.tire_temp[1], d->telem.tire_temp[2], d->telem.tire_temp[3] );
+ }
+}
diff --git a/src/overlay.h b/src/overlay.h
new file mode 100644
index 0000000..c690c64
--- /dev/null
+++ b/src/overlay.h
@@ -0,0 +1,46 @@
+#pragma once
+#include "udp.h"
+#include "config.h"
+
+struct OVERLAY_DATA {
+ TELEMETRY telem;
+ CONFIG cfg;
+ F64 disp_rpm, peak_rpm;
+ F64 disp_boost_bar, peak_boost_bar;
+ F64 abs_until, tcs_until;
+ F64 last_power;
+ F64 last_acc;
+ F64 last_rpm;
+ F64 t_prev;
+ F32 last_pos_x, last_pos_y, last_pos_z;
+ F32 tele_pos_x, tele_pos_y, tele_pos_z;
+ F32 vel_x, vel_y, vel_z;
+ U32 teleported;
+ U8 was_race_on;
+ I32 last_brake, last_hb;
+ I32 frame;
+
+ F32 valid_redline;
+ I32 valid_shift;
+ I32 shift_rpm;
+ I32 last_gear;
+ I32 last_valid_gear;
+
+ U8 was_live;
+ F64 live_since;
+ F64 boost_since;
+ I32 car_ordinal;
+ F32 car_rpm_max;
+ I32 prev_race_on;
+ U8 lc_armed;
+
+ F64 prev_disp_rpm, rpm_vel;
+ F64 prev_disp_bar, bar_vel;
+
+};
+
+OVERLAY_DATA* overlay_inst();
+U8 overlay_live( OVERLAY_DATA* d, F64 now );
+void overlay_update( OVERLAY_DATA *d, F64 now, F64 dt );
+U8 overlay_visible( OVERLAY_DATA *d, F64 now );
+void overlay_build_cluster( OVERLAY_DATA* d, class GAUGE_RENDERER* gauge, F32 w, F32 h, F64 now );
diff --git a/src/spv.h b/src/spv.h
new file mode 100644
index 0000000..505423c
--- /dev/null
+++ b/src/spv.h
@@ -0,0 +1,137 @@
+unsigned char gauge_vert_spv[] = {
+ 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x00, 0x08, 0x00,
+ 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30,
+ 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
+ 0x26, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0xc2, 0x01, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x67, 0x6c, 0x5f, 0x50, 0x65, 0x72, 0x56, 0x65,
+ 0x72, 0x74, 0x65, 0x78, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x6c, 0x5f, 0x50,
+ 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x06, 0x00, 0x07, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x67, 0x6c, 0x5f, 0x50,
+ 0x6f, 0x69, 0x6e, 0x74, 0x53, 0x69, 0x7a, 0x65, 0x00, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x07, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x67, 0x6c, 0x5f, 0x43, 0x6c, 0x69, 0x70, 0x44, 0x69, 0x73, 0x74, 0x61,
+ 0x6e, 0x63, 0x65, 0x00, 0x06, 0x00, 0x07, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x67, 0x6c, 0x5f, 0x43, 0x75, 0x6c, 0x6c, 0x44,
+ 0x69, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x00, 0x05, 0x00, 0x03, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00,
+ 0x12, 0x00, 0x00, 0x00, 0x69, 0x6e, 0x5f, 0x70, 0x6f, 0x73, 0x00, 0x00,
+ 0x05, 0x00, 0x03, 0x00, 0x14, 0x00, 0x00, 0x00, 0x50, 0x43, 0x00, 0x00,
+ 0x06, 0x00, 0x05, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x73, 0x63, 0x61, 0x6c, 0x65, 0x00, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00,
+ 0x16, 0x00, 0x00, 0x00, 0x70, 0x63, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x76, 0x5f, 0x63, 0x6f, 0x6c, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x04, 0x00, 0x26, 0x00, 0x00, 0x00, 0x69, 0x6e, 0x5f, 0x63,
+ 0x6f, 0x6c, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x48, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x47, 0x00, 0x04, 0x00, 0x12, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x47, 0x00, 0x04, 0x00, 0x24, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x26, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x2b, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x06, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x3b, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x17, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00,
+ 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x03, 0x00, 0x14, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x04, 0x00, 0x15, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x15, 0x00, 0x00, 0x00,
+ 0x16, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
+ 0x17, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x80, 0x3f, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
+ 0x22, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x3b, 0x00, 0x04, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x25, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00,
+ 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x13, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
+ 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
+ 0x0f, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x19, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
+ 0x19, 0x00, 0x00, 0x00, 0x50, 0x00, 0x05, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x1c, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00,
+ 0x83, 0x00, 0x05, 0x00, 0x10, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00,
+ 0x1a, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x50, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
+ 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
+ 0x1b, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x22, 0x00, 0x00, 0x00,
+ 0x23, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0x3e, 0x00, 0x03, 0x00, 0x23, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
+ 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
+ 0x26, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x24, 0x00, 0x00, 0x00,
+ 0x27, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00
+};
+unsigned int gauge_vert_spv_len = 1200;
+unsigned char gauge_frag_spv[] = {
+ 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x00, 0x08, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30,
+ 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0xc2, 0x01, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x6f, 0x5f, 0x63, 0x6f,
+ 0x6c, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x76, 0x5f, 0x63, 0x6f, 0x6c, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x47, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x16, 0x00, 0x03, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00
+};
+unsigned int gauge_frag_spv_len = 372;
diff --git a/src/typedef.h b/src/typedef.h
new file mode 100644
index 0000000..0bff722
--- /dev/null
+++ b/src/typedef.h
@@ -0,0 +1,64 @@
+#pragma once
+
+#define DEBUG
+#ifdef DEBUG
+#include <stdio.h>
+#endif
+
+enum STAT {
+ STAT_OK,
+ STAT_ERR,
+ STAT_BREAK,
+ STAT_ALREADYEXISTS,
+};
+
+#define OK( x ) ( (STAT)( x ) == STAT_OK )
+
+typedef char I8;
+typedef short I16;
+typedef int I32;
+typedef long long I64;
+
+typedef unsigned char U8;
+typedef unsigned short U16;
+typedef unsigned int U32;
+typedef unsigned long long U64;
+
+typedef unsigned long ULONG;
+
+typedef float F32;
+typedef double F64;
+
+typedef unsigned long PTR;
+
+// lambda macro - captures all by ref
+#define fn( ... ) [&]( __VA_ARGS__ )
+// lambda macro - captures all by copy
+#define cfn( ... ) [=]( __VA_ARGS__ )
+// lambda macro - raw pointer func
+#define pfn( ... ) []( __VA_ARGS__ )
+
+template <typename T>
+struct __defer_t {
+ T f;
+ __defer_t( T f ) : f( f ) {};
+ ~__defer_t() { f(); }
+};
+
+template <typename T>
+__defer_t<T> __defer_func( T f ) {
+ return __defer_t<T>( f );
+}
+
+#define DEFER_1( x, y ) x##y
+#define DEFER_2( x, y ) DEFER_1( x, y )
+#define DEFER_3( x ) DEFER_2( x, __COUNTER__ )
+#define defer( code ) auto DEFER_3( _defer_ ) = __defer_func( fn() { code; } )
+
+#ifdef DEBUG
+#define dlog( ... ) (void)fprintf( stderr, __VA_ARGS__ )
+#define ddef( x ) x
+#else
+#define dlog( ... ) (void)0
+#define ddef( x )
+#endif
diff --git a/src/udp.cpp b/src/udp.cpp
new file mode 100644
index 0000000..f54e60a
--- /dev/null
+++ b/src/udp.cpp
@@ -0,0 +1,106 @@
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+
+#include "udp.h"
+
+UDP_LISTENER::UDP_LISTENER() {
+ fd = -1;
+}
+
+UDP_LISTENER::~UDP_LISTENER() {
+ if( fd >= 0 )
+ close( fd );
+}
+
+STAT udp_open( UDP_LISTENER* udp, I32 port ) {
+ udp->fd = socket( AF_INET, SOCK_DGRAM, 0 );
+ if( udp->fd < 0 )
+ return STAT_ERR;
+
+ I32 one = 1;
+ setsockopt( udp->fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one) );
+
+ struct sockaddr_in a;
+ memset( &a, 0, sizeof( a ) );
+ a.sin_family = AF_INET;
+ a.sin_addr.s_addr = htonl( INADDR_ANY );
+ a.sin_port = htons( (U16)port );
+
+ if( bind( udp->fd, (struct sockaddr*)&a, sizeof(a) ) < 0 ) {
+ close( udp->fd );
+ udp->fd = -1;
+ return STAT_ERR;
+ }
+
+ fcntl( udp->fd, F_SETFL, O_NONBLOCK );
+ return STAT_OK;
+}
+
+void udp_parse( UDP_LISTENER* udp, TELEMETRY* t ) {
+ if( udp->fd < 0 )
+ return;
+
+ U8 buf[2048];
+ for( ;; ) {
+ ssize_t n = recv( udp->fd, buf, sizeof( buf ), 0 );
+ if( n < 0 )
+ break; // EAGAIN
+ if( n < 20 )
+ continue; // not a forza packet
+
+ I32 race;
+ F32 fmax, fidle, fcur;
+ memcpy( &race, buf + 0, 4 );
+ memcpy( &fmax, buf + 8, 4 );
+ memcpy( &fidle, buf + 12, 4 );
+ memcpy( &fcur, buf + 16, 4 );
+
+ if( !( fmax >= 0.0f && fmax < 60000.0f ) )
+ continue;
+ if( !( fcur >= 0.0f && fcur < 60000.0f ) )
+ continue;
+
+ memcpy( &t->vel_x, buf + 28, 4 );
+ memcpy( &t->vel_y, buf + 32, 4 );
+ memcpy( &t->vel_z, buf + 40, 4 );
+
+ t->race_on = race;
+ t->rpm_max = fmax;
+ t->rpm_idle = fidle;
+ t->rpm = fcur;
+ t->extended = n >= 324;
+
+ if( t->extended ) {
+ I32 dt;
+ memcpy( &t->pos_x, buf + 244, 4 );
+ memcpy( &t->pos_y, buf + 248, 4 );
+ memcpy( &t->pos_z, buf + 252, 4 );
+ memcpy( &t->power, buf + 260, 4 );
+ memcpy( &t->speed_ms, buf + 256, 4 );
+ memcpy( &t->boost_psi, buf + 284, 4 );
+ memcpy( &dt, buf + 224, 4 );
+ memcpy( &t->laptime, buf + 304, 4 );
+ memcpy( &t->racetime, buf + 308, 4 );
+ t->lapnum = *(U16*)( &buf[312] );
+ t->racepos = buf[314];
+ t->drivetrain = dt;
+ t->accel = buf[315];
+ t->brake = buf[316];
+ t->clutch = buf[317];
+ t->handbrake = buf[318];
+ t->gear = buf[319];
+ t->norm_driving_line = buf[321];
+ t->norm_ai_brake_diff = buf[322];
+ memcpy( &t->car_ordinal, buf + 212, 4 );
+ for( I32 i = 0; i < 4; i++ ) {
+ memcpy( &t->slip_ratio[i], buf + 84 + 4* i, 4 );
+ memcpy( &t->combined_slip[i], buf + 180 + 4* i, 4 );
+ memcpy( &t->tire_temp[i], buf + 268 + 4* i, 4 );
+ }
+ }
+ t->stamp = t_nows();
+ }
+}
diff --git a/src/udp.h b/src/udp.h
new file mode 100644
index 0000000..05db434
--- /dev/null
+++ b/src/udp.h
@@ -0,0 +1,44 @@
+#pragma once
+#include "util.h"
+
+#define PSI_TO_BAR 0.0689476f
+
+struct TELEMETRY {
+ I32 race_on;
+ F32 rpm;
+ F32 rpm_max;
+ F32 rpm_idle;
+
+ U8 extended; // fields below valid (324-byte packet)
+ F32 speed_ms;
+ F32 pos_x, pos_y, pos_z;
+ F32 vel_x, vel_y, vel_z;
+ F32 power;
+ F32 boost_psi;
+ I32 gear; // 0 = R, 1..10, >= 11 = N
+ I32 accel, brake; // 0..255
+ I32 handbrake, clutch; // 0..255
+ I32 car_ordinal; // unique car id; changes on car swap
+ I32 drivetrain; // 0 FWD, 1 RWD, 2 AWD
+ F32 slip_ratio[4]; // FL FR RL RR
+ F32 combined_slip[4];
+ F32 tire_temp[4];
+ F32 racetime;
+ U32 racepos;
+ U32 lapnum;
+ F32 laptime;
+ I32 norm_driving_line;
+ I32 norm_ai_brake_diff;
+ F64 stamp;
+};
+
+class UDP_LISTENER {
+public:
+ I32 fd;
+
+ UDP_LISTENER();
+ ~UDP_LISTENER();
+};
+
+STAT udp_open( UDP_LISTENER* udp, I32 port );
+void udp_parse( UDP_LISTENER* udp, TELEMETRY* t );
diff --git a/src/util.h b/src/util.h
new file mode 100644
index 0000000..bc06b42
--- /dev/null
+++ b/src/util.h
@@ -0,0 +1,27 @@
+#pragma once
+#include <time.h>
+#include <math.h>
+#include "typedef.h"
+
+inline F64 t_nows() {
+ struct timespec ts;
+ clock_gettime( CLOCK_MONOTONIC, &ts );
+ return (F64)ts.tv_sec + (F64)ts.tv_nsec * 1e-9;
+}
+
+
+inline F32 fade_alpha( F64 since, F64 now ) {
+ if( since <= 0.0 )
+ return 0.0f;
+ F64 a = ( now - since - 0.30 ) / 0.30;
+ if( a < 0.0 ) a = 0.0;
+ if( a > 1.0 ) a = 1.0;
+ return (F32)a;
+}
+
+inline F32 vec_dist( F32 x1, F32 y1, F32 z1, F32 x2, F32 y2, F32 z2 ) {
+ F32 dx = x1 - x2;
+ F32 dy = y1 - y2;
+ F32 dz = z1 - z2;
+ return sqrtf( dx * dx + dy * dy + dz * dz );
+}
diff --git a/src/vulkan.cpp b/src/vulkan.cpp
new file mode 100644
index 0000000..20fa449
--- /dev/null
+++ b/src/vulkan.cpp
@@ -0,0 +1,618 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "vulkan.h"
+#include "gauges.h"
+#include "spv.h"
+
+pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER;
+
+INSTANCE_DATA g_instances[MAX_INSTANCES];
+DEVICE_DATA * g_devices[MAX_DEVICES];
+
+INSTANCE_DATA* vk_find_instance( void* key ) {
+ for( I32 i = 0; i < MAX_INSTANCES; i++ )
+ if( g_instances[i].key == key )
+ return &g_instances[i];
+ return 0;
+}
+
+DEVICE_DATA* vk_find_device( void* key ) {
+ for( I32 i = 0; i < MAX_DEVICES; i++ )
+ if( g_devices[i] && g_devices[i]->key == key )
+ return g_devices[i];
+ return 0;
+}
+
+SWAP_DATA* vk_find_swap( DEVICE_DATA* d, VkSwapchainKHR sc ) {
+ for( I32 i = 0; i < MAX_SWAPS; i++ )
+ if( d->swaps[i] && d->swaps[i]->sc == sc )
+ return d->swaps[i];
+ return 0;
+}
+
+INSTANCE_DATA* vk_instance_register( VkInstance inst, PFN_vkGetInstanceProcAddr gipa ) {
+ pthread_mutex_lock( &g_lock );
+ defer( pthread_mutex_unlock( &g_lock ) );
+
+ for( I32 i = 0; i < MAX_INSTANCES; i++ ) {
+ if( g_instances[i].key )
+ continue;
+
+ INSTANCE_DATA* id = &g_instances[i];
+ id->key = GET_KEY( inst );
+ id->instance = inst;
+ id->gipa = gipa;
+ id->destroy_instance = ( decltype( id->destroy_instance ) )gipa( inst, "vkDestroyInstance" );
+ id->get_memory_properties = ( decltype( id->get_memory_properties ) )gipa( inst, "vkGetPhysicalDeviceMemoryProperties" );
+ id->get_queue_family_properties = ( decltype( id->get_queue_family_properties ) )gipa( inst, "vkGetPhysicalDeviceQueueFamilyProperties" );
+ return id;
+ }
+ return 0;
+}
+
+void vk_instance_forget( VkInstance inst ) {
+ pthread_mutex_lock( &g_lock );
+ defer( pthread_mutex_unlock( &g_lock ) );
+
+ INSTANCE_DATA* id = vk_find_instance( GET_KEY( inst ) );
+ if( id )
+ memset( id, 0, sizeof(* id ) );
+}
+
+void vk_device_register( DEVICE_DATA* d ) {
+ pthread_mutex_lock( &g_lock );
+ defer( pthread_mutex_unlock( &g_lock ) );
+
+ for( I32 i = 0; i < MAX_DEVICES; i++ )
+ if( !g_devices[i] ) {
+ g_devices[i] = d;
+ return;
+ }
+}
+
+void vk_device_unregister( DEVICE_DATA* d ) {
+ pthread_mutex_lock( &g_lock );
+ defer( pthread_mutex_unlock( &g_lock ) );
+
+ for( I32 i = 0; i < MAX_DEVICES; i++ )
+ if( g_devices[i] == d )
+ g_devices[i] = 0;
+}
+
+void vk_device_add_queue( DEVICE_DATA* d, VkQueue q, U32 family ) {
+ pthread_mutex_lock( &g_lock );
+ defer( pthread_mutex_unlock( &g_lock ) );
+
+ if( d->nqueues < MAX_QUEUES ) {
+ d->queues[d->nqueues].q = q;
+ d->queues[d->nqueues].family = family;
+ d->nqueues++;
+ }
+}
+
+#define RESOLVE( field, name ) \
+ d->field = (PFN_##name)gdpa( dev, #name )
+
+void vk_resolve_device_procs( DEVICE_DATA* d, VkDevice dev, PFN_vkGetDeviceProcAddr gdpa ) {
+ RESOLVE( destroy_device, vkDestroyDevice );
+ RESOLVE( get_device_queue, vkGetDeviceQueue );
+ RESOLVE( get_device_queue2, vkGetDeviceQueue2 );
+ RESOLVE( create_swapchain, vkCreateSwapchainKHR );
+ RESOLVE( destroy_swapchain, vkDestroySwapchainKHR );
+ RESOLVE( get_swapchain_images, vkGetSwapchainImagesKHR );
+ RESOLVE( queue_present, vkQueuePresentKHR );
+ RESOLVE( queue_submit, vkQueueSubmit );
+ RESOLVE( device_wait_idle, vkDeviceWaitIdle );
+ RESOLVE( create_image_view, vkCreateImageView );
+ RESOLVE( destroy_image_view, vkDestroyImageView );
+ RESOLVE( create_render_pass, vkCreateRenderPass );
+ RESOLVE( destroy_render_pass, vkDestroyRenderPass );
+ RESOLVE( create_framebuffer, vkCreateFramebuffer );
+ RESOLVE( destroy_framebuffer, vkDestroyFramebuffer );
+ RESOLVE( create_shader_module, vkCreateShaderModule );
+ RESOLVE( destroy_shader_module, vkDestroyShaderModule );
+ RESOLVE( create_pipeline_layout, vkCreatePipelineLayout );
+ RESOLVE( destroy_pipeline_layout, vkDestroyPipelineLayout );
+ RESOLVE( create_graphics_pipelines, vkCreateGraphicsPipelines );
+ RESOLVE( destroy_pipeline, vkDestroyPipeline );
+ RESOLVE( create_command_pool, vkCreateCommandPool );
+ RESOLVE( destroy_command_pool, vkDestroyCommandPool );
+ RESOLVE( allocate_command_buffers, vkAllocateCommandBuffers );
+ RESOLVE( begin_command_buffer, vkBeginCommandBuffer );
+ RESOLVE( end_command_buffer, vkEndCommandBuffer );
+ RESOLVE( cmd_begin_render_pass, vkCmdBeginRenderPass );
+ RESOLVE( cmd_end_render_pass, vkCmdEndRenderPass );
+ RESOLVE( cmd_bind_pipeline, vkCmdBindPipeline );
+ RESOLVE( cmd_bind_vertex_buffers, vkCmdBindVertexBuffers );
+ RESOLVE( cmd_push_constants, vkCmdPushConstants );
+ RESOLVE( cmd_set_viewport, vkCmdSetViewport );
+ RESOLVE( cmd_set_scissor, vkCmdSetScissor );
+ RESOLVE( cmd_draw, vkCmdDraw );
+ RESOLVE( create_semaphore, vkCreateSemaphore );
+ RESOLVE( destroy_semaphore, vkDestroySemaphore );
+ RESOLVE( create_fence, vkCreateFence );
+ RESOLVE( destroy_fence, vkDestroyFence );
+ RESOLVE( wait_for_fences, vkWaitForFences );
+ RESOLVE( reset_fences, vkResetFences );
+ RESOLVE( create_buffer, vkCreateBuffer );
+ RESOLVE( destroy_buffer, vkDestroyBuffer );
+ RESOLVE( get_buffer_memory_requirements, vkGetBufferMemoryRequirements );
+ RESOLVE( allocate_memory, vkAllocateMemory );
+ RESOLVE( free_memory, vkFreeMemory );
+ RESOLVE( bind_buffer_memory, vkBindBufferMemory );
+ RESOLVE( map_memory, vkMapMemory );
+ RESOLVE( unmap_memory, vkUnmapMemory );
+}
+
+
+STAT vk_swap_create_render_pass( DEVICE_DATA* d, SWAP_DATA* s ) {
+ VkAttachmentDescription att = {
+ .format = s->fmt,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
+ .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
+ .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
+ .initialLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+ .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+ };
+ VkAttachmentReference ref = {
+ .attachment = 0,
+ .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ };
+ VkSubpassDescription sub = {
+ .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
+ .colorAttachmentCount = 1,
+ .pColorAttachments = &ref,
+ };
+ VkRenderPassCreateInfo rpci = {
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
+ .attachmentCount = 1,
+ .pAttachments = &att,
+ .subpassCount = 1,
+ .pSubpasses = &sub,
+ };
+ if( d->create_render_pass( d->dev, &rpci, 0, &s->rp ) != VK_SUCCESS )
+ return STAT_ERR;
+ return STAT_OK;
+}
+
+STAT vk_swap_create_framebuffers( DEVICE_DATA* d, SWAP_DATA* s ) {
+ for( U32 i = 0; i < s->nimages; i++ ) {
+ VkImageViewCreateInfo vci = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .image = s->imgs[i],
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = s->fmt,
+ .subresourceRange = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .levelCount = 1,
+ .layerCount = 1,
+ },
+ };
+ if( d->create_image_view( d->dev, &vci, 0, &s->views[i] ) != VK_SUCCESS )
+ return STAT_ERR;
+
+ VkFramebufferCreateInfo fci = {
+ .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
+ .renderPass = s->rp,
+ .attachmentCount = 1,
+ .pAttachments = &s->views[i],
+ .width = s->extent.width,
+ .height = s->extent.height,
+ .layers = 1,
+ };
+ if( d->create_framebuffer( d->dev, &fci, 0, &s->fbs[i] ) != VK_SUCCESS )
+ return STAT_ERR;
+ }
+ return STAT_OK;
+}
+
+STAT vk_swap_create_shaders( DEVICE_DATA* d, SWAP_DATA* s ) {
+ VkShaderModuleCreateInfo smci = {
+ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+ .codeSize = gauge_vert_spv_len,
+ .pCode = (const U32*)gauge_vert_spv,
+ };
+ if( d->create_shader_module( d->dev, &smci, 0, &s->vs ) != VK_SUCCESS )
+ return STAT_ERR;
+
+ smci.codeSize = gauge_frag_spv_len;
+ smci.pCode = (const U32*)gauge_frag_spv;
+ if( d->create_shader_module( d->dev, &smci, 0, &s->fs ) != VK_SUCCESS )
+ return STAT_ERR;
+
+ VkPushConstantRange pcr = {
+ .stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
+ .offset = 0,
+ .size = 2 * sizeof(F32),
+ };
+ VkPipelineLayoutCreateInfo plci = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+ .pushConstantRangeCount = 1,
+ .pPushConstantRanges = &pcr,
+ };
+ if( d->create_pipeline_layout( d->dev, &plci, 0, &s->pl ) != VK_SUCCESS )
+ return STAT_ERR;
+ return STAT_OK;
+}
+
+// jesus fucking christ
+STAT vk_swap_create_pipeline( DEVICE_DATA* d, SWAP_DATA* s ) {
+ VkPipelineShaderStageCreateInfo stages[2] = {
+ {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ .stage = VK_SHADER_STAGE_VERTEX_BIT,
+ .module = s->vs,
+ .pName = "main"
+ },
+ {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ .stage = VK_SHADER_STAGE_FRAGMENT_BIT,
+ .module = s->fs,
+ .pName = "main"
+ },
+ };
+
+ VkVertexInputBindingDescription bind = {
+ .binding = 0,
+ .stride = sizeof( VERTEX ),
+ .inputRate = VK_VERTEX_INPUT_RATE_VERTEX,
+ };
+ VkVertexInputAttributeDescription attrs[2] = {
+ {
+ .location = 0, .binding = 0,
+ .format = VK_FORMAT_R32G32_SFLOAT, .offset = 0
+ },
+ {
+ .location = 1, .binding = 0,
+ .format = VK_FORMAT_R8G8B8A8_UNORM, .offset = 8
+ },
+ };
+ VkPipelineVertexInputStateCreateInfo vin = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+ .vertexBindingDescriptionCount = 1,
+ .pVertexBindingDescriptions = &bind,
+ .vertexAttributeDescriptionCount = 2,
+ .pVertexAttributeDescriptions = attrs,
+ };
+ VkPipelineInputAssemblyStateCreateInfo ia = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+ };
+ VkPipelineViewportStateCreateInfo vp = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+ .viewportCount = 1,
+ .scissorCount = 1,
+ };
+ VkPipelineRasterizationStateCreateInfo rs = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+ .polygonMode = VK_POLYGON_MODE_FILL,
+ .cullMode = VK_CULL_MODE_NONE,
+ .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,
+ .lineWidth = 1.0f,
+ };
+ VkPipelineMultisampleStateCreateInfo ms = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+ .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
+ };
+ VkPipelineColorBlendAttachmentState ba = {
+ .blendEnable = VK_TRUE,
+ .srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA,
+ .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
+ .colorBlendOp = VK_BLEND_OP_ADD,
+ .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
+ .dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
+ .alphaBlendOp = VK_BLEND_OP_ADD,
+ .colorWriteMask = VK_COLOR_COMPONENT_R_BIT |
+ VK_COLOR_COMPONENT_G_BIT |
+ VK_COLOR_COMPONENT_B_BIT |
+ VK_COLOR_COMPONENT_A_BIT,
+ };
+ VkPipelineColorBlendStateCreateInfo cb = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+ .attachmentCount = 1,
+ .pAttachments = &ba,
+ };
+ VkDynamicState dyns[2] = {
+ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR,
+ };
+ VkPipelineDynamicStateCreateInfo dyn = {
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
+ .dynamicStateCount = 2,
+ .pDynamicStates = dyns,
+ };
+ VkGraphicsPipelineCreateInfo gpci = {
+ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+ .stageCount = 2,
+ .pStages = stages,
+ .pVertexInputState = &vin,
+ .pInputAssemblyState = &ia,
+ .pViewportState = &vp,
+ .pRasterizationState = &rs,
+ .pMultisampleState = &ms,
+ .pColorBlendState = &cb,
+ .pDynamicState = &dyn,
+ .layout = s->pl,
+ .renderPass = s->rp,
+ };
+ if( d->create_graphics_pipelines( d->dev, VK_NULL_HANDLE, 1, &gpci, 0, &s->pipe ) != VK_SUCCESS )
+ return STAT_ERR;
+ return STAT_OK;
+}
+
+U32 find_memory_type( DEVICE_DATA* d, U32 bits, VkMemoryPropertyFlags want ) {
+ VkPhysicalDeviceMemoryProperties mp;
+ d->inst->get_memory_properties( d->phys, &mp );
+ for( U32 m = 0; m < mp.memoryTypeCount; m++ )
+ if( ( bits & ( 1u << m ) ) &&
+ ( mp.memoryTypes[m].propertyFlags & want ) == want )
+ return m;
+ return UINT32_MAX;
+}
+
+STAT vk_swap_create_vbuffers( DEVICE_DATA* d, SWAP_DATA* s ) {
+ for( U32 i = 0; i < s->nimages; i++ ) {
+ VkBufferCreateInfo bci = {
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .size = MAX_VERTS * sizeof(VERTEX),
+ .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ };
+ if( d->create_buffer( d->dev, &bci, 0, &s->vbufs[i] ) != VK_SUCCESS )
+ return STAT_ERR;
+
+ VkMemoryRequirements mr;
+ d->get_buffer_memory_requirements( d->dev, s->vbufs[i], &mr );
+ U32 type = find_memory_type(
+ d, mr.memoryTypeBits,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
+ );
+
+ if( type == UINT32_MAX )
+ return STAT_ERR;
+
+ VkMemoryAllocateInfo mai = {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .allocationSize = mr.size,
+ .memoryTypeIndex = type,
+ };
+ if( d->allocate_memory( d->dev, &mai, 0, &s->vmems[i] ) != VK_SUCCESS )
+ return STAT_ERR;
+ if( d->bind_buffer_memory( d->dev, s->vbufs[i], s->vmems[i], 0 )
+ != VK_SUCCESS )
+ return STAT_ERR;
+ if( d->map_memory( d->dev, s->vmems[i], 0, VK_WHOLE_SIZE, 0,
+ &s->vmaps[i] ) != VK_SUCCESS )
+ return STAT_ERR;
+ }
+ return STAT_OK;
+}
+
+STAT vk_swap_create_sync( DEVICE_DATA* d, SWAP_DATA* s ) {
+ for( U32 i = 0; i < s->nimages; i++ ) {
+ VkSemaphoreCreateInfo sci = {
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
+ };
+ if( d->create_semaphore( d->dev, &sci, 0, &s->sems[i] ) != VK_SUCCESS )
+ return STAT_ERR;
+
+ VkFenceCreateInfo fci = {
+ .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
+ .flags = VK_FENCE_CREATE_SIGNALED_BIT,
+ };
+ if( d->create_fence( d->dev, &fci, 0, &s->fences[i] ) != VK_SUCCESS )
+ return STAT_ERR;
+ }
+ return STAT_OK;
+}
+
+STAT swap_build_static( DEVICE_DATA* d, SWAP_DATA* s ) {
+ if( !OK( vk_swap_create_render_pass( d, s ) ) ) return STAT_ERR;
+ if( !OK( vk_swap_create_framebuffers( d, s ) ) ) return STAT_ERR;
+ if( !OK( vk_swap_create_shaders( d, s ) ) ) return STAT_ERR;
+ if( !OK( vk_swap_create_pipeline( d, s ) ) ) return STAT_ERR;
+ if( !OK( vk_swap_create_vbuffers( d, s ) ) ) return STAT_ERR;
+ if( !OK( vk_swap_create_sync( d, s ) ) ) return STAT_ERR;
+ return STAT_OK;
+}
+
+SWAP_DATA* vk_swap_create(
+ DEVICE_DATA* d,
+ VkSwapchainKHR sc,
+ const VkSwapchainCreateInfoKHR* ci
+) {
+ pthread_mutex_lock( &g_lock );
+ defer( pthread_mutex_unlock( &g_lock ) );
+
+ SWAP_DATA* s = (SWAP_DATA*)calloc( 1, sizeof( SWAP_DATA ) );
+ s->sc = sc;
+ s->fmt = ci->imageFormat;
+ s->extent = ci->imageExtent;
+
+ U32 n = 0;
+ d->get_swapchain_images( d->dev, sc, &n, 0 );
+ if( n > MAX_IMAGES )
+ n = MAX_IMAGES;
+ s->nimages = n;
+ d->get_swapchain_images( d->dev, sc, &n, s->imgs );
+
+ if( !OK( swap_build_static( d, s ) ) ) {
+ dlog( "fhvkov: swapchain setup failed, overlay disabled for this swapchain\n" );
+ s->broken = 1;
+ }
+
+ for( I32 i = 0; i < MAX_SWAPS; i++ )
+ if( !d->swaps[i] ) {
+ d->swaps[i] = s;
+ return s;
+ }
+
+ vk_swap_destroy( d, s );
+ return 0;
+}
+
+STAT vk_swap_setup_frames( DEVICE_DATA* d, SWAP_DATA* s, VkQueue q ) {
+ U32 family = UINT32_MAX;
+ for( I32 i = 0; i < d->nqueues; i++ )
+ if( d->queues[i].q == q ) {
+ family = d->queues[i].family;
+ break;
+ }
+ if( family == UINT32_MAX ) {
+ if( !d->warned_family ) {
+ dlog( "fhvkov: present queue family unknown, overlay disabled\n" );
+ d->warned_family = 1;
+ }
+ return STAT_ERR;
+ }
+
+ U32 nfam = 0;
+ d->inst->get_queue_family_properties( d->phys, &nfam, 0 );
+ VkQueueFamilyProperties fam[16];
+ if( nfam > 16 )
+ nfam = 16;
+ d->inst->get_queue_family_properties( d->phys, &nfam, fam );
+ if( family >= nfam || !(fam[family].queueFlags & VK_QUEUE_GRAPHICS_BIT) ) {
+ dlog( "fhvkov: present queue not graphics-capable, overlay disabled\n" );
+ return STAT_ERR;
+ }
+
+ VkCommandPoolCreateInfo cpci = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
+ .queueFamilyIndex = family,
+ };
+ if( d->create_command_pool( d->dev, &cpci, 0, &s->pool ) != VK_SUCCESS )
+ return STAT_ERR;
+
+ VkCommandBufferAllocateInfo cbai = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = s->pool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = s->nimages,
+ };
+ if( d->allocate_command_buffers( d->dev, &cbai, s->cbs ) != VK_SUCCESS )
+ return STAT_ERR;
+
+ s->frames_ready = true;
+ return STAT_OK;
+}
+
+void vk_swap_destroy( DEVICE_DATA* d, SWAP_DATA* s ) {
+ d->device_wait_idle( d->dev );
+ for( U32 i = 0; i < s->nimages; i++ ) {
+ if( s->vmaps[i] ) d->unmap_memory( d->dev, s->vmems[i] );
+ if( s->vbufs[i] ) d->destroy_buffer( d->dev, s->vbufs[i], 0 );
+ if( s->vmems[i] ) d->free_memory( d->dev, s->vmems[i], 0 );
+ if( s->sems[i] ) d->destroy_semaphore( d->dev, s->sems[i], 0 );
+ if( s->fences[i] ) d->destroy_fence( d->dev, s->fences[i], 0 );
+ if( s->fbs[i] ) d->destroy_framebuffer( d->dev, s->fbs[i], 0 );
+ if( s->views[i] ) d->destroy_image_view( d->dev, s->views[i], 0 );
+ }
+ if( s->pool ) d->destroy_command_pool( d->dev, s->pool, 0 );
+ if( s->pipe ) d->destroy_pipeline( d->dev, s->pipe, 0 );
+ if( s->pl ) d->destroy_pipeline_layout( d->dev, s->pl, 0 );
+ if( s->vs ) d->destroy_shader_module( d->dev, s->vs, 0 );
+ if( s->fs ) d->destroy_shader_module( d->dev, s->fs, 0 );
+ if( s->rp ) d->destroy_render_pass( d->dev, s->rp, 0 );
+ free( s );
+}
+
+void vk_swap_forget( DEVICE_DATA* d, VkSwapchainKHR sc ) {
+ pthread_mutex_lock( &g_lock );
+ defer( pthread_mutex_unlock( &g_lock ) );
+
+ for( I32 i = 0; i < MAX_SWAPS; i++ )
+ if( d->swaps[i] && d->swaps[i]->sc == sc ) {
+ vk_swap_destroy( d, d->swaps[i] );
+ d->swaps[i] = 0;
+ return;
+ }
+}
+
+void vk_draw( DEVICE_DATA* d, SWAP_DATA* s, U32 idx ) {
+ VkCommandBuffer cb = s->cbs[idx];
+ F32 w = (F32)s->extent.width, h = (F32)s->extent.height;
+
+ VkCommandBufferBeginInfo bi = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
+ };
+ d->begin_command_buffer( cb, &bi );
+
+ VkRenderPassBeginInfo rbi = {
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
+ .renderPass = s->rp,
+ .framebuffer = s->fbs[idx],
+ .renderArea = { .extent = s->extent },
+ };
+ d->cmd_begin_render_pass( cb, &rbi, VK_SUBPASS_CONTENTS_INLINE );
+ d->cmd_bind_pipeline( cb, VK_PIPELINE_BIND_POINT_GRAPHICS, s->pipe );
+
+ VkViewport vp = { 0.0f, 0.0f, w, h, 0.0f, 1.0f };
+ d->cmd_set_viewport( cb, 0, 1, &vp );
+ VkRect2D sc = { { 0, 0 }, s->extent };
+ d->cmd_set_scissor( cb, 0, 1, &sc );
+
+ VkDeviceSize zero = 0;
+ d->cmd_bind_vertex_buffers( cb, 0, 1, &s->vbufs[idx], &zero );
+
+ F32 pc[2] = { 2.0f / w, 2.0f / h };
+ d->cmd_push_constants( cb, s->pl, VK_SHADER_STAGE_VERTEX_BIT, 0,
+ sizeof( pc ), pc );
+
+ d->cmd_draw( cb, (U32)d->gauge.nverts, 1, 0, 0 );
+ d->cmd_end_render_pass( cb );
+ d->end_command_buffer( cb );
+}
+
+VkResult vk_present(
+ DEVICE_DATA* d,
+ SWAP_DATA* s,
+ VkQueue q,
+ const VkPresentInfoKHR* pi
+) {
+ U32 idx = pi->pImageIndices[0];
+ if( idx >= s->nimages )
+ return d->queue_present( q, pi );
+
+ d->wait_for_fences( d->dev, 1, &s->fences[idx], VK_TRUE, UINT64_MAX );
+ d->reset_fences( d->dev, 1, &s->fences[idx] );
+
+ overlay_build_cluster( d->overlay, &d->gauge, (F32)s->extent.width, (F32)s->extent.height, t_nows() );
+ memcpy( s->vmaps[idx], d->gauge.verts, (PTR)d->gauge.nverts * sizeof( VERTEX ) );
+ vk_draw( d, s, idx );
+
+ U32 nwait = pi->waitSemaphoreCount;
+ if( nwait > MAX_WAIT_SEMS )
+ nwait = MAX_WAIT_SEMS;
+ VkPipelineStageFlags stages[MAX_WAIT_SEMS];
+ for( U32 i = 0; i < nwait; i++ )
+ stages[i] = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+
+ VkSubmitInfo si = {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .waitSemaphoreCount = nwait,
+ .pWaitSemaphores = pi->pWaitSemaphores,
+ .pWaitDstStageMask = stages,
+ .commandBufferCount = 1,
+ .pCommandBuffers = &s->cbs[idx],
+ .signalSemaphoreCount = 1,
+ .pSignalSemaphores = &s->sems[idx],
+ };
+
+ if( d->queue_submit( q, 1, &si, s->fences[idx] ) != VK_SUCCESS ) {
+ s->broken = true;
+ return d->queue_present( q, pi );
+ }
+
+ VkPresentInfoKHR pi2 = *pi;
+ pi2.waitSemaphoreCount = 1;
+ pi2.pWaitSemaphores = &s->sems[idx];
+
+ return d->queue_present( q, &pi2 );
+}
diff --git a/src/vulkan.h b/src/vulkan.h
new file mode 100644
index 0000000..7a951e2
--- /dev/null
+++ b/src/vulkan.h
@@ -0,0 +1,134 @@
+#pragma once
+#include <vulkan/vulkan.h>
+#include <pthread.h>
+
+#include "gauges.h"
+#include "overlay.h"
+
+#define MAX_INSTANCES 4
+#define MAX_DEVICES 4
+#define MAX_QUEUES 16
+#define MAX_SWAPS 8
+#define MAX_IMAGES 8
+#define MAX_WAIT_SEMS 16
+
+#define GET_KEY( x ) ( *(void **)( x ) )
+
+struct INSTANCE_DATA {
+ void *key;
+ VkInstance instance;
+ PFN_vkGetInstanceProcAddr gipa;
+ PFN_vkDestroyInstance destroy_instance;
+ PFN_vkGetPhysicalDeviceMemoryProperties get_memory_properties;
+ PFN_vkGetPhysicalDeviceQueueFamilyProperties get_queue_family_properties;
+};
+
+struct SWAP_DATA {
+ VkSwapchainKHR sc;
+ VkFormat fmt;
+ VkExtent2D extent;
+ U32 nimages;
+ U8 frames_ready;
+ U8 broken;
+
+ VkImage imgs[MAX_IMAGES];
+ VkImageView views[MAX_IMAGES];
+ VkFramebuffer fbs[MAX_IMAGES];
+ VkCommandBuffer cbs[MAX_IMAGES];
+ VkSemaphore sems[MAX_IMAGES];
+ VkFence fences[MAX_IMAGES];
+ VkBuffer vbufs[MAX_IMAGES];
+ VkDeviceMemory vmems[MAX_IMAGES];
+ void *vmaps[MAX_IMAGES];
+
+ VkRenderPass rp;
+ VkShaderModule vs, fs;
+ VkPipelineLayout pl;
+ VkPipeline pipe;
+ VkCommandPool pool;
+};
+
+struct DEVICE_DATA {
+ void *key;
+ VkDevice dev;
+ VkPhysicalDevice phys;
+ INSTANCE_DATA *inst;
+
+ PFN_vkGetDeviceProcAddr gdpa;
+ PFN_vkDestroyDevice destroy_device;
+ PFN_vkGetDeviceQueue get_device_queue;
+ PFN_vkGetDeviceQueue2 get_device_queue2;
+ PFN_vkCreateSwapchainKHR create_swapchain;
+ PFN_vkDestroySwapchainKHR destroy_swapchain;
+ PFN_vkGetSwapchainImagesKHR get_swapchain_images;
+ PFN_vkQueuePresentKHR queue_present;
+ PFN_vkQueueSubmit queue_submit;
+ PFN_vkDeviceWaitIdle device_wait_idle;
+ PFN_vkCreateImageView create_image_view;
+ PFN_vkDestroyImageView destroy_image_view;
+ PFN_vkCreateRenderPass create_render_pass;
+ PFN_vkDestroyRenderPass destroy_render_pass;
+ PFN_vkCreateFramebuffer create_framebuffer;
+ PFN_vkDestroyFramebuffer destroy_framebuffer;
+ PFN_vkCreateShaderModule create_shader_module;
+ PFN_vkDestroyShaderModule destroy_shader_module;
+ PFN_vkCreatePipelineLayout create_pipeline_layout;
+ PFN_vkDestroyPipelineLayout destroy_pipeline_layout;
+ PFN_vkCreateGraphicsPipelines create_graphics_pipelines;
+ PFN_vkDestroyPipeline destroy_pipeline;
+ PFN_vkCreateCommandPool create_command_pool;
+ PFN_vkDestroyCommandPool destroy_command_pool;
+ PFN_vkAllocateCommandBuffers allocate_command_buffers;
+ PFN_vkBeginCommandBuffer begin_command_buffer;
+ PFN_vkEndCommandBuffer end_command_buffer;
+ PFN_vkCmdBeginRenderPass cmd_begin_render_pass;
+ PFN_vkCmdEndRenderPass cmd_end_render_pass;
+ PFN_vkCmdBindPipeline cmd_bind_pipeline;
+ PFN_vkCmdBindVertexBuffers cmd_bind_vertex_buffers;
+ PFN_vkCmdPushConstants cmd_push_constants;
+ PFN_vkCmdSetViewport cmd_set_viewport;
+ PFN_vkCmdSetScissor cmd_set_scissor;
+ PFN_vkCmdDraw cmd_draw;
+ PFN_vkCreateSemaphore create_semaphore;
+ PFN_vkDestroySemaphore destroy_semaphore;
+ PFN_vkCreateFence create_fence;
+ PFN_vkDestroyFence destroy_fence;
+ PFN_vkWaitForFences wait_for_fences;
+ PFN_vkResetFences reset_fences;
+ PFN_vkCreateBuffer create_buffer;
+ PFN_vkDestroyBuffer destroy_buffer;
+ PFN_vkGetBufferMemoryRequirements get_buffer_memory_requirements;
+ PFN_vkAllocateMemory allocate_memory;
+ PFN_vkFreeMemory free_memory;
+ PFN_vkBindBufferMemory bind_buffer_memory;
+ PFN_vkMapMemory map_memory;
+ PFN_vkUnmapMemory unmap_memory;
+ U8 warned_family;
+
+ struct { VkQueue q; U32 family; } queues[MAX_QUEUES];
+ I32 nqueues;
+
+ SWAP_DATA *swaps[MAX_SWAPS];
+
+ UDP_LISTENER udp;
+ GAUGE_RENDERER gauge;
+ OVERLAY_DATA* overlay;
+};
+
+extern pthread_mutex_t g_lock;
+
+INSTANCE_DATA *vk_instance_register( VkInstance inst, PFN_vkGetInstanceProcAddr gipa );
+void vk_instance_forget( VkInstance inst );
+INSTANCE_DATA *vk_find_instance( void *key );
+void vk_device_register( DEVICE_DATA *d );
+void vk_device_unregister( DEVICE_DATA *d );
+DEVICE_DATA *vk_find_device( void *key );
+void vk_device_add_queue( DEVICE_DATA *d, VkQueue q, U32 family );
+SWAP_DATA *vk_find_swap( DEVICE_DATA *d, VkSwapchainKHR sc );
+void vk_resolve_device_procs( DEVICE_DATA *d, VkDevice dev, PFN_vkGetDeviceProcAddr gdpa );
+SWAP_DATA *vk_swap_create( DEVICE_DATA *d, VkSwapchainKHR sc, const VkSwapchainCreateInfoKHR *ci );
+void vk_swap_forget( DEVICE_DATA *d, VkSwapchainKHR sc );
+void vk_swap_destroy( DEVICE_DATA *d, SWAP_DATA *s );
+STAT vk_swap_setup_frames( DEVICE_DATA *d, SWAP_DATA *s, VkQueue q );
+VkResult vk_present( DEVICE_DATA *d, SWAP_DATA *s, VkQueue q, const VkPresentInfoKHR *pi );
+void vk_draw( DEVICE_DATA *d, SWAP_DATA *s, U32 idx );