From 57d172df4db1451ba3caeeb0ca485d0845a61912 Mon Sep 17 00:00:00 2001 From: aura Date: Mon, 13 Jul 2026 10:15:03 +0200 Subject: push source --- .gitignore | 3 + Makefile | 23 +++ install.sh | 53 +++++ src/config.cpp | 180 ++++++++++++++++ src/config.h | 23 +++ src/gauge.frag | 4 + src/gauge.vert | 9 + src/gauges.cpp | 621 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/gauges.h | 99 +++++++++ src/hooks.cpp | 275 +++++++++++++++++++++++++ src/overlay.cpp | 269 ++++++++++++++++++++++++ src/overlay.h | 46 +++++ src/spv.h | 137 +++++++++++++ src/typedef.h | 64 ++++++ src/udp.cpp | 106 ++++++++++ src/udp.h | 44 ++++ src/util.h | 27 +++ src/vulkan.cpp | 618 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/vulkan.h | 134 ++++++++++++ vktest/vktest.c | 208 +++++++++++++++++++ 20 files changed, 2943 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 install.sh create mode 100644 src/config.cpp create mode 100644 src/config.h create mode 100644 src/gauge.frag create mode 100644 src/gauge.vert create mode 100644 src/gauges.cpp create mode 100644 src/gauges.h create mode 100644 src/hooks.cpp create mode 100644 src/overlay.cpp create mode 100644 src/overlay.h create mode 100644 src/spv.h create mode 100644 src/typedef.h create mode 100644 src/udp.cpp create mode 100644 src/udp.h create mode 100644 src/util.h create mode 100644 src/vulkan.cpp create mode 100644 src/vulkan.h create mode 100644 vktest/vktest.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c277174 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +libfhvkov_layer.so +compile_commands.json +vkwin diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..88ea7bf --- /dev/null +++ b/Makefile @@ -0,0 +1,23 @@ +CXX ?= g++ +CXXFLAGS ?= -O2 -Wall -Wextra -std=gnu++20 -Wno-missing-field-initializers -fPIC -fvisibility=hidden +GLSL ?= glslangValidator + +SRCS = src/hooks.cpp src/vulkan.cpp src/gauges.cpp src/udp.cpp src/config.cpp src/overlay.cpp + +libfhvkov_layer.so: $(SRCS) + $(CXX) $(CXXFLAGS) -shared -o $@ $(SRCS) -lpthread + +# regenerate spir-v shadercode +spv.h: src/gauge.vert src/gauge.frag + $(GLSL) -V src/gauge.vert -o build/gauge.vert.spv + $(GLSL) -V src/gauge.frag -o build/gauge.frag.spv + xxd -i build/gauge.vert.spv > src/$@ + xxd -i build/gauge.frag.spv >> src/$@ + +vkwin: vktest/vktest.c + $(CC) -O2 -Wall -Wextra -o $@ $< -lvulkan -lX11 + +clean: + rm -f libfhvkov_layer.so gauge.vert.spv gauge.frag.spv + +.PHONY: clean diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..ef87134 --- /dev/null +++ b/install.sh @@ -0,0 +1,53 @@ +#!/bin/sh + +set -ex pipefail + +if [[ $UID == 0 ]]; then + echo "cannot run as root" +fi + +make +if [ $? -ne 0 ]; then + echo "make failed" + exit 1 +fi + +mkdir -p ~/.local/lib64/ +if [ $? -ne 0 ]; then + echo "mkdir -p ~/.local/lib64/ failed" + exit 1 +fi +cp ./libfhvkov_layer.so ~/.local/lib64/ +if [ $? -ne 0 ]; then + echo "cp ./libfhvkov_layer.so ~/.local/lib64/ failed" + exit 1 +fi + +mkdir -p ~/.local/share/vulkan/implicit_layer.d/ +if [ $? -ne 0 ]; then + echo "mkdir -p ~/.local/share/vulkan/implicit_layer.d/ failed" + exit 1 +fi + +cat >> ~/.local/share/vulkan/implicit_layer.d/fhvkov_layer.json << EOF +{ + "file_format_version": "1.0.0", + "layer": { + "name": "VK_LAYER_fhrpm_overlay", + "type": "GLOBAL", + "library_path": "$HOME/.local/lib64/libfhvkov_layer.so", + "api_version": "1.3.0", + "implementation_version": "1", + "description": "forza horizon vulkan telemetry overlay", + "enable_environment": { "FHVKOV": "1" }, + "disable_environment": { "FHVKOV_DISABLE": "1" } + } +} +EOF + +if [ $? -ne 0 ]; then + echo "failed to install layer" + exit 1 +fi + +echo "done." 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 +#include +#include +#include +#include + +#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 +#include + +#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 +#include +#include +#include +#include +#include +#include + +#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 +#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 +struct __defer_t { + T f; + __defer_t( T f ) : f( f ) {}; + ~__defer_t() { f(); } +}; + +template +__defer_t __defer_func( T f ) { + return __defer_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 +#include +#include +#include +#include + +#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 +#include +#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 +#include +#include +#include + +#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 +#include + +#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 ); diff --git a/vktest/vktest.c b/vktest/vktest.c new file mode 100644 index 0000000..31bc828 --- /dev/null +++ b/vktest/vktest.c @@ -0,0 +1,208 @@ +#include +#include +#include +#include +#include +#include +#include + +#define W 1260 +#define H 720 + +#define CK(x) do { VkResult _r = (x); if (_r != VK_SUCCESS) { \ + fprintf(stderr, "FAIL %s -> %d\n", #x, _r); exit(1); } } while (0) + +int main() +{ + Display *dpy = XOpenDisplay( NULL ); + if (!dpy) { fprintf( stderr, "no display\n" ); return 1; } + Window win = XCreateSimpleWindow( dpy, DefaultRootWindow( dpy ), 10, 10, W, H, 0, 0, 0 ); + XStoreName( dpy, win, "vktest" ); + XMapWindow( dpy, win ); + XFlush( dpy ); + + const char *iexts[] = { "VK_KHR_surface", "VK_KHR_xlib_surface" }; + VkApplicationInfo ai = { VK_STRUCTURE_TYPE_APPLICATION_INFO }; + ai.apiVersion = VK_API_VERSION_1_1; + VkInstanceCreateInfo ici = { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO }; + ici.pApplicationInfo = &ai; + ici.enabledExtensionCount = 2; + ici.ppEnabledExtensionNames = iexts; + VkInstance inst; + CK( vkCreateInstance(&ici, NULL, &inst )); + + uint32_t n = 1; + VkPhysicalDevice phys; + vkEnumeratePhysicalDevices( inst, &n, &phys ); + + VkSurfaceKHR surf; + VkXlibSurfaceCreateInfoKHR sci = { VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR }; + sci.dpy = dpy; + sci.window = win; + CK( vkCreateXlibSurfaceKHR( inst, &sci, NULL, &surf ) ); + + uint32_t nfam = 0; + vkGetPhysicalDeviceQueueFamilyProperties( phys, &nfam, NULL ); + VkQueueFamilyProperties fam[8]; + if( nfam > 8 ) nfam = 8; + vkGetPhysicalDeviceQueueFamilyProperties( phys, &nfam, fam ); + uint32_t qf = UINT32_MAX; + for( uint32_t i = 0; i < nfam; i++ ) { + VkBool32 pres = VK_FALSE; + vkGetPhysicalDeviceSurfaceSupportKHR( phys, i, surf, &pres ); + if( (fam[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) && pres ) { qf = i; break; } + } + if( qf == UINT32_MAX ) { fprintf( stderr, "no queue\n" ); return 1; } + + float prio = 1.0f; + VkDeviceQueueCreateInfo qci = { VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO }; + qci.queueFamilyIndex = qf; + qci.queueCount = 1; + qci.pQueuePriorities = &prio; + const char *dexts[] = { "VK_KHR_swapchain" }; + VkDeviceCreateInfo dci = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO }; + dci.queueCreateInfoCount = 1; + dci.pQueueCreateInfos = &qci; + dci.enabledExtensionCount = 1; + dci.ppEnabledExtensionNames = dexts; + VkDevice dev; + CK( vkCreateDevice( phys, &dci, NULL, &dev ) ); + VkQueue q; + vkGetDeviceQueue( dev, qf, 0, &q ); + + VkSurfaceFormatKHR fmts[16]; + uint32_t nfmt = 16; + vkGetPhysicalDeviceSurfaceFormatsKHR( phys, surf, &nfmt, fmts ); + VkSurfaceCapabilitiesKHR caps; + vkGetPhysicalDeviceSurfaceCapabilitiesKHR( phys, surf, &caps ); + + VkSwapchainCreateInfoKHR scci = { VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR }; + scci.surface = surf; + scci.minImageCount = caps.minImageCount; + scci.imageFormat = fmts[0].format; + scci.imageColorSpace = fmts[0].colorSpace; + scci.imageExtent.width = W; + scci.imageExtent.height = H; + scci.imageArrayLayers = 1; + scci.imageUsage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + scci.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + scci.preTransform = caps.currentTransform; + scci.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + scci.presentMode = VK_PRESENT_MODE_FIFO_KHR; + scci.clipped = VK_TRUE; + VkSwapchainKHR sc; + CK( vkCreateSwapchainKHR( dev, &scci, NULL, &sc ) ); + printf( "swapchain format: %d\n", fmts[0].format ); + + uint32_t nimg = 8; + VkImage imgs[8]; + vkGetSwapchainImagesKHR( dev, sc, &nimg, imgs ); + + VkCommandPoolCreateInfo cpci = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO }; + cpci.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + cpci.queueFamilyIndex = qf; + VkCommandPool pool; + CK( vkCreateCommandPool( dev, &cpci, NULL, &pool )); + VkCommandBufferAllocateInfo cbai = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO }; + cbai.commandPool = pool; + cbai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + cbai.commandBufferCount = 1; + VkCommandBuffer cb; + CK( vkAllocateCommandBuffers( dev, &cbai, &cb ) ); + + VkSemaphoreCreateInfo semci = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; + VkSemaphore acq, ren; + CK( vkCreateSemaphore( dev, &semci, NULL, &acq ) ); + CK( vkCreateSemaphore( dev, &semci, NULL, &ren ) ); + VkFenceCreateInfo fci = { VK_STRUCTURE_TYPE_FENCE_CREATE_INFO }; + VkFence fence; + CK( vkCreateFence(dev, &fci, NULL, &fence )); + + VkBufferCreateInfo bci = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bci.size = (VkDeviceSize)W * H * 4; + bci.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; + VkBuffer rb; + CK( vkCreateBuffer( dev, &bci, NULL, &rb ) ); + VkMemoryRequirements mr; + vkGetBufferMemoryRequirements( dev, rb, &mr ); + VkPhysicalDeviceMemoryProperties mp; + vkGetPhysicalDeviceMemoryProperties( phys, &mp ); + uint32_t type = 0; + for( uint32_t i = 0; i < mp.memoryTypeCount; i++ ) { + if( (mr.memoryTypeBits & (1u << i)) && (mp.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) ) { + type = i; break; + } + } + VkMemoryAllocateInfo mai = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; + mai.allocationSize = mr.size; + mai.memoryTypeIndex = type; + VkDeviceMemory rmem; + CK( vkAllocateMemory(dev, &mai, NULL, &rmem ) ); + CK( vkBindBufferMemory(dev, rb, rmem, 0 ) ); + + VkImageSubresourceRange range; + memset( &range, 0, sizeof( range )); + range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + range.levelCount = 1; + range.layerCount = 1; + + for( ;; ) { + uint32_t idx; + CK( vkAcquireNextImageKHR(dev, sc, UINT64_MAX, acq, VK_NULL_HANDLE, &idx ) ); + + VkCommandBufferBeginInfo bi = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; + bi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + CK( vkBeginCommandBuffer( cb, &bi ) ); + + VkImageMemoryBarrier b = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; + b.srcAccessMask = 0; + b.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + b.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + b.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + b.srcQueueFamilyIndex = b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + b.image = imgs[idx]; + b.subresourceRange = range; + vkCmdPipelineBarrier( + cb, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, + 0, NULL, 1, &b + ); + + VkClearColorValue col = { { 0.05f, 0.10f, 0.20f, 1.0f } }; + vkCmdClearColorImage( cb, imgs[idx], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &col, 1, &range ); + + b.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + b.dstAccessMask = 0; + b.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + b.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + vkCmdPipelineBarrier( + cb, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, + 0, NULL, 1, &b + ); + CK( vkEndCommandBuffer( cb ) ); + + VkPipelineStageFlags wait = VK_PIPELINE_STAGE_TRANSFER_BIT; + VkSubmitInfo si = { VK_STRUCTURE_TYPE_SUBMIT_INFO }; + si.waitSemaphoreCount = 1; + si.pWaitSemaphores = &acq; + si.pWaitDstStageMask = &wait; + si.commandBufferCount = 1; + si.pCommandBuffers = &cb; + si.signalSemaphoreCount = 1; + si.pSignalSemaphores = &ren; + CK( vkQueueSubmit(q, 1, &si, VK_NULL_HANDLE ) ); + + VkPresentInfoKHR pi = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR }; + pi.waitSemaphoreCount = 1; + pi.pWaitSemaphores = &ren; + pi.swapchainCount = 1; + pi.pSwapchains = ≻ + pi.pImageIndices = &idx; + CK( vkQueuePresentKHR( q, &pi ) ); + vkQueueWaitIdle( q ); + usleep( 1 ); + } + + return 0; +} -- cgit v1.2.3