├── .gitignore ├── CMakeLists.txt ├── acf-configs └── FJS727 │ └── OpenWXR.cfg ├── api └── openwxr │ ├── wxr_intf.h │ └── xplane_api.h ├── build_release ├── data ├── Makefile ├── cleanup.frag ├── generic.vert ├── glsl_cleanup.sh ├── smear.frag ├── smear.vert └── smooth.frag ├── fonts └── Inconsolata │ ├── FONTLOG.txt │ ├── Inconsolata-Bold.ttf │ ├── Inconsolata-Regular.ttf │ └── OFL.txt └── src ├── CMakeLists.txt ├── XCompile.cmake ├── atmo.h ├── atmo_xp11.c ├── atmo_xp11.h ├── dbg_log.c ├── dbg_log.h ├── fontmgr.c ├── fontmgr.h ├── glpriv.h ├── prototypes ├── prototype.c └── prototype.h ├── standalone.c ├── standalone.h ├── symbols.version ├── wxr.c ├── wxr.h ├── xplane.c └── xplane.h /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeCache.txt 2 | Makefile 3 | *.cmake 4 | CMakeFiles 5 | bin 6 | .DS_Store 7 | OpenWXR.zip 8 | OpenWXR 9 | src/libOpenWXR.xpl.dll.a 10 | mac_x64 11 | lin_x64 12 | win_x64 13 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CDDL HEADER START 2 | # 3 | # This file and its contents are supplied under the terms of the 4 | # Common Development and Distribution License ("CDDL"), version 1.0. 5 | # You may only use this file in accordance with the terms of version 6 | # 1.0 of the CDDL. 7 | # 8 | # A full copy of the text of the CDDL should have accompanied this 9 | # source. A copy of the CDDL is also available via the Internet at 10 | # http://www.illumos.org/license/CDDL. 11 | # 12 | # CDDL HEADER END 13 | 14 | # Copyright 2018 Saso Kiselkov. All rights reserved. 15 | 16 | if(COMMAND cmake_policy) 17 | cmake_policy(SET CMP0003 NEW) 18 | endif(COMMAND cmake_policy) 19 | 20 | if(NOT CMAKE_BUILD_TYPE) 21 | SET(CMAKE_BUILD_TYPE "RelWithDebInfo") 22 | endif() 23 | message(STATUS "Current build type is : ${CMAKE_BUILD_TYPE}") 24 | 25 | add_subdirectory(src) 26 | cmake_minimum_required(VERSION 2.8) 27 | -------------------------------------------------------------------------------- /acf-configs/FJS727/OpenWXR.cfg: -------------------------------------------------------------------------------- 1 | # CDDL HEADER START 2 | # 3 | # This file and its contents are supplied under the terms of the 4 | # Common Development and Distribution License ("CDDL"), version 1.0. 5 | # You may only use this file in accordance with the terms of version 6 | # 1.0 of the CDDL. 7 | # 8 | # A full copy of the text of the CDDL should have accompanied this 9 | # source. A copy of the CDDL is also available via the Internet at 10 | # http://www.illumos.org/license/CDDL. 11 | # 12 | # CDDL HEADER END 13 | 14 | # Copyright 2018 Saso Kiselkov. All rights reserved. 15 | 16 | # FlyJSim 727 OpenWXR config file 17 | 18 | standalone = true 19 | 20 | efis/x = 798 21 | efis/y = 726 22 | 23 | res/x = 128 24 | res/y = 128 25 | 26 | num_modes = 2 27 | 28 | ui/style = RDR-4B 29 | 30 | power_dr = FJS/727/Elec/pow 31 | power_sw_dr = FJS/727/wx/WXSysSwitch 32 | power_on_delay = 10 33 | 34 | range_dr = FJS/727/wx/WX_MapRange 35 | tilt_dr = FJS/727/wx/TiltKnob 36 | tilt_rate = 5 37 | mode_dr = FJS/727/wx/WX_or_MAP 38 | gain_dr = FJS/727/wx/WXGainKnob 39 | gain_auto_pos = 0 40 | 41 | ctl/delay/power_sw = 0.5 42 | ctl/delay/mode = 0.25 43 | ctl/delay/tilt = 0.25 44 | ctl/delay/range = 0.25 45 | ctl/delay/scr/0/power_sw = 0.25 46 | 47 | # NM ranges: (screen off) 4, 10, 20, 40, 80, 160 48 | num_ranges = 7 49 | range/0 = 7408 50 | range/1 = 7408 51 | range/2 = 18520 52 | range/3 = 37040 53 | range/4 = 74080 54 | range/5 = 148160 55 | range/6 = 296320 56 | 57 | # WX mode 58 | 59 | mode/0/name = WX 60 | mode/0/beam_shape/x = 6 61 | mode/0/beam_shape/y = 6 62 | mode/0/scan_time = 6 63 | mode/0/scan_angle = 180 64 | mode/0/smear/x = 1 65 | mode/0/smear/y = 0 66 | mode/0/parked_azi = 0 67 | 68 | mode/0/num_colors = 5 69 | mode/0/colors/0/thresh = 0.84 70 | mode/0/colors/0/rgba = d864a5ff 71 | mode/0/colors/1/thresh = 0.63 72 | mode/0/colors/1/rgba = ed2024ff 73 | mode/0/colors/2/thresh = 0.42 74 | mode/0/colors/2/rgba = fff200ff 75 | mode/0/colors/3/thresh = 0.315 76 | mode/0/colors/3/rgba = 78c255ff 77 | mode/0/colors/4/thresh = 0 78 | mode/0/colors/4/rgba = 000000ff 79 | 80 | # MAP mode 81 | 82 | mode/1/name = MAP 83 | mode/1/beam_shape/x = 6 84 | mode/1/beam_shape/y = 30 85 | mode/1/scan_time = 6 86 | mode/1/scan_angle = 180 87 | mode/1/smear/x = 1 88 | mode/1/smear/y = 0 89 | mode/1/parked_azi = 0 90 | 91 | mode/1/num_colors = 5 92 | mode/1/colors/0/thresh = 0.84 93 | mode/1/colors/0/rgba = d864a5ff 94 | mode/1/colors/1/thresh = 0.63 95 | mode/1/colors/1/rgba = ed2024ff 96 | mode/1/colors/2/thresh = 0.42 97 | mode/1/colors/2/rgba = fff200ff 98 | mode/1/colors/3/thresh = 0.315 99 | mode/1/colors/3/rgba = 78c255ff 100 | mode/1/colors/4/thresh = 0 101 | mode/1/colors/4/rgba = 0 102 | 103 | num_screens = 1 104 | # Panel texture 0 x 577 x 512 x 378 105 | scr/0/x = 10 106 | scr/0/y = 577 107 | scr/0/w = 512 108 | scr/0/h = 378 109 | scr/0/fps = 4 110 | scr/0/underscan = 0.93 111 | scr/0/power_dr = FJS/727/Elec/pow 112 | scr/0/power_sw_dr = FJS/727/wx/WX_MapRange 113 | scr/0/power_on_rate = 20 114 | scr/0/power_off_rate = 0.05 115 | scr/0/brt_dr = FJS/727/wx/BrtKnob 116 | -------------------------------------------------------------------------------- /api/openwxr/wxr_intf.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CDDL HEADER START 3 | * 4 | * This file and its contents are supplied under the terms of the 5 | * Common Development and Distribution License ("CDDL"), version 1.0. 6 | * You may only use this file in accordance with the terms of version 7 | * 1.0 of the CDDL. 8 | * 9 | * A full copy of the text of the CDDL should have accompanied this 10 | * source. A copy of the CDDL is also available via the Internet at 11 | * http://www.illumos.org/license/CDDL. 12 | * 13 | * CDDL HEADER END 14 | */ 15 | /* 16 | * Copyright 2018 Saso Kiselkov. All rights reserved. 17 | */ 18 | 19 | #ifndef _OPENWXR_WXR_INTF_H_ 20 | #define _OPENWXR_WXR_INTF_H_ 21 | 22 | #include 23 | 24 | #ifdef __cplusplus 25 | extern "C" { 26 | #endif 27 | 28 | #define WXR_MIN_RES 32 29 | #define WXR_MAX_RANGES 32 30 | 31 | typedef enum { 32 | WXR_DISP_ARC, 33 | WXR_DISP_SQUARE 34 | } wxr_disp_t; 35 | 36 | /* 37 | * The main WXR configuration structure. This must be passed by te aircraft 38 | * to set up a WXR instance of OpenWXR. 39 | */ 40 | typedef struct { 41 | /* Number of range scales contained in `ranges' */ 42 | size_t num_ranges; 43 | /* 44 | * The range scales at which the screen of the WXR can draw. This 45 | * determines the sampling interval of the underlying weather so 46 | * as to avoid excessive sample density and the associated 47 | * performance cost. 48 | */ 49 | double ranges[WXR_MAX_RANGES]; 50 | /* 51 | * WXR weather sampling resolution. 52 | * 53 | * res_x - the number of radial samples sent out by the antenna as it 54 | * scans left-to-right. This is NOT screen resolution, although 55 | * in general you will probably want your radial resolution to 56 | * be close to the actual screen resolution, so that the WXR 57 | * samples don't appear too blobby and large on the screen. 58 | * res_y - the number of samples along a radar scan line. This should 59 | * be close to the screen resolution to avoid "blobby" looks, but 60 | * is again not its physical meaning. This means at what 61 | * resolution the WXR talks to the atmosphere to determine 62 | * reflected energy along a scan line. 63 | */ 64 | unsigned res_x; 65 | unsigned res_y; 66 | /* 67 | * The beam cone shape: 68 | * X - side-to-side angle of the radar beam 69 | * Y - up-down angle of the radar beam 70 | * These do NOT represent the radar's scan limit (i.e. how far the 71 | * antenna can swing). They represent the spacial size of one radar 72 | * pulse (once sent out). 73 | */ 74 | vect2_t beam_shape; 75 | 76 | wxr_disp_t disp_type; /* The of radar display to draw. */ 77 | double scan_time; /* Secs for one full swing side2side */ 78 | double scan_angle; /* Degrees btwn full lateral deflect */ 79 | double scan_angle_vert;/* Degrees btwn full vertical deflect */ 80 | double parked_azi; /* Neutral position of antenna in deg */ 81 | /* 82 | * This controls lateral smearing of the rendered radar image to 83 | * simulate small scanning inaccuracies. The X axis is used for 84 | * lateral smearing (approximately in pixels) when in horizontal 85 | * scanning mode, whereas the Y axis is used for up-down smearing 86 | * in the vertical scanning mode. 87 | */ 88 | vect2_t smear; 89 | } wxr_conf_t; 90 | 91 | #ifdef __cplusplus 92 | } 93 | #endif 94 | 95 | #endif /* _OPENWXR_WXR_INTF_H_ */ 96 | -------------------------------------------------------------------------------- /api/openwxr/xplane_api.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CDDL HEADER START 3 | * 4 | * This file and its contents are supplied under the terms of the 5 | * Common Development and Distribution License ("CDDL"), version 1.0. 6 | * You may only use this file in accordance with the terms of version 7 | * 1.0 of the CDDL. 8 | * 9 | * A full copy of the text of the CDDL should have accompanied this 10 | * source. A copy of the CDDL is also available via the Internet at 11 | * http://www.illumos.org/license/CDDL. 12 | * 13 | * CDDL HEADER END 14 | */ 15 | /* 16 | * Copyright 2018 Saso Kiselkov. All rights reserved. 17 | */ 18 | 19 | #ifndef _OPENWXR_XPLANE_API_H_ 20 | #define _OPENWXR_XPLANE_API_H_ 21 | 22 | #include "wxr_intf.h" 23 | 24 | #ifdef __cplusplus 25 | extern "C" { 26 | #endif 27 | 28 | #define OPENWXR_PLUGIN_SIG "skiselkov.openwxr" 29 | 30 | typedef struct atmo_s atmo_t; 31 | typedef struct wxr_s wxr_t; 32 | 33 | typedef struct { 34 | double min_val; 35 | double max_val; 36 | uint32_t rgba; /* Big-endian RGBA */ 37 | } wxr_color_t; 38 | 39 | typedef struct { 40 | wxr_t *(*init)(const wxr_conf_t *conf, const atmo_t *atmo); 41 | void (*fini)(wxr_t *wxr); 42 | 43 | void (*set_acf_pos)(wxr_t *wxr, geo_pos3_t pos, vect3_t orient); 44 | void (*set_scale)(wxr_t *wxr, unsigned range_idx); 45 | unsigned (*get_scale)(const wxr_t *wxr); 46 | void (*set_azimuth_limits)(wxr_t *wxr, double left, double right); 47 | double (*get_ant_azimuth)(const wxr_t *wxr); 48 | void (*set_ant_pitch)(wxr_t *wxr, double angle); 49 | double (*get_ant_pitch)(const wxr_t *wxr); 50 | void (*set_gain)(wxr_t *wxr, double gain); 51 | double (*get_gain)(const wxr_t *wxr); 52 | void (*set_stab)(wxr_t *wxr, double angle, double roll); 53 | void (*get_stab)(const wxr_t *wxr, bool_t *pitch, bool_t *roll); 54 | void (*set_beam_shadow)(wxr_t *wxr, bool_t flag); 55 | bool_t (*get_beam_shadow)(const wxr_t *wxr); 56 | void (*set_standby)(wxr_t *wxr, bool_t flag); 57 | bool_t (*get_standby)(const wxr_t *wxr); 58 | void (*draw)(wxr_t *wxr, vect2_t pos, vect2_t size); 59 | void (*clear_screen)(wxr_t *wxr); 60 | void (*set_vert_mode)(wxr_t *wxr, bool_t flag, double azimuth); 61 | bool_t (*get_vert_mode)(const wxr_t *wxr); 62 | void (*set_colors)(wxr_t *wxr, const wxr_color_t *colors, size_t num); 63 | double (*get_brightness)(const wxr_t *wxr); 64 | void (*set_brightness)(wxr_t *wxr, double brt); 65 | 66 | /* Debugging support */ 67 | bool_t (*reload_gl_progs)(wxr_t *wxr); 68 | } openwxr_intf_t; 69 | 70 | typedef enum { 71 | OPENWXR_ATMO_XP11_SET_EFIS = 0x20000, /* int coords[4] arg */ 72 | OPENWXR_INTF_GET, /* openwxr_intf_t ** arg */ 73 | OPENWXR_ATMO_GET /* atmo_t ** arg */ 74 | } openwxr_msg_t; 75 | 76 | #ifdef __cplusplus 77 | } 78 | #endif 79 | 80 | #endif /* _OPENWXR_XPLANE_API_H_ */ 81 | -------------------------------------------------------------------------------- /build_release: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # CDDL HEADER START 3 | # 4 | # This file and its contents are supplied under the terms of the 5 | # Common Development and Distribution License ("CDDL"), version 1.0. 6 | # You may only use this file in accordance with the terms of version 7 | # 1.0 of the CDDL. 8 | # 9 | # A full copy of the text of the CDDL should have accompanied this 10 | # source. A copy of the CDDL is also available via the Internet at 11 | # http://www.illumos.org/license/CDDL. 12 | # 13 | # CDDL HEADER END 14 | 15 | # Copyright 2024 Saso Kiselkov. All rights reserved. 16 | 17 | function my_realpath() { 18 | [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}" 19 | } 20 | 21 | OUTPUT_DIR="OpenWXR" 22 | OS="$(uname)" 23 | 24 | case "$OS" in 25 | Darwin) 26 | INSTALL="install -m 644" 27 | REALPATH=my_realpath 28 | ;; 29 | *) 30 | INSTALL="install -D -m 644" 31 | REALPATH=realpath 32 | ;; 33 | esac 34 | 35 | while getopts "A:a:g:h" opt; do 36 | case "$opt" in 37 | A) 38 | ACF_TYPE="$OPTARG" 39 | ;; 40 | a) 41 | LIBACFUTILS="$(${REALPATH} "$OPTARG")" 42 | ;; 43 | g) 44 | OPENGPWS="$(${REALPATH} "$OPTARG")" 45 | ;; 46 | h) 47 | cat << EOF 48 | Usage: $0 -a 49 | -a : path to built libacfutils repo 50 | EOF 51 | exit 52 | ;; 53 | *) 54 | exit 1 55 | ;; 56 | esac 57 | done 58 | 59 | if [ -z "$LIBACFUTILS" ]; then 60 | echo "Missing mandatory argument -a." \ 61 | "Try $0 -h for more information" >&2 62 | exit 1 63 | fi 64 | if [ -z "$OPENGPWS" ]; then 65 | echo "Missing mandatory argument -g." \ 66 | "Try $0 -h for more information" >&2 67 | exit 1 68 | fi 69 | 70 | if [[ $(uname) = Linux ]] && ! which glslang &> /dev/null && \ 71 | ! which glslangValidator &> /dev/null; then 72 | cat << EOF >&2 73 | Couldn't find the Vulkan SDK. Please download the SDK from: 74 | https://vulkan.lunarg.com/sdk/home 75 | Then symlink glslangValidator and spirv-cross in your \$PATH 76 | EOF 77 | exit 1 78 | fi 79 | 80 | set -e 81 | 82 | rm -rf "$OUTPUT_DIR" "$OUTPUT_DIR.zip" 83 | mkdir -p "$OUTPUT_DIR" 84 | 85 | if [[ "$OS" = "Darwin" ]]; then 86 | NCPUS=$(( $(sysctl -n hw.ncpu) + 1 )) 87 | rm -f CMakeCache.txt 88 | ( cd src && rm -f CMakeCache.txt && \ 89 | cmake . -DLIBACFUTILS="${LIBACFUTILS}" \ 90 | -DOPENGPWS="$OPENGPWS" \ 91 | -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" && \ 92 | make -j "$NCPUS" ) || exit 1 93 | else 94 | NCPUS=$(( $(grep 'processor[[:space:]]\+:' /proc/cpuinfo | wc -l) + \ 95 | 1 )) 96 | ( cd src && rm -f CMakeCache.txt && \ 97 | cmake . -DLIBACFUTILS="${LIBACFUTILS}" \ 98 | -DOPENGPWS="$OPENGPWS" && \ 99 | make -j "${NCPUS}" ) || exit 1 100 | ( cd src && rm -f CMakeCache.txt && \ 101 | cmake . -DLIBACFUTILS="${LIBACFUTILS}" \ 102 | -DOPENGPWS="$OPENGPWS" \ 103 | -DCMAKE_TOOLCHAIN_FILE="XCompile.cmake" \ 104 | -DHOST=x86_64-w64-mingw32 && \ 105 | make -j "${NCPUS}" && \ 106 | "$LIBACFUTILS/tools/mksyms" "../win_x64/OpenWXR.xpl" > \ 107 | "../win_x64/syms.txt" ) || exit 1 108 | fi 109 | 110 | if [[ "$OS" = Linux ]]; then 111 | ( cd data && make ) || exit 1 112 | 113 | $INSTALL -- "lin_x64/${OUTPUT_DIR}.xpl" \ 114 | "$OUTPUT_DIR/lin_x64/${OUTPUT_DIR}.xpl" 115 | $INSTALL -- "win_x64/${OUTPUT_DIR}.xpl" \ 116 | "$OUTPUT_DIR/win_x64/${OUTPUT_DIR}.xpl" 117 | $INSTALL -- "win_x64/syms.txt" "$OUTPUT_DIR/win_x64/syms.txt" 118 | else 119 | mkdir -p "$OUTPUT_DIR/mac_x64" \ 120 | "$OUTPUT_DIR/data" \ 121 | "$OUTPUT_DIR/openwxr" \ 122 | "$OUTPUT_DIR/fonts/Inconsolata" 123 | $INSTALL -- "mac_x64/${OUTPUT_DIR}.xpl" \ 124 | "$OUTPUT_DIR/mac_x64/${OUTPUT_DIR}.xpl" 125 | fi 126 | 127 | if [[ "$OS" = Linux ]]; then 128 | DATA_BIN_DIR="data/bin" 129 | fi 130 | 131 | find $DATA_BIN_DIR fonts -iname '*.opus' -or -iname '*.glsl*' -or \ 132 | -iname '*.spv' -or -iname '*.ttf' -or -iname '*.otf' | \ 133 | while read; do 134 | $INSTALL -- "$REPLY" "$OUTPUT_DIR/$REPLY" 135 | done 136 | 137 | if [ -n "$ACF_TYPE" ]; then 138 | cp "acf-configs/$ACF_TYPE/OpenWXR.cfg" "$OUTPUT_DIR" 139 | fi 140 | 141 | $INSTALL -- "api/openwxr/xplane_api.h" "$OUTPUT_DIR/openwxr/xplane_api.h" 142 | $INSTALL -- "api/openwxr/wxr_intf.h" "$OUTPUT_DIR/openwxr/wxr_intf.h" 143 | 144 | # ZIP up the product 145 | zip -r "$OUTPUT_DIR.zip" "$OUTPUT_DIR" 146 | -------------------------------------------------------------------------------- /data/Makefile: -------------------------------------------------------------------------------- 1 | # CDDL HEADER START 2 | # 3 | # This file and its contents are supplied under the terms of the 4 | # Common Development and Distribution License ("CDDL"), version 1.0. 5 | # You may only use this file in accordance with the terms of version 6 | # 1.0 of the CDDL. 7 | # 8 | # A full copy of the text of the CDDL should have accompanied this 9 | # source. A copy of the CDDL is also available via the Internet at 10 | # http://www.illumos.org/license/CDDL. 11 | # 12 | # CDDL HEADER END 13 | # 14 | # Copyright 2019 Saso Kiselkov. All rights reserved. 15 | 16 | SPVS = \ 17 | cleanup.frag.spv \ 18 | generic.vert.spv \ 19 | smear.frag.spv \ 20 | smear.vert.spv \ 21 | smooth.frag.spv 22 | 23 | OUTDIR=bin 24 | SPIRVX_TGT_VERSION=120 25 | SPIRVX_420_VERSION=420 26 | SPIRVX_430_VERSION=430 27 | 28 | GLSLANG=glslangValidator 29 | SPIRVX=spirv-cross 30 | GLSL_CLEANUP=./glsl_cleanup.sh 31 | 32 | ifeq ($(V),1) 33 | VERB= 34 | define logMsg 35 | endef 36 | else # Not Verbose 37 | VERB=@ 38 | define logMsg 39 | @echo $(1) 40 | endef 41 | endif 42 | 43 | define BUILD_SHADER 44 | $(call logMsg,-n \ [GLSLANG]\ ) 45 | $(VERB) $(GLSLANG) $(2) -G -o $@ $^ 46 | 47 | $(call logMsg,\ [SPIRVX $(SPIRVX_TGT_VERSION)]\ $@) 48 | $(VERB) $(SPIRVX) --version $(SPIRVX_TGT_VERSION) \ 49 | --extension GL_EXT_gpu_shader4 \ 50 | --output $(@:%.$(1).spv=%.$(1).glsl) $@ 51 | $(VERB) $(GLSL_CLEANUP) $(@:%.$(1).spv=%.$(1).glsl) 52 | 53 | $(call logMsg,\ [SPIRVX $(SPIRVX_420_VERSION)]\ $@) 54 | $(VERB) $(SPIRVX) --version $(SPIRVX_420_VERSION) \ 55 | --output $(@:%.$(1).spv=%.$(1).glsl420) $@ 56 | $(VERB) $(GLSL_CLEANUP) $(@:%.$(1).spv=%.$(1).glsl420) 57 | endef 58 | 59 | define BUILD_COMP_SHADER 60 | $(call logMsg,-n \ [GLSLANG]\ ) 61 | $(VERB) $(GLSLANG) $(2) -G -S comp -o $@ $^ 62 | 63 | $(call logMsg,\ [SPIRVX 4.30]\ $(@:%.$(1).spv=%.$(1).glsl430)) 64 | $(VERB) $(SPIRVX) --version $(SPIRVX_430_VERSION) \ 65 | --output $(@:%.$(1).spv=%.$(1).glsl430) $@ 66 | endef 67 | 68 | SPVS_OUT=$(addprefix $(OUTDIR)/,$(SPVS)) 69 | all : $(SPVS_OUT) 70 | 71 | clean : 72 | rm -f $(SPVS_OUT) $(patsubst %.spv,%.glsl,$(SPVS_OUT)) \ 73 | $(patsubst %.spv,%.glsl420,$(SPVS_OUT)) 74 | 75 | $(OUTDIR)/%.vert.spv : %.vert 76 | $(call BUILD_SHADER,vert) 77 | 78 | $(OUTDIR)/%.frag.spv : %.frag 79 | $(call BUILD_SHADER,frag) 80 | 81 | $(OUTDIR)/%.comp.spv : %.glsl 82 | $(call BUILD_COMP_SHADER,comp) 83 | 84 | $(OUTDIR)/%_comp.frag.spv : %.frag 85 | $(call BUILD_SHADER,frag,-DCOMPUTE_VARIANT=1) 86 | 87 | $(addprefix $(OUTDIR)/,$(SPVS)) : | $(OUTDIR) 88 | 89 | $(OUTDIR) : 90 | mkdir -p $(OUTDIR) 91 | -------------------------------------------------------------------------------- /data/cleanup.frag: -------------------------------------------------------------------------------- 1 | /* 2 | * CDDL HEADER START 3 | * 4 | * This file and its contents are supplied under the terms of the 5 | * Common Development and Distribution License ("CDDL"), version 1.0. 6 | * You may only use this file in accordance with the terms of version 7 | * 1.0 of the CDDL. 8 | * 9 | * A full copy of the text of the CDDL should have accompanied this 10 | * source. A copy of the CDDL is also available via the Internet at 11 | * http://www.illumos.org/license/CDDL. 12 | * 13 | * CDDL HEADER END 14 | */ 15 | /* 16 | * Copyright 2018 Saso Kiselkov. All rights reserved. 17 | */ 18 | 19 | #version 460 20 | 21 | layout(location = 10) uniform sampler2D tex; 22 | layout(location = 11) uniform vec2 tex_sz; 23 | 24 | layout(location = 0) out vec4 color_out; 25 | 26 | float 27 | compute_intens(vec2 pos) 28 | { 29 | vec4 color = texture(tex, pos); 30 | 31 | if (color.b > 0.1) { 32 | if (color.a < 0.95) 33 | return (0.0); 34 | else if (color.r > 0.9 && color.g > 0.9) 35 | return (0.75); 36 | else if (color.r > 0.9) 37 | return (1.0); 38 | else if (color.g > 0.9) 39 | return (0.25); 40 | else if (color.g > 0.7) 41 | return (0.5); 42 | else 43 | return (0.0); 44 | } else { 45 | if (color.a < 0.95) 46 | return (0.0); 47 | else if (color.r > 0.9 && color.g > 0.9) 48 | return (0.75); 49 | else if (color.r > 0.9) 50 | return (1.0); 51 | else if (color.g > 0.8) 52 | return (0.25); 53 | else if (color.g > 0.4) 54 | return (0.5); 55 | else 56 | return (0.0); 57 | } 58 | } 59 | 60 | void 61 | main(void) 62 | { 63 | vec2 pos = gl_FragCoord.xy / tex_sz; 64 | color_out = vec4(compute_intens(pos), 0, 0, 1); 65 | } 66 | -------------------------------------------------------------------------------- /data/generic.vert: -------------------------------------------------------------------------------- 1 | /* 2 | * CDDL HEADER START 3 | * 4 | * This file and its contents are supplied under the terms of the 5 | * Common Development and Distribution License ("CDDL"), version 1.0. 6 | * You may only use this file in accordance with the terms of version 7 | * 1.0 of the CDDL. 8 | * 9 | * A full copy of the text of the CDDL should have accompanied this 10 | * source. A copy of the CDDL is also available via the Internet at 11 | * http://www.illumos.org/license/CDDL. 12 | * 13 | * CDDL HEADER END 14 | */ 15 | /* 16 | * Copyright 2018 Saso Kiselkov. All rights reserved. 17 | */ 18 | 19 | #version 460 20 | 21 | layout(location = 0) uniform mat4 pvm; 22 | 23 | layout(location = 0) in vec3 vtx_pos; 24 | layout(location = 1) in vec2 vtx_tex0; 25 | 26 | layout(location = 0) out vec2 tex_coord; 27 | 28 | void 29 | main(void) 30 | { 31 | tex_coord = vtx_tex0; 32 | gl_Position = pvm * vec4(vtx_pos, 1.0); 33 | } 34 | -------------------------------------------------------------------------------- /data/glsl_cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # CDDL HEADER START 3 | # 4 | # This file and its contents are supplied under the terms of the 5 | # Common Development and Distribution License ("CDDL"), version 1.0. 6 | # You may only use this file in accordance with the terms of version 7 | # 1.0 of the CDDL. 8 | # 9 | # A full copy of the text of the CDDL should have accompanied this 10 | # source. A copy of the CDDL is also available via the Internet at 11 | # http://www.illumos.org/license/CDDL. 12 | # 13 | # CDDL HEADER END 14 | # 15 | # Copyright 2019 Saso Kiselkov. All rights reserved. 16 | 17 | sed -i ' 18 | s/\/textureSize2D/; 19 | ' $@ 20 | -------------------------------------------------------------------------------- /data/smear.frag: -------------------------------------------------------------------------------- 1 | /* 2 | * CDDL HEADER START 3 | * 4 | * This file and its contents are supplied under the terms of the 5 | * Common Development and Distribution License ("CDDL"), version 1.0. 6 | * You may only use this file in accordance with the terms of version 7 | * 1.0 of the CDDL. 8 | * 9 | * A full copy of the text of the CDDL should have accompanied this 10 | * source. A copy of the CDDL is also available via the Internet at 11 | * http://www.illumos.org/license/CDDL. 12 | * 13 | * CDDL HEADER END 14 | */ 15 | /* 16 | * Copyright 2018 Saso Kiselkov. All rights reserved. 17 | */ 18 | 19 | #version 460 20 | 21 | #ifdef GL_EXT_gpu_shader4 22 | #extension GL_EXT_gpu_shader4: enable 23 | #endif 24 | 25 | layout(location = 10) uniform sampler2D tex; 26 | layout(location = 11) uniform float smear_mult; 27 | layout(location = 12) uniform float brt; 28 | 29 | layout(location = 0) in vec2 tex_coord; 30 | 31 | layout(location = 0) out vec4 color_out; 32 | 33 | vec3 34 | brt_adjust(vec3 c) 35 | { 36 | float f = pow(4.0, brt) / 4.0; 37 | return (c * vec3(f)); 38 | } 39 | 40 | void 41 | main() 42 | { 43 | vec4 pixel = texture(tex, tex_coord); 44 | vec2 tex_size = textureSize(tex, 0); 45 | 46 | if (pixel.r == pixel.g && pixel.r == pixel.b) { 47 | color_out = pixel; 48 | } else { 49 | float s1 = sin(tex_coord.s * 16.1803); 50 | float s2 = sin(tex_coord.s * 95.828); 51 | float s3 = sin(tex_coord.s * 181.959); 52 | float s4 = sin(tex_coord.s * 314.159); 53 | float s5 = sin(tex_coord.s * 547.363); 54 | float smear_s = (2.5 * smear_mult * s1 * s2 * s3 * s4 * s5) / 55 | tex_size.x; 56 | 57 | float t1 = sin(tex_coord.t * 16.1803); 58 | float t2 = sin(tex_coord.t * 95.828); 59 | float smear_t = (smear_mult * t1) / tex_size.y; 60 | 61 | /* 62 | * Yes the smear components are reversed here, the 't' 63 | * component is smeared using smear_s and vice versa. 64 | * Gives us the jaggedy-edged look we want. 65 | */ 66 | color_out = texture(tex, vec2(tex_coord.s, 67 | clamp(tex_coord.t + smear_s, 0.0, 1.0))); 68 | } 69 | 70 | color_out = vec4(brt_adjust(color_out.rgb), color_out.a); 71 | } 72 | -------------------------------------------------------------------------------- /data/smear.vert: -------------------------------------------------------------------------------- 1 | /* 2 | * CDDL HEADER START 3 | * 4 | * This file and its contents are supplied under the terms of the 5 | * Common Development and Distribution License ("CDDL"), version 1.0. 6 | * You may only use this file in accordance with the terms of version 7 | * 1.0 of the CDDL. 8 | * 9 | * A full copy of the text of the CDDL should have accompanied this 10 | * source. A copy of the CDDL is also available via the Internet at 11 | * http://www.illumos.org/license/CDDL. 12 | * 13 | * CDDL HEADER END 14 | */ 15 | /* 16 | * Copyright 2018 Saso Kiselkov. All rights reserved. 17 | */ 18 | 19 | #version 460 20 | 21 | layout(location = 0) uniform mat4 pvm; 22 | 23 | layout(location = 0) in vec3 vtx_pos; 24 | layout(location = 1) in vec2 vtx_tex0; 25 | 26 | layout(location = 0) out vec2 tex_coord; 27 | 28 | void 29 | main() 30 | { 31 | gl_Position = pvm * vec4(vtx_pos, 1.0); 32 | /* 33 | * Just transfer the UV mapping information to the fragment shader. 34 | */ 35 | tex_coord = vtx_tex0; 36 | } 37 | -------------------------------------------------------------------------------- /data/smooth.frag: -------------------------------------------------------------------------------- 1 | /* 2 | * CDDL HEADER START 3 | * 4 | * This file and its contents are supplied under the terms of the 5 | * Common Development and Distribution License ("CDDL"), version 1.0. 6 | * You may only use this file in accordance with the terms of version 7 | * 1.0 of the CDDL. 8 | * 9 | * A full copy of the text of the CDDL should have accompanied this 10 | * source. A copy of the CDDL is also available via the Internet at 11 | * http://www.illumos.org/license/CDDL. 12 | * 13 | * CDDL HEADER END 14 | */ 15 | /* 16 | * Copyright 2018 Saso Kiselkov. All rights reserved. 17 | */ 18 | 19 | #version 460 20 | 21 | layout(location = 10) uniform sampler2D tex; 22 | layout(location = 11) uniform vec2 tex_sz; 23 | layout(location = 12) uniform float smooth_val; 24 | 25 | layout(location = 0) out vec4 color_out; 26 | 27 | void 28 | main(void) 29 | { 30 | vec2 pos = gl_FragCoord.xy / tex_sz; 31 | vec4 intens; 32 | 33 | for (int i = -2; i <= 2; i += 1) { 34 | for (int j = -2; j <= 2; j += 1) { 35 | intens += texture(tex, vec2(pos.x + i * smooth_val, 36 | pos.y + j * smooth_val)); 37 | } 38 | } 39 | 40 | color_out = intens / 25.0; 41 | } 42 | -------------------------------------------------------------------------------- /fonts/Inconsolata/FONTLOG.txt: -------------------------------------------------------------------------------- 1 | FONTLOG for the Inconsolata Bold font 2 | 3 | This file provides detailed information on the Inconsolata Bold 4 | font Software. 5 | 6 | This information should be distributed along with the 7 | Inconsolata Bold fonts and any derivative works. 8 | 9 | Basic Font Information 10 | 11 | Inconsolata Bold is a Unicode typeface family that supports 12 | languages that use the Latin script and its variants, and 13 | could be expanded to support other scripts. 14 | 15 | More specifically, this release supports the following Unicode 16 | ranges: Latin-1 17 | 18 | There are two .vfb Source files: 19 | 1. Inconsolata Bold-OTF.vfb (Merged contours) 20 | 2. Inconsolata Bold-TTF.vfb (TTF countours) 21 | 22 | ChangeLog 23 | 24 | 18 September 2012 (Alexei Vanyashiin) Inconsolata bold v. 1.014 25 | - Fixed spacing issue associated with the character "4" and "0" 26 | - Applied ttfautohint (to fix windows rendering problem that occurred in v 1.000) 27 | 28 | 29 | 7 December 2011 (Kirill Tkachev) Inconsolata Bold v. 1.000 30 | - Completed Bold version of Inconsolata in VBF format. 31 | 32 | If you make modifications be sure to add your name (N), 33 | email (E), web-address (if you have one) (W) and description (D). 34 | This list is in alphabetical order. 35 | 36 | N: Kirill Tkachev 37 | W: http://cyreal.org 38 | E: contact at cyreal.org 39 | D: Designer 40 | 41 | -------------------------------------------------------------------------------- /fonts/Inconsolata/Inconsolata-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skiselkov/OpenWXR/b6406fb63dd228f67300dad2d3531de9209ab530/fonts/Inconsolata/Inconsolata-Bold.ttf -------------------------------------------------------------------------------- /fonts/Inconsolata/Inconsolata-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skiselkov/OpenWXR/b6406fb63dd228f67300dad2d3531de9209ab530/fonts/Inconsolata/Inconsolata-Regular.ttf -------------------------------------------------------------------------------- /fonts/Inconsolata/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Raph Levien (firstname.lastname@gmail.com), Copyright (c) 2012, Cyreal (cyreal.org) 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CDDL HEADER START 2 | # 3 | # This file and its contents are supplied under the terms of the 4 | # Common Development and Distribution License ("CDDL"), version 1.0. 5 | # You may only use this file in accordance with the terms of version 6 | # 1.0 of the CDDL. 7 | # 8 | # A full copy of the text of the CDDL should have accompanied this 9 | # source. A copy of the CDDL is also available via the Internet at 10 | # http://www.illumos.org/license/CDDL. 11 | # 12 | # CDDL HEADER END 13 | 14 | # Copyright 2024 Saso Kiselkov. All rights reserved. 15 | 16 | cmake_minimum_required(VERSION 3.9) 17 | project(openwxr) 18 | 19 | option(LIBACFUTILS "libacfutils repo") 20 | option(OPENGPWS "OpenGPWS repo") 21 | option(FAST_DEBUG "enable fast debug mode") 22 | 23 | if(APPLE) 24 | set(PLAT_SHORT "mac64") 25 | set(PLAT_LONG "mac-64") 26 | set(PLAT_ONLY "mac") 27 | set(PLUGIN_BIN_OUTDIR "mac_x64") 28 | elseif(WIN32) 29 | set(PLAT_SHORT "win64") 30 | set(PLAT_LONG "win-64") 31 | set(PLAT_ONLY "win") 32 | set(PLUGIN_BIN_OUTDIR "win_x64") 33 | else() 34 | set(PLAT_SHORT "lin64") 35 | set(PLAT_LONG "linux-64") 36 | set(PLAT_ONLY "linux") 37 | set(PLUGIN_BIN_OUTDIR "lin_x64") 38 | endif() 39 | 40 | set(SRC 41 | atmo_xp11.c 42 | dbg_log.c 43 | fontmgr.c 44 | standalone.c 45 | wxr.c 46 | xplane.c 47 | ) 48 | set(HDR 49 | atmo.h 50 | atmo_xp11.h 51 | dbg_log.h 52 | fontmgr.h 53 | standalone.h 54 | wxr.h 55 | xplane.h 56 | ) 57 | 58 | set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELEASE} -DDEBUG -g") 59 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DDEBUG -O0 -g") 60 | 61 | set(ALL_SRC ${SRC} ${HDR}) 62 | list(SORT ALL_SRC) 63 | 64 | if(APPLE) 65 | add_executable(openwxr ${ALL_SRC}) 66 | else() 67 | add_library(openwxr SHARED ${ALL_SRC}) 68 | endif() 69 | 70 | execute_process(COMMAND 71 | ${LIBACFUTILS}/pkg-config-deps ${PLAT_LONG} --cflags 72 | OUTPUT_VARIABLE DEP_CFLAGS) 73 | string(REGEX REPLACE "\n$" "" DEP_CFLAGS "${DEP_CFLAGS}") 74 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${DEP_CFLAGS}") 75 | 76 | execute_process(COMMAND 77 | ${LIBACFUTILS}/pkg-config-deps ${PLAT_LONG} --libs 78 | OUTPUT_VARIABLE DEP_LIBS) 79 | string(REGEX REPLACE "\n$" "" DEP_LIBS "${DEP_LIBS}") 80 | 81 | include_directories(openwxr PUBLIC 82 | "${LIBACFUTILS}/src" 83 | "../api" 84 | "${OPENGPWS}/api" 85 | ) 86 | 87 | #compiler flags 88 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror --std=c11 \ 89 | -Wno-unused-local-typedefs -Wno-missing-field-initializers") 90 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} \ 91 | -DCHECK_RESULT_USED=\"__attribute__ ((warn_unused_result))\"") 92 | add_definitions(-D_GNU_SOURCE -D_FILE_OFFSET_BITS=64) 93 | 94 | if(${FAST_DEBUG}) 95 | add_definitions(-DFAST_DEBUG=1) 96 | else() 97 | add_definitions(-DFAST_DEBUG=0) 98 | endif() 99 | 100 | #libraries 101 | include_directories(openwxr PUBLIC 102 | "${LIBACFUTILS}/SDK/CHeaders/XPLM" 103 | "${LIBACFUTILS}/SDK/CHeaders/Widgets" 104 | "${LIBACFUTILS}/SDK/CHeaders/Wrappers") 105 | 106 | # Yes, we need to strip a trailing newline from command output. 107 | execute_process(COMMAND git rev-parse --short HEAD 108 | OUTPUT_VARIABLE PLUGIN_VERSION) 109 | string(REGEX REPLACE "\n$" "" PLUGIN_VERSION "${PLUGIN_VERSION}") 110 | 111 | add_definitions(-DPLUGIN_VERSION="${PLUGIN_VERSION}") 112 | add_definitions(-DXPLM200=1 -DXPLM210=1 -DXPLM300=1 -DXPLM301=1) 113 | add_definitions(-DGLEW_BUILD=GLEW_STATIC) 114 | if(WIN32) 115 | add_definitions(-DAPL=0 -DIBM=1 -DLIN=0 -D_WIN32_WINNT=0x0600) 116 | elseif(APPLE) 117 | add_definitions(-DAPL=1 -DIBM=0 -DLIN=0) 118 | else() 119 | add_definitions(-DAPL=0 -DIBM=0 -DLIN=1) 120 | endif() 121 | 122 | # linking 123 | find_library(LIBACFUTILS_LIBRARY acfutils "${LIBACFUTILS}/qmake/${PLAT_SHORT}") 124 | if(WIN32) 125 | find_library(XPLM_LIBRARY XPLM_64 "${LIBACFUTILS}/SDK/Libraries/Win") 126 | find_library(XPWIDGETS_LIBRARY XPWidgets_64 127 | "${LIBACFUTILS}/SDK/Libraries/Win") 128 | find_library(OPENGL_LIBRARY opengl32 "../GL_for_Windows/lib") 129 | target_link_libraries(openwxr 130 | ${LIBACFUTILS_LIBRARY} 131 | ${XPLM_LIBRARY} 132 | ${XPWIDGETS_LIBRARY} 133 | ${OPENGL_LIBRARY} 134 | ${DEP_LIBS} 135 | ) 136 | set_target_properties(openwxr PROPERTIES LINK_FLAGS 137 | "${CMAKE_SHARED_LINKER_FLAGS} -undefined_warning \ 138 | -fvisibility=hidden -static-libgcc -static-libstdc++") 139 | elseif(APPLE) 140 | find_library(XPLM_LIBRARY XPLM "${LIBACFUTILS}/SDK/Libraries/Mac") 141 | find_library(XPWIDGETS_LIBRARY XPWidgets 142 | "${LIBACFUTILS}/SDK/Libraries/Mac") 143 | find_library(OPENGL_FRAMEWORK OpenGL) 144 | find_library(COREFOUNDATION_FRAMEWORK CoreFoundation) 145 | target_link_libraries(openwxr 146 | ${LIBACFUTILS_LIBRARY} 147 | ${XPLM_LIBRARY} 148 | ${XPWIDGETS_LIBRARY} 149 | ${COREFOUNDATION_FRAMEWORK} 150 | ${OPENGL_FRAMEWORK} 151 | ${DEP_LIBS} 152 | ) 153 | set_target_properties(openwxr PROPERTIES LINK_FLAGS 154 | "${CMAKE_SHARED_LINKER_FLAGS} -fvisibility=hidden -bundle") 155 | else() 156 | target_link_libraries(openwxr 157 | ${LIBACFUTILS_LIBRARY} 158 | ${DEP_LIBS} 159 | ) 160 | set_target_properties(openwxr PROPERTIES LINK_FLAGS 161 | "${CMAKE_SHARED_LINKER_FLAGS} -rdynamic -nodefaultlibs \ 162 | -undefined_warning -fPIC -fvisibility=hidden \ 163 | -static-libgcc -static-libstdc++ \ 164 | -Wl,--version-script=symbols.version") 165 | endif() 166 | 167 | set_target_properties(openwxr PROPERTIES PREFIX "") 168 | set_target_properties(openwxr PROPERTIES SUFFIX "") 169 | set_target_properties(openwxr PROPERTIES LINKER_LANGUAGE CXX) 170 | set_target_properties(openwxr PROPERTIES C_STANDARD 11) 171 | set_target_properties(openwxr PROPERTIES CXX_STANDARD 17) 172 | 173 | set_target_properties(openwxr PROPERTIES RUNTIME_OUTPUT_DIRECTORY 174 | "${CMAKE_SOURCE_DIR}/../${PLUGIN_BIN_OUTDIR}") 175 | set_target_properties(openwxr PROPERTIES LIBRARY_OUTPUT_DIRECTORY 176 | "${CMAKE_SOURCE_DIR}/../${PLUGIN_BIN_OUTDIR}") 177 | set_target_properties(openwxr PROPERTIES OUTPUT_NAME "OpenWXR.xpl") 178 | -------------------------------------------------------------------------------- /src/XCompile.cmake: -------------------------------------------------------------------------------- 1 | # Cross-compiling requires CMake 2.6 or newer. Example: 2 | # cmake . -DCMAKE_TOOLCHAIN_FILE=XCompile.cmake -DHOST=x86_64-w64-mingw32 3 | # Where 'i686-w64-mingw32' is the host prefix for your cross-compiler. If you 4 | # already have a toolchain file setup, you may use that instead of this file. 5 | 6 | # the name of the target operating system 7 | set(CMAKE_SYSTEM_NAME Windows) 8 | 9 | # which compilers to use for C and C++ 10 | set(CMAKE_C_COMPILER "${HOST}-gcc") 11 | set(CMAKE_CXX_COMPILER "${HOST}-g++") 12 | set(CMAKE_RC_COMPILER "${HOST}-windres") 13 | 14 | # here is the target environment located 15 | set(CMAKE_FIND_ROOT_PATH "/usr/${HOST}") 16 | 17 | # here is where stuff gets installed to 18 | set(CMAKE_INSTALL_PREFIX "${CMAKE_FIND_ROOT_PATH}" CACHE STRING 19 | "Install path prefix, prepended onto install directories." FORCE) 20 | 21 | # set env vars so that pkg-config will look in the appropriate directory for 22 | # .pc files (as there seems to be no way to force using ${HOST}-pkg-config) 23 | set(ENV{PKG_CONFIG_LIBDIR} "${CMAKE_INSTALL_PREFIX}/lib/pkgconfig") 24 | set(ENV{PKG_CONFIG_PATH} "") 25 | -------------------------------------------------------------------------------- /src/atmo.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CDDL HEADER START 3 | * 4 | * This file and its contents are supplied under the terms of the 5 | * Common Development and Distribution License ("CDDL"), version 1.0. 6 | * You may only use this file in accordance with the terms of version 7 | * 1.0 of the CDDL. 8 | * 9 | * A full copy of the text of the CDDL should have accompanied this 10 | * source. A copy of the CDDL is also available via the Internet at 11 | * http://www.illumos.org/license/CDDL. 12 | * 13 | * CDDL HEADER END 14 | */ 15 | /* 16 | * Copyright 2018 Saso Kiselkov. All rights reserved. 17 | */ 18 | 19 | #ifndef _ATMO_H_ 20 | #define _ATMO_H_ 21 | 22 | #include 23 | 24 | #include 25 | 26 | #ifdef __cplusplus 27 | extern "C" { 28 | #endif 29 | 30 | typedef struct { 31 | geo_pos3_t origin; /* beam origin point */ 32 | bool_t vert_scan; /* are we scanning vert or horiz? */ 33 | double ant_rhdg; /* rel antenna hdg from aircraft */ 34 | vect2_t dir; /* X-hdg degrees, Y-pitch degrees up */ 35 | vect2_t shape; /* X-horiz degrees, Y-vert degrees */ 36 | double energy; /* beam energy (no units), log scale */ 37 | double range; /* scan line sampling range */ 38 | double max_range; /* for calibrating energy depletion */ 39 | int num_samples; /* number of samples to return */ 40 | double *energy_out; /* energy return samples, log scale */ 41 | double *doppler_out; /* freq shift, relative motion, m/s */ 42 | } scan_line_t; 43 | 44 | struct atmo_s { 45 | void (*set_range)(double rng); 46 | void (*probe)(scan_line_t *sl); 47 | }; 48 | 49 | #ifdef __cplusplus 50 | } 51 | #endif 52 | 53 | #endif /* _ATMO_H_ */ 54 | -------------------------------------------------------------------------------- /src/atmo_xp11.c: -------------------------------------------------------------------------------- 1 | /* 2 | * CDDL HEADER START 3 | * 4 | * This file and its contents are supplied under the terms of the 5 | * Common Development and Distribution License ("CDDL"), version 1.0. 6 | * You may only use this file in accordance with the terms of version 7 | * 1.0 of the CDDL. 8 | * 9 | * A full copy of the text of the CDDL should have accompanied this 10 | * source. A copy of the CDDL is also available via the Internet at 11 | * http://www.illumos.org/license/CDDL. 12 | * 13 | * CDDL HEADER END 14 | */ 15 | /* 16 | * Copyright 2018 Saso Kiselkov. All rights reserved. 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #include 41 | 42 | #include "atmo_xp11.h" 43 | #include "glpriv.h" 44 | #include "xplane.h" 45 | 46 | #define UPD_INTVAL 100000 /* us */ 47 | 48 | #define EFIS_WIDTH 194 49 | #define EFIS_LAT_PIX (EFIS_WIDTH / 2) 50 | #define EFIS_LON_AFT 134 51 | #define EFIS_LON_FWD 134 52 | #define EFIS_HEIGHT (EFIS_LON_FWD + EFIS_LON_AFT) 53 | #define WX_SMOOTH_RNG 300 /* meters */ 54 | 55 | static void atmo_xp11_set_range(double range); 56 | static void atmo_xp11_probe(scan_line_t *sl); 57 | 58 | static bool_t inited = B_FALSE; 59 | static atmo_t atmo = { 60 | .set_range = atmo_xp11_set_range, 61 | .probe = atmo_xp11_probe 62 | }; 63 | static XPLMCommandRef debug_cmd = NULL; 64 | 65 | enum { 66 | XPLANE_RENDER_GAUGES_2D = 0, 67 | XPLANE_RENDER_GAUGES_3D_UNLIT, 68 | XPLANE_RENDER_GAUGES_3D_LIT 69 | }; 70 | 71 | typedef enum { 72 | EFIS_MAP_RANGE_2_5NM, 73 | EFIS_MAP_RANGE_5NM, 74 | EFIS_MAP_RANGE_10NM, 75 | EFIS_MAP_RANGE_20NM, 76 | EFIS_MAP_RANGE_40NM, 77 | EFIS_MAP_RANGE_80NM, 78 | EFIS_MAP_RANGE_160NM, 79 | EFIS_MAP_NUM_RANGES 80 | } efis_map_range_t; 81 | 82 | /* Must follow order of efis_map_range_t */ 83 | static double efis_map_ranges[EFIS_MAP_NUM_RANGES] = { 84 | NM2MET(2.5), 85 | NM2MET(5), 86 | NM2MET(10), 87 | NM2MET(20), 88 | NM2MET(40), 89 | NM2MET(80), 90 | NM2MET(160) 91 | }; 92 | 93 | static struct { 94 | mutex_t lock; 95 | 96 | /* protected by lock */ 97 | uint32_t *pixels; 98 | double range; 99 | unsigned range_i; 100 | vect2_t precip_nodes[5]; 101 | 102 | /* only accessed by foreground drawing thread */ 103 | uint64_t last_update; 104 | unsigned efis_x; 105 | unsigned efis_y; 106 | unsigned efis_w; 107 | unsigned efis_h; 108 | mat4 efis_pvm; 109 | glutils_quads_t efis_quads; 110 | GLuint pbo; 111 | GLuint tmp_tex[3]; 112 | GLuint tmp_fbo[3]; 113 | GLsync xfer_sync; 114 | GLint smooth_prog; 115 | struct { 116 | GLint pvm; 117 | GLint tex; 118 | GLint tex_sz; 119 | GLint smooth_val; 120 | } smooth_prog_loc; 121 | GLint cleanup_prog; 122 | struct { 123 | GLint pvm; 124 | GLint tex; 125 | GLint tex_sz; 126 | } cleanup_prog_loc; 127 | } xp11_atmo; 128 | 129 | typedef enum { 130 | XP11_CLOUD_CLEAR = 0, 131 | XP11_CLOUD_HIGH_CIRRUS = 1, 132 | XP11_CLOUD_SCATTERED = 2, 133 | XP11_CLOUD_BROKEN = 3, 134 | XP11_CLOUD_OVERCAST = 4, 135 | XP11_CLOUD_STRATUS = 5 136 | } xp11_cloud_type_t; 137 | 138 | static struct { 139 | dr_t cloud_type[3]; /* xp11_cloud_type_t */ 140 | dr_t cloud_cover[3]; /* enum, 0..6 */ 141 | dr_t cloud_base[3]; /* meters MSL */ 142 | dr_t cloud_tops[3]; /* meters MSL */ 143 | dr_t wind_alt[3]; /* meters MSL */ 144 | dr_t wind_dir[3]; /* degrees true */ 145 | dr_t wind_spd[3]; /* knots */ 146 | dr_t wind_turb[3]; /* ratio 0..10 */ 147 | dr_t shear_dir[3]; /* degrees relative */ 148 | dr_t shear_spd[3]; /* knots */ 149 | dr_t render_type; 150 | 151 | dr_t temp_sl; 152 | /* These datarefs have been deprecated in XP12 */ 153 | dr_t temp_tropo_c; 154 | dr_t alt_tropo_m; 155 | 156 | struct { 157 | dr_t instr_brt; 158 | dr_t mode; 159 | dr_t submode; 160 | dr_t range; 161 | dr_t shows_wx; 162 | dr_t wx_alpha; 163 | dr_t shows_tcas; 164 | dr_t shows_arpts; 165 | dr_t shows_wpts; 166 | dr_t shows_VORs; 167 | dr_t shows_NDBs; 168 | dr_t kill_map_fms_line; 169 | } EFIS; 170 | } drs; 171 | 172 | static const shader_info_t generic_vert_info = 173 | { .filename = "generic.vert.spv" }; 174 | static const shader_info_t cleanup_frag_info = 175 | { .filename = "cleanup.frag.spv" }; 176 | static const shader_info_t smooth_frag_info = 177 | { .filename = "smooth.frag.spv" }; 178 | 179 | static const shader_prog_info_t cleanup_prog_info = { 180 | .progname = "atmo_xp11_cleanup", 181 | .vert = &generic_vert_info, 182 | .frag = &cleanup_frag_info 183 | }; 184 | 185 | static const shader_prog_info_t smooth_prog_info = { 186 | .progname = "atmo_xp11_cleanup", 187 | .vert = &generic_vert_info, 188 | .frag = &smooth_frag_info 189 | }; 190 | 191 | static int 192 | debug_cmd_handler(XPLMCommandRef ref, XPLMCommandPhase phase, void *refcon) 193 | { 194 | uint8_t buf[EFIS_WIDTH * EFIS_HEIGHT * 4]; 195 | GLint old_read_fbo; 196 | 197 | UNUSED(ref); 198 | UNUSED(refcon); 199 | 200 | if (phase != xplm_CommandBegin) 201 | return (1); 202 | 203 | glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old_read_fbo); 204 | for (int i = 0; i < 3; i++) { 205 | char filename[64]; 206 | 207 | if (xp11_atmo.tmp_fbo[i] == 0) 208 | continue; 209 | 210 | glBindFramebuffer(GL_READ_FRAMEBUFFER, xp11_atmo.tmp_fbo[i]); 211 | glReadBuffer(GL_COLOR_ATTACHMENT0); 212 | 213 | glReadPixels(0, 0, EFIS_WIDTH, EFIS_HEIGHT, 214 | GL_RGBA, GL_UNSIGNED_BYTE, buf); 215 | snprintf(filename, sizeof (filename), "xp11_atmo_fbo%d.png", 216 | i); 217 | png_write_to_file_rgba(filename, EFIS_WIDTH, EFIS_HEIGHT, buf); 218 | } 219 | glBindFramebuffer(GL_READ_FRAMEBUFFER, old_read_fbo); 220 | 221 | return (1); 222 | } 223 | 224 | static void 225 | atmo_xp11_set_range(double range) 226 | { 227 | mutex_enter(&xp11_atmo.lock); 228 | /* Set the fallback value first */ 229 | xp11_atmo.range = efis_map_ranges[EFIS_MAP_NUM_RANGES - 1]; 230 | xp11_atmo.range_i = EFIS_MAP_NUM_RANGES - 1; 231 | 232 | for (int i = 0; i < EFIS_MAP_NUM_RANGES; i++) { 233 | if (range <= efis_map_ranges[i]) { 234 | xp11_atmo.range = efis_map_ranges[i]; 235 | xp11_atmo.range_i = i; 236 | break; 237 | } 238 | } 239 | mutex_exit(&xp11_atmo.lock); 240 | } 241 | 242 | static void 243 | atmo_xp11_probe(scan_line_t *sl) 244 | { 245 | #define COST_PER_1KM 0.07 246 | double range; 247 | double dir_rand1 = (sin(DEG2RAD(sl->dir.x) * 6.7768) * 248 | sin(DEG2RAD(sl->dir.x) * 18.06) * 249 | sin(DEG2RAD(sl->dir.x) * 31.415)) / 15.0; 250 | double dir_rand2 = (sin(DEG2RAD(sl->dir.x) * 3.1767) * 251 | sin(DEG2RAD(sl->dir.x) * 14.459) * 252 | sin(DEG2RAD(sl->dir.x) * 34.252)) / 15.0; 253 | 254 | double rhdg_left = DEG2RAD(sl->ant_rhdg - sl->shape.x); 255 | double rhdg_right = DEG2RAD(sl->ant_rhdg + sl->shape.x); 256 | 257 | double sin_rhdg = sin(DEG2RAD(sl->ant_rhdg)); 258 | double sin_rhdg_left = sl->vert_scan ? sin(rhdg_left) : 0; 259 | double sin_rhdg_right = sl->vert_scan ? sin(rhdg_right) : 0; 260 | 261 | double cos_rhdg = cos(DEG2RAD(sl->ant_rhdg)); 262 | double cos_rhdg_left = sl->vert_scan ? cos(rhdg_left) : 0; 263 | double cos_rhdg_right = sl->vert_scan ? cos(rhdg_right) : 0; 264 | double sin_pitch = sin(DEG2RAD(sl->dir.y)); 265 | double sin_pitch_up = !sl->vert_scan ? sin(DEG2RAD(sl->dir.y + 266 | sl->shape.y * (0.5 + dir_rand1))) : 0; 267 | double sin_pitch_dn = !sl->vert_scan ? sin(DEG2RAD(sl->dir.y - 268 | sl->shape.y * (0.5 + dir_rand2))) : 0; 269 | vect2_t precip_nodes[5]; 270 | double energy = sl->energy; 271 | double sample_sz = sl->range / sl->num_samples; 272 | double sample_sz_rat = sample_sz / 1000.0; 273 | double cost_per_sample = COST_PER_1KM * sample_sz_rat; 274 | 275 | mutex_enter(&xp11_atmo.lock); 276 | range = xp11_atmo.range; 277 | memcpy(precip_nodes, xp11_atmo.precip_nodes, sizeof (precip_nodes)); 278 | mutex_exit(&xp11_atmo.lock); 279 | 280 | for (int i = 0; i < sl->num_samples; i++) { 281 | int x = (((double)i + 1) / sl->num_samples) * 282 | (sl->range / range) * EFIS_LON_FWD * sin_rhdg; 283 | int x_left = sl->vert_scan ? (((double)i + 1) / 284 | sl->num_samples) * (sl->range / range) * EFIS_LON_FWD * 285 | sin_rhdg_left : 0; 286 | int x_right = sl->vert_scan ? (((double)i + 1) / 287 | sl->num_samples) * (sl->range / range) * EFIS_LON_FWD * 288 | sin_rhdg_right : 0; 289 | int y = (((double)i + 1) / sl->num_samples) * 290 | (sl->range / range) * EFIS_LON_FWD * cos_rhdg; 291 | int y_left = sl->vert_scan ? (((double)i + 1) / 292 | sl->num_samples) * (sl->range / range) * EFIS_LON_FWD * 293 | cos_rhdg_left : 0; 294 | int y_right = sl->vert_scan ? (((double)i + 1) / 295 | sl->num_samples) * (sl->range / range) * EFIS_LON_FWD * 296 | cos_rhdg_right : 0; 297 | double d = (((double)i + 1) / sl->num_samples) * sl->range; 298 | double z_up = sl->origin.elev + d * sin_pitch_up; 299 | double z = sl->origin.elev + d * sin_pitch; 300 | double z_dn = sl->origin.elev + d * sin_pitch_dn; 301 | double precip_intens_pt; 302 | double precip_intens[3]; 303 | double energy_cost = 0; 304 | 305 | UNUSED(z); 306 | 307 | x += EFIS_LAT_PIX; 308 | y += EFIS_LON_AFT; 309 | 310 | /* 311 | * No doppler radar support yet. 312 | */ 313 | sl->doppler_out[i] = 0; 314 | 315 | if (x < 0 || x >= EFIS_WIDTH || y < 0 || y >= EFIS_HEIGHT) { 316 | sl->energy_out[i] = 0; 317 | continue; 318 | } 319 | 320 | if (xp11_atmo.pixels != NULL) { 321 | precip_intens_pt = (xp11_atmo.pixels[y * 322 | EFIS_WIDTH + x] & 0xff) / 255.0; 323 | if (sl->vert_scan) { 324 | precip_intens_pt += (xp11_atmo.pixels[y_left * 325 | EFIS_WIDTH + x_left] & 0xff) / 255.0; 326 | precip_intens_pt += (xp11_atmo.pixels[y_right * 327 | EFIS_WIDTH + x_right] & 0xff) / 255.0; 328 | precip_intens_pt /= 3.0; 329 | } 330 | } else { 331 | precip_intens_pt = 0.0; 332 | } 333 | 334 | /* 335 | * Compute precip intensity while taking the precip modulation 336 | * nodes into account. 337 | */ 338 | if (!sl->vert_scan) { 339 | precip_intens[0] = precip_intens_pt * 340 | fx_lin_multi(z_up, precip_nodes, B_FALSE); 341 | if (isnan(precip_intens[0])) 342 | precip_intens[0] = 0; 343 | } else { 344 | precip_intens[0] = 0; 345 | } 346 | 347 | precip_intens[1] = precip_intens_pt * 348 | fx_lin_multi(z, precip_nodes, B_FALSE); 349 | if (isnan(precip_intens[1])) 350 | precip_intens[1] = 0; 351 | 352 | if (!sl->vert_scan) { 353 | precip_intens[2] = precip_intens_pt * 354 | fx_lin_multi(z_dn, precip_nodes, B_FALSE); 355 | if (isnan(precip_intens[2])) 356 | precip_intens[2] = 0; 357 | } else { 358 | precip_intens[2] = 0; 359 | } 360 | 361 | energy_cost = MAX(energy_cost, cost_per_sample * 362 | precip_intens[0] * (energy / sl->energy)); 363 | energy_cost = MAX(energy_cost, cost_per_sample * 364 | precip_intens[1] * (energy / sl->energy)); 365 | energy_cost = MAX(energy_cost, cost_per_sample * 366 | precip_intens[2] * (energy / sl->energy)); 367 | 368 | sl->energy_out[i] = energy_cost; 369 | energy = MAX(0, energy - energy_cost); 370 | } 371 | } 372 | 373 | static void 374 | update_efis(void) 375 | { 376 | enum { 377 | EFIS_MODE_NORM = 1, 378 | EFIS_SUBMODE_MAP = 2, 379 | EFIS_SUBMODE_NAV = 3, 380 | EFIS_SUBMODE_PLANE = 4, 381 | EFIS_SUBMODE_GOOD_MAP = 5 382 | }; 383 | /* 384 | * IMPORTANT: the EFIS map brightness is tied to 385 | * instrument_brightness_ratio[0], so make sure it's full intensity 386 | * all the time so we can read the map. 387 | */ 388 | if (dr_getf(&drs.EFIS.instr_brt) != 1.0) 389 | dr_setf(&drs.EFIS.instr_brt, 1.0); 390 | if (dr_geti(&drs.EFIS.mode) != EFIS_MODE_NORM) 391 | dr_seti(&drs.EFIS.mode, EFIS_MODE_NORM); 392 | if (dr_geti(&drs.EFIS.submode) != EFIS_SUBMODE_GOOD_MAP) 393 | dr_seti(&drs.EFIS.submode, EFIS_SUBMODE_GOOD_MAP); 394 | if (dr_geti(&drs.EFIS.range) != (int)xp11_atmo.range_i) 395 | dr_seti(&drs.EFIS.range, xp11_atmo.range_i); 396 | if (dr_geti(&drs.EFIS.shows_wx) != 1) 397 | dr_seti(&drs.EFIS.shows_wx, 1); 398 | if (dr_getf(&drs.EFIS.wx_alpha) != 1.0) 399 | dr_seti(&drs.EFIS.wx_alpha, 1); 400 | if (dr_geti(&drs.EFIS.shows_tcas) != 0) 401 | dr_seti(&drs.EFIS.shows_tcas, 0); 402 | if (dr_geti(&drs.EFIS.shows_arpts) != 0) 403 | dr_seti(&drs.EFIS.shows_arpts, 0); 404 | if (dr_geti(&drs.EFIS.shows_wpts) != 0) 405 | dr_seti(&drs.EFIS.shows_wpts, 0); 406 | if (dr_geti(&drs.EFIS.shows_VORs) != 0) 407 | dr_seti(&drs.EFIS.shows_VORs, 0); 408 | if (dr_geti(&drs.EFIS.shows_NDBs) != 0) 409 | dr_seti(&drs.EFIS.shows_NDBs, 0); 410 | if (dr_geti(&drs.EFIS.kill_map_fms_line) == 0) 411 | dr_seti(&drs.EFIS.kill_map_fms_line, 1); 412 | } 413 | 414 | static void 415 | force_increasing_x(const vect2_t REQ_PTR(v1), vect2_t REQ_PTR(v2)) 416 | { 417 | if (v1->x >= v2->x) { 418 | v2->x = v1->x + 1.0; 419 | } 420 | } 421 | 422 | static void 423 | update_precip(void) 424 | { 425 | double cloud_z[2] = { 0, 0 }; 426 | double tmp_0_alt, tmp_minus_20_alt; 427 | enum { CLOUD_TOP_MARGIN = 50, RAIN_EVAP_MARGIN = 5000 }; 428 | double temp_tropo_c, alt_tropo_m; 429 | 430 | if (get_xpver() < 12000) { 431 | temp_tropo_c = dr_getf_prot(&drs.temp_tropo_c); 432 | alt_tropo_m = dr_getf_prot(&drs.alt_tropo_m); 433 | } else { 434 | temp_tropo_c = ISA_SL_TEMP_C - ISA_TLR_PER_1M * 435 | FEET2MET(ISA_TP_ALT); 436 | alt_tropo_m = FEET2MET(ISA_TP_ALT); 437 | } 438 | /* 439 | * To compute the location of the freezing level, we use the 440 | * sea-level temperature and tropopause temperature & altitude 441 | * to construct a linear temperature ramp. This is more-or-less 442 | * how temperature decreases with altitude. 443 | */ 444 | tmp_0_alt = fx_lin(0, dr_getf(&drs.temp_sl), 0, 445 | temp_tropo_c, alt_tropo_m); 446 | tmp_minus_20_alt = fx_lin(-20.0, dr_getf(&drs.temp_sl), 0, 447 | temp_tropo_c, alt_tropo_m); 448 | 449 | /* 450 | * If the temperature is inverted, force the algorithm below to at 451 | * least not crash. 452 | */ 453 | if (tmp_0_alt >= tmp_minus_20_alt - 100) 454 | tmp_0_alt = tmp_minus_20_alt - 1000; 455 | 456 | for (int i = 0; i < 3; i++) { 457 | /* clear skies or cirrus clouds don't generate precip */ 458 | if (dr_geti(&drs.cloud_type[i]) <= XP11_CLOUD_HIGH_CIRRUS) 459 | continue; 460 | cloud_z[0] = MIN(cloud_z[0], dr_getf(&drs.cloud_base[i])); 461 | cloud_z[1] = MAX(cloud_z[0], dr_getf(&drs.cloud_tops[i])); 462 | } 463 | 464 | /* 465 | * The top of the precip ramp is just above the cloud top. 466 | * The bottom is either at the cloud top minus the margin, 467 | * or in the middle between the cloud top & bottom, whichever 468 | * is higher. 469 | */ 470 | if (cloud_z[0] == cloud_z[1]) { 471 | for (int i = 0; i < 4; i++) 472 | xp11_atmo.precip_nodes[i] = VECT2(i, 0); 473 | } else { 474 | xp11_atmo.precip_nodes[0] = 475 | VECT2(cloud_z[0] - RAIN_EVAP_MARGIN, 1.0); 476 | xp11_atmo.precip_nodes[1] = VECT2(cloud_z[0], 1.0); 477 | xp11_atmo.precip_nodes[2] = 478 | VECT2(MAX(cloud_z[1] - CLOUD_TOP_MARGIN, 479 | (cloud_z[0] + cloud_z[1] / 2)), 1); 480 | xp11_atmo.precip_nodes[3] = 481 | VECT2(cloud_z[1] + CLOUD_TOP_MARGIN, 0); 482 | 483 | for (int i = 0; i < 4; i++) { 484 | /* 485 | * Clamp the modulation curve so as not to extend 486 | * above the freezing level. Even if the cloud 487 | * reaches higher, its contents will be completely 488 | * frozen, so WXR won't see them. 489 | */ 490 | xp11_atmo.precip_nodes[i].y *= 491 | (1 - iter_fract(xp11_atmo.precip_nodes[i].x, 492 | tmp_0_alt, tmp_minus_20_alt, B_TRUE)); 493 | } 494 | // Make sure that the precip nodes are properly ordered. 495 | // XP12 seems to sometimes not do this right. 496 | force_increasing_x(&xp11_atmo.precip_nodes[0], 497 | &xp11_atmo.precip_nodes[1]); 498 | force_increasing_x(&xp11_atmo.precip_nodes[1], 499 | &xp11_atmo.precip_nodes[2]); 500 | force_increasing_x(&xp11_atmo.precip_nodes[2], 501 | &xp11_atmo.precip_nodes[3]); 502 | } 503 | } 504 | 505 | static void 506 | setup_opengl(void) 507 | { 508 | if (xp11_atmo.pbo == 0) { 509 | glGenBuffers(1, &xp11_atmo.pbo); 510 | glBindBuffer(GL_PIXEL_PACK_BUFFER, xp11_atmo.pbo); 511 | glBufferData(GL_PIXEL_PACK_BUFFER, EFIS_WIDTH * EFIS_HEIGHT * 512 | sizeof (*xp11_atmo.pixels), 0, GL_STREAM_READ); 513 | glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); 514 | } 515 | 516 | if (xp11_atmo.tmp_tex[0] == 0) { 517 | glGenTextures(3, xp11_atmo.tmp_tex); 518 | for (int i = 0; i < 3; i++) { 519 | XPLMBindTexture2d(xp11_atmo.tmp_tex[i], GL_TEXTURE_2D); 520 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, EFIS_WIDTH, 521 | EFIS_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); 522 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, 523 | GL_LINEAR); 524 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, 525 | GL_LINEAR); 526 | } 527 | } 528 | 529 | if (xp11_atmo.tmp_fbo[0] == 0) { 530 | GLint old_fbo; 531 | 532 | glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &old_fbo); 533 | glGenFramebuffers(3, xp11_atmo.tmp_fbo); 534 | for (int i = 0; i < 3; i++) { 535 | glBindFramebuffer(GL_FRAMEBUFFER, xp11_atmo.tmp_fbo[i]); 536 | glFramebufferTexture2D(GL_FRAMEBUFFER, 537 | GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 538 | xp11_atmo.tmp_tex[i], 0); 539 | VERIFY3U(glCheckFramebufferStatus(GL_FRAMEBUFFER), ==, 540 | GL_FRAMEBUFFER_COMPLETE); 541 | } 542 | glBindFramebuffer(GL_DRAW_FRAMEBUFFER, old_fbo); 543 | } 544 | 545 | if (xp11_atmo.efis_quads.vbo == 0) { 546 | vect2_t vtx[4] = { 547 | VECT2(0, 0), VECT2(0, EFIS_HEIGHT), 548 | VECT2(EFIS_WIDTH, EFIS_HEIGHT), VECT2(EFIS_WIDTH, 0) 549 | }; 550 | glutils_init_2D_quads(&xp11_atmo.efis_quads, vtx, NULL, 4); 551 | glm_ortho(0, EFIS_WIDTH, 0, EFIS_HEIGHT, 0, 1, 552 | xp11_atmo.efis_pvm); 553 | } 554 | } 555 | 556 | static void 557 | transfer_new_efis_frame(void) 558 | { 559 | double range = efis_map_ranges[MIN((unsigned)dr_geti(&drs.EFIS.range), 560 | EFIS_MAP_NUM_RANGES - 1)]; 561 | GLint old_read_fbo, old_draw_fbo; 562 | 563 | XPLMSetGraphicsState(0, 1, 0, 1, 1, 1, 1); 564 | glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old_read_fbo); 565 | glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &old_draw_fbo); 566 | 567 | /* 568 | * Step 1: transfer the EFIS screen FBO into the input FBO. 569 | */ 570 | glBindFramebuffer(GL_READ_FRAMEBUFFER, old_read_fbo); 571 | glReadBuffer(GL_COLOR_ATTACHMENT0); 572 | glBindFramebuffer(GL_DRAW_FRAMEBUFFER, xp11_atmo.tmp_fbo[0]); 573 | glClear(GL_COLOR_BUFFER_BIT); 574 | glDrawBuffer(GL_COLOR_ATTACHMENT0); 575 | glBlitFramebuffer(xp11_atmo.efis_x, xp11_atmo.efis_y, 576 | xp11_atmo.efis_x + EFIS_WIDTH, xp11_atmo.efis_y + EFIS_HEIGHT, 577 | 0, 0, EFIS_WIDTH, EFIS_HEIGHT, GL_COLOR_BUFFER_BIT, GL_NEAREST); 578 | 579 | /* 580 | * Step 2: pass the EFIS output through a cleanup shader to 581 | * get rid of the EFIS symbology. 582 | */ 583 | glBindFramebuffer(GL_DRAW_FRAMEBUFFER, xp11_atmo.tmp_fbo[1]); 584 | glClear(GL_COLOR_BUFFER_BIT); 585 | glActiveTexture(GL_TEXTURE0); 586 | glBindTexture(GL_TEXTURE_2D, xp11_atmo.tmp_tex[0]); 587 | 588 | glUseProgram(xp11_atmo.cleanup_prog); 589 | glUniformMatrix4fv(xp11_atmo.cleanup_prog_loc.pvm, 590 | 1, GL_FALSE, (GLfloat *)xp11_atmo.efis_pvm); 591 | glUniform1i(xp11_atmo.cleanup_prog_loc.tex, 0); 592 | glUniform2f(xp11_atmo.cleanup_prog_loc.tex_sz, 593 | EFIS_WIDTH, EFIS_HEIGHT); 594 | glutils_draw_quads(&xp11_atmo.efis_quads, xp11_atmo.cleanup_prog); 595 | 596 | /* 597 | * Step 3: smooth the EFIS output to get a more sensible 598 | * representation of precip intensity (rather than just 599 | * using the pre-rendered colors as a fixed value. 600 | */ 601 | glBindFramebuffer(GL_DRAW_FRAMEBUFFER, xp11_atmo.tmp_fbo[2]); 602 | glClear(GL_COLOR_BUFFER_BIT); 603 | glActiveTexture(GL_TEXTURE0); 604 | glBindTexture(GL_TEXTURE_2D, xp11_atmo.tmp_tex[1]); 605 | 606 | glUseProgram(xp11_atmo.smooth_prog); 607 | glUniformMatrix4fv(glGetUniformLocation(xp11_atmo.smooth_prog, "pvm"), 608 | 1, GL_FALSE, (GLfloat *)xp11_atmo.efis_pvm); 609 | glUniform1i(glGetUniformLocation(xp11_atmo.smooth_prog, "tex"), 0); 610 | glUniform2f(glGetUniformLocation(xp11_atmo.smooth_prog, "tex_sz"), 611 | EFIS_WIDTH, EFIS_HEIGHT); 612 | glUniform1f(glGetUniformLocation(xp11_atmo.smooth_prog, "smooth_val"), 613 | WX_SMOOTH_RNG / range); 614 | glutils_draw_quads(&xp11_atmo.efis_quads, xp11_atmo.smooth_prog); 615 | 616 | glUseProgram(0); 617 | 618 | /* 619 | * Step 4: set up transfer of the output FBO back to the CPU. 620 | */ 621 | glBindFramebuffer(GL_READ_FRAMEBUFFER, xp11_atmo.tmp_fbo[2]); 622 | glBindBuffer(GL_PIXEL_PACK_BUFFER, xp11_atmo.pbo); 623 | glReadPixels(0, 0, EFIS_WIDTH, EFIS_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, 624 | NULL); 625 | glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); 626 | 627 | /* 628 | * Step 5: restore the FBO state of X-Plane. 629 | */ 630 | glBindFramebuffer(GL_READ_FRAMEBUFFER, old_read_fbo); 631 | glBindFramebuffer(GL_DRAW_FRAMEBUFFER, old_draw_fbo); 632 | } 633 | 634 | static int 635 | update_cb(XPLMDrawingPhase phase, int before, void *refcon) 636 | { 637 | uint64_t now; 638 | UNUSED(phase); 639 | UNUSED(before); 640 | UNUSED(refcon); 641 | 642 | /* 643 | * Careful, don't read the FBO from the other phases, you'll get junk. 644 | */ 645 | if (dr_geti(&drs.render_type) != XPLANE_RENDER_GAUGES_3D_LIT) 646 | return (1); 647 | 648 | #if !APL 649 | /* 650 | * On Apple, this can break 3rd party plugins such as SASL, which 651 | * use legacy OpenGL 2.x drawing operations. 652 | */ 653 | glutils_disable_all_client_state(); 654 | #endif /* !APL */ 655 | 656 | mutex_enter(&xp11_atmo.lock); 657 | 658 | update_efis(); 659 | update_precip(); 660 | 661 | if (xp11_atmo.pixels == NULL) { 662 | if (xp11_atmo.efis_w == 0 || xp11_atmo.efis_h == 0) 663 | goto out; 664 | xp11_atmo.pixels = safe_calloc(EFIS_WIDTH * EFIS_HEIGHT, 665 | sizeof (*xp11_atmo.pixels)); 666 | } 667 | 668 | setup_opengl(); 669 | 670 | now = microclock(); 671 | if (xp11_atmo.xfer_sync != 0) { 672 | if (glClientWaitSync(xp11_atmo.xfer_sync, 0, 0) != 673 | GL_TIMEOUT_EXPIRED) { 674 | void *ptr; 675 | 676 | /* Latest WXR image transfer is complete, fetch it */ 677 | glBindBuffer(GL_PIXEL_PACK_BUFFER, xp11_atmo.pbo); 678 | ptr = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY); 679 | if (ptr != NULL) { 680 | memcpy(xp11_atmo.pixels, ptr, EFIS_WIDTH * 681 | EFIS_HEIGHT * sizeof (*xp11_atmo.pixels)); 682 | glUnmapBuffer(GL_PIXEL_PACK_BUFFER); 683 | } 684 | glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); 685 | glDeleteSync(xp11_atmo.xfer_sync); 686 | xp11_atmo.xfer_sync = 0; 687 | } 688 | } else if (xp11_atmo.last_update + UPD_INTVAL <= now) { 689 | transfer_new_efis_frame(); 690 | xp11_atmo.xfer_sync = 691 | glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); 692 | xp11_atmo.last_update = now; 693 | } 694 | 695 | out: 696 | mutex_exit(&xp11_atmo.lock); 697 | return (1); 698 | } 699 | 700 | atmo_t * 701 | atmo_xp11_init(void) 702 | { 703 | ASSERT(!inited); 704 | inited = B_TRUE; 705 | 706 | memset(&xp11_atmo, 0, sizeof (xp11_atmo)); 707 | mutex_init(&xp11_atmo.lock); 708 | 709 | debug_cmd = XPLMCreateCommand("openwxr/debug_atmo_xp11", 710 | "Dump XP11 screenshot into X-Plane folder"); 711 | ASSERT(debug_cmd != NULL); 712 | XPLMRegisterCommandHandler(debug_cmd, debug_cmd_handler, 0, NULL); 713 | XPLMRegisterDrawCallback(update_cb, xplm_Phase_Gauges, 0, NULL); 714 | 715 | for (int i = 0; i < 3; i++) { 716 | fdr_find(&drs.cloud_type[i], 717 | "sim/weather/cloud_type[%d]", i); 718 | fdr_find(&drs.cloud_cover[i], 719 | "sim/weather/cloud_coverage[%d]", i); 720 | fdr_find(&drs.cloud_base[i], 721 | "sim/weather/cloud_base_msl_m[%d]", i); 722 | fdr_find(&drs.cloud_tops[i], 723 | "sim/weather/cloud_tops_msl_m[%d]", i); 724 | fdr_find(&drs.wind_alt[i], 725 | "sim/weather/wind_altitude_msl_m[%d]", i); 726 | fdr_find(&drs.wind_dir[i], 727 | "sim/weather/wind_direction_degt[%d]", i); 728 | fdr_find(&drs.wind_spd[i], 729 | "sim/weather/wind_speed_kt[%d]", i); 730 | fdr_find(&drs.wind_turb[i], 731 | "sim/weather/turbulence[%d]", i); 732 | fdr_find(&drs.shear_dir[i], 733 | "sim/weather/shear_direction_degt[%d]", i); 734 | fdr_find(&drs.shear_spd[i], 735 | "sim/weather/shear_speed_kt[%d]", i); 736 | } 737 | 738 | fdr_find(&drs.EFIS.instr_brt, 739 | "sim/cockpit2/switches/instrument_brightness_ratio"); 740 | fdr_find(&drs.EFIS.mode, "sim/cockpit2/EFIS/map_mode"); 741 | fdr_find(&drs.EFIS.submode, "sim/cockpit/switches/EFIS_map_submode"); 742 | fdr_find(&drs.EFIS.range, 743 | "sim/cockpit/switches/EFIS_map_range_selector"); 744 | fdr_find(&drs.EFIS.shows_wx, "sim/cockpit/switches/EFIS_shows_weather"); 745 | fdr_find(&drs.EFIS.wx_alpha, "sim/cockpit/switches/EFIS_weather_alpha"); 746 | fdr_find(&drs.EFIS.shows_tcas, "sim/cockpit/switches/EFIS_shows_tcas"); 747 | fdr_find(&drs.EFIS.shows_arpts, 748 | "sim/cockpit/switches/EFIS_shows_airports"); 749 | fdr_find(&drs.EFIS.shows_wpts, 750 | "sim/cockpit/switches/EFIS_shows_waypoints"); 751 | fdr_find(&drs.EFIS.shows_VORs, "sim/cockpit/switches/EFIS_shows_VORs"); 752 | fdr_find(&drs.EFIS.shows_NDBs, "sim/cockpit/switches/EFIS_shows_NDBs"); 753 | fdr_find(&drs.EFIS.kill_map_fms_line, "sim/graphics/misc/kill_map_fms_line"); 754 | 755 | fdr_find(&drs.render_type, "sim/graphics/view/panel_render_type"); 756 | 757 | fdr_find(&drs.temp_sl, "sim/weather/temperature_sealevel_c"); 758 | if (get_xpver() < 12000) { 759 | /* These datarefs have been deprecated in XP12 */ 760 | fdr_find(&drs.temp_tropo_c, "sim/weather/temperature_tropo_c"); 761 | fdr_find(&drs.alt_tropo_m, "sim/weather/tropo_alt_mtr"); 762 | } 763 | for (int i = 0; i < 4; i++) 764 | xp11_atmo.precip_nodes[i] = VECT2(i, 0); 765 | xp11_atmo.precip_nodes[4] = NULL_VECT2; 766 | 767 | if (!reload_gl_prog(&xp11_atmo.cleanup_prog, &cleanup_prog_info) || 768 | !reload_gl_prog(&xp11_atmo.smooth_prog, &smooth_prog_info)) 769 | goto errout; 770 | 771 | xp11_atmo.cleanup_prog_loc.pvm = 772 | glGetUniformLocation(xp11_atmo.cleanup_prog, "pvm"); 773 | xp11_atmo.cleanup_prog_loc.tex = 774 | glGetUniformLocation(xp11_atmo.cleanup_prog, "tex"); 775 | xp11_atmo.cleanup_prog_loc.tex_sz = 776 | glGetUniformLocation(xp11_atmo.cleanup_prog, "tex_sz"); 777 | 778 | xp11_atmo.smooth_prog_loc.pvm = 779 | glGetUniformLocation(xp11_atmo.smooth_prog, "pvm"); 780 | xp11_atmo.smooth_prog_loc.tex = 781 | glGetUniformLocation(xp11_atmo.smooth_prog, "tex"); 782 | xp11_atmo.smooth_prog_loc.tex_sz = 783 | glGetUniformLocation(xp11_atmo.smooth_prog, "tex_sz"); 784 | xp11_atmo.smooth_prog_loc.smooth_val = 785 | glGetUniformLocation(xp11_atmo.smooth_prog, "smooth_val"); 786 | 787 | return (&atmo); 788 | errout: 789 | atmo_xp11_fini(); 790 | return (NULL); 791 | } 792 | 793 | void 794 | atmo_xp11_fini(void) 795 | { 796 | if (!inited) 797 | return; 798 | inited = B_FALSE; 799 | 800 | XPLMUnregisterCommandHandler(debug_cmd, debug_cmd_handler, 0, NULL); 801 | XPLMUnregisterDrawCallback(update_cb, xplm_Phase_Gauges, 0, NULL); 802 | 803 | if (xp11_atmo.pbo != 0) 804 | glDeleteBuffers(1, &xp11_atmo.pbo); 805 | if (xp11_atmo.tmp_fbo[0] != 0) 806 | glDeleteFramebuffers(3, xp11_atmo.tmp_fbo); 807 | if (xp11_atmo.tmp_tex[0] != 0) 808 | glDeleteTextures(3, xp11_atmo.tmp_tex); 809 | if (xp11_atmo.cleanup_prog != 0) 810 | glDeleteProgram(xp11_atmo.cleanup_prog); 811 | if (xp11_atmo.smooth_prog != 0) 812 | glDeleteProgram(xp11_atmo.smooth_prog); 813 | glutils_destroy_quads(&xp11_atmo.efis_quads); 814 | 815 | mutex_destroy(&xp11_atmo.lock); 816 | } 817 | 818 | void 819 | atmo_xp11_set_efis_pos(unsigned x, unsigned y, unsigned w, unsigned h) 820 | { 821 | mutex_enter(&xp11_atmo.lock); 822 | 823 | xp11_atmo.efis_x = x; 824 | xp11_atmo.efis_y = y; 825 | xp11_atmo.efis_w = w; 826 | xp11_atmo.efis_h = h; 827 | 828 | free(xp11_atmo.pixels); 829 | xp11_atmo.pixels = NULL; 830 | 831 | mutex_exit(&xp11_atmo.lock); 832 | } 833 | -------------------------------------------------------------------------------- /src/atmo_xp11.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CDDL HEADER START 3 | * 4 | * This file and its contents are supplied under the terms of the 5 | * Common Development and Distribution License ("CDDL"), version 1.0. 6 | * You may only use this file in accordance with the terms of version 7 | * 1.0 of the CDDL. 8 | * 9 | * A full copy of the text of the CDDL should have accompanied this 10 | * source. A copy of the CDDL is also available via the Internet at 11 | * http://www.illumos.org/license/CDDL. 12 | * 13 | * CDDL HEADER END 14 | */ 15 | /* 16 | * Copyright 2018 Saso Kiselkov. All rights reserved. 17 | */ 18 | 19 | #ifndef _ATMO_XP11_H_ 20 | #define _ATMO_XP11_H_ 21 | 22 | #include "atmo.h" 23 | 24 | #ifdef __cplusplus 25 | extern "C" { 26 | #endif 27 | 28 | atmo_t *atmo_xp11_init(void); 29 | void atmo_xp11_fini(void); 30 | 31 | void atmo_xp11_set_efis_pos(unsigned x, unsigned y, unsigned w, unsigned h); 32 | 33 | #ifdef __cplusplus 34 | } 35 | #endif 36 | 37 | #endif /* _ATMO_H_ */ 38 | -------------------------------------------------------------------------------- /src/dbg_log.c: -------------------------------------------------------------------------------- 1 | /* 2 | * CDDL HEADER START 3 | * 4 | * This file and its contents are supplied under the terms of the 5 | * Common Development and Distribution License ("CDDL"), version 1.0. 6 | * You may only use this file in accordance with the terms of version 7 | * 1.0 of the CDDL. 8 | * 9 | * A full copy of the text of the CDDL should have accompanied this 10 | * source. A copy of the CDDL is also available via the Internet at 11 | * http://www.illumos.org/license/CDDL. 12 | * 13 | * CDDL HEADER END 14 | */ 15 | /* 16 | * Copyright 2017 Saso Kiselkov. All rights reserved. 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | #include "dbg_log.h" 25 | 26 | dbg_level_t dbg_level = { .all = -1 }; 27 | 28 | void 29 | dbg_log_init(const conf_t *conf) 30 | { 31 | memset(&dbg_level, 0, sizeof (dbg_level)); 32 | 33 | conf_get_i(conf, "debug_all", &dbg_level.all); 34 | } 35 | 36 | void 37 | dbg_log_fini(void) 38 | { 39 | } 40 | 41 | void 42 | dbg_log_impl(const char *dbg_class, int level, const char *fmt, ...) 43 | { 44 | va_list ap; 45 | int len; 46 | char *msg; 47 | 48 | va_start(ap, fmt); 49 | len = vsnprintf(NULL, 0, fmt, ap); 50 | va_end(ap); 51 | msg = safe_malloc(len + 1); 52 | va_start(ap, fmt); 53 | vsnprintf(msg, len + 1, fmt, ap); 54 | va_end(ap); 55 | logMsg("[%s/%d]: %s", dbg_class, level, msg); 56 | free(msg); 57 | } 58 | -------------------------------------------------------------------------------- /src/dbg_log.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CDDL HEADER START 3 | * 4 | * This file and its contents are supplied under the terms of the 5 | * Common Development and Distribution License ("CDDL"), version 1.0. 6 | * You may only use this file in accordance with the terms of version 7 | * 1.0 of the CDDL. 8 | * 9 | * A full copy of the text of the CDDL should have accompanied this 10 | * source. A copy of the CDDL is also available via the Internet at 11 | * http://www.illumos.org/license/CDDL. 12 | * 13 | * CDDL HEADER END 14 | */ 15 | /* 16 | * Copyright 2017 Saso Kiselkov. All rights reserved. 17 | */ 18 | 19 | #ifndef _DBG_LOG_H_ 20 | #define _DBG_LOG_H_ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #ifdef __cplusplus 28 | extern "C" { 29 | #endif 30 | 31 | typedef struct { 32 | int all; 33 | } dbg_level_t; 34 | 35 | extern dbg_level_t dbg_level; 36 | 37 | #define dbg_log(dbg_class, level, ...) \ 38 | do { \ 39 | ASSERT(dbg_level.all != -1); \ 40 | if (dbg_level.dbg_class >= level || dbg_level.all >= level) \ 41 | dbg_log_impl(#dbg_class, level, __VA_ARGS__); \ 42 | } while (0) 43 | 44 | void dbg_log_init(const conf_t *conf); 45 | void dbg_log_fini(void); 46 | void dbg_log_impl(const char *dbg_class, int level, const char *fmt, ...) 47 | PRINTF_ATTR(3); 48 | 49 | #ifdef __cplusplus 50 | } 51 | #endif 52 | 53 | #endif /* _DBG_LOG_H_ */ 54 | -------------------------------------------------------------------------------- /src/fontmgr.c: -------------------------------------------------------------------------------- 1 | /* 2 | * CDDL HEADER START 3 | * 4 | * This file and its contents are supplied under the terms of the 5 | * Common Development and Distribution License ("CDDL"), version 1.0. 6 | * You may only use this file in accordance with the terms of version 7 | * 1.0 of the CDDL. 8 | * 9 | * A full copy of the text of the CDDL should have accompanied this 10 | * source. A copy of the CDDL is also available via the Internet at 11 | * http://www.illumos.org/license/CDDL. 12 | * 13 | * CDDL HEADER END 14 | */ 15 | /* 16 | * Copyright 2018 Saso Kiselkov. All rights reserved. 17 | */ 18 | 19 | #include 20 | #include FT_FREETYPE_H 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #include "fontmgr.h" 27 | 28 | typedef struct { 29 | const char *file; 30 | FT_Face ft_font; 31 | cairo_font_face_t *cr_font; 32 | } font_info_t; 33 | 34 | static bool_t inited = B_FALSE; 35 | static FT_Library ft = NULL; 36 | static font_info_t fonts[NUM_FONTMGR_FONTS] = { 37 | { "Inconsolata/Inconsolata-Regular.ttf" }, /* FONTMGR_EFIS_FONT */ 38 | }; 39 | 40 | bool_t 41 | fontmgr_init(const char *xpdir, const char *plugindir) 42 | { 43 | char *fontdir = mkpathname(xpdir, plugindir, "fonts", NULL); 44 | FT_Error err; 45 | 46 | ASSERT(!inited); 47 | inited = B_TRUE; 48 | 49 | if ((err = FT_Init_FreeType(&ft)) != 0) { 50 | logMsg("Error initializing FreeType library: %s", 51 | ft_err2str(err)); 52 | goto errout; 53 | } 54 | 55 | for (int i = 0; i < NUM_FONTMGR_FONTS; i++) { 56 | font_info_t *f = &fonts[i]; 57 | 58 | ASSERT(f->file != NULL); 59 | if (!try_load_font(fontdir, f->file, ft, &f->ft_font, 60 | &f->cr_font)) 61 | goto errout; 62 | } 63 | 64 | lacf_free(fontdir); 65 | 66 | return (B_TRUE); 67 | errout: 68 | lacf_free(fontdir); 69 | fontmgr_fini(); 70 | return (B_FALSE); 71 | } 72 | 73 | void 74 | fontmgr_fini(void) 75 | { 76 | if (!inited) 77 | return; 78 | inited = B_FALSE; 79 | 80 | for (int i = 0; i < NUM_FONTMGR_FONTS; i++) { 81 | if (fonts[i].cr_font != NULL) { 82 | cairo_font_face_destroy(fonts[i].cr_font); 83 | fonts[i].cr_font = NULL; 84 | } 85 | if (fonts[i].ft_font != NULL) { 86 | FT_Done_Face(fonts[i].ft_font); 87 | fonts[i].ft_font = NULL; 88 | } 89 | } 90 | if (ft != NULL) { 91 | FT_Done_FreeType(ft); 92 | ft = NULL; 93 | } 94 | } 95 | 96 | cairo_font_face_t * 97 | fontmgr_get(fontmgr_font_t f) 98 | { 99 | ASSERT(fonts[f].cr_font != NULL); 100 | return (fonts[f].cr_font); 101 | } 102 | -------------------------------------------------------------------------------- /src/fontmgr.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CDDL HEADER START 3 | * 4 | * This file and its contents are supplied under the terms of the 5 | * Common Development and Distribution License ("CDDL"), version 1.0. 6 | * You may only use this file in accordance with the terms of version 7 | * 1.0 of the CDDL. 8 | * 9 | * A full copy of the text of the CDDL should have accompanied this 10 | * source. A copy of the CDDL is also available via the Internet at 11 | * http://www.illumos.org/license/CDDL. 12 | * 13 | * CDDL HEADER END 14 | */ 15 | /* 16 | * Copyright 2018 Saso Kiselkov. All rights reserved. 17 | */ 18 | 19 | #ifndef _FONTMGR_H_ 20 | #define _FONTMGR_H_ 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #ifdef __cplusplus 27 | extern "C" { 28 | #endif 29 | 30 | typedef enum { 31 | FONTMGR_EFIS_FONT, 32 | NUM_FONTMGR_FONTS 33 | } fontmgr_font_t; 34 | 35 | bool_t fontmgr_init(const char *xpdir, const char *plugindir); 36 | void fontmgr_fini(void); 37 | 38 | cairo_font_face_t *fontmgr_get(fontmgr_font_t f); 39 | 40 | #ifdef __cplusplus 41 | } 42 | #endif 43 | 44 | #endif /* _FONTMGR_H_ */ 45 | -------------------------------------------------------------------------------- /src/glpriv.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CDDL HEADER START 3 | * 4 | * This file and its contents are supplied under the terms of the 5 | * Common Development and Distribution License ("CDDL"), version 1.0. 6 | * You may only use this file in accordance with the terms of version 7 | * 1.0 of the CDDL. 8 | * 9 | * A full copy of the text of the CDDL should have accompanied this 10 | * source. A copy of the CDDL is also available via the Internet at 11 | * http://www.illumos.org/license/CDDL. 12 | * 13 | * CDDL HEADER END 14 | */ 15 | /* 16 | * Copyright 2019 Saso Kiselkov. All rights reserved. 17 | */ 18 | 19 | #ifndef _GLPRIV_H_ 20 | #define _GLPRIV_H_ 21 | 22 | #include 23 | #include 24 | 25 | #include "xplane.h" 26 | 27 | #ifdef __cplusplus 28 | extern "C" { 29 | #endif 30 | 31 | static bool_t 32 | reload_gl_prog(GLint *prog, const shader_prog_info_t *info) 33 | { 34 | GLint new_prog; 35 | char *path = mkpathname(get_xpdir(), get_plugindir(), "data", "bin", 36 | NULL); 37 | 38 | ASSERT(path != NULL); 39 | new_prog = shader_prog_from_info(path, info); 40 | lacf_free(path); 41 | if (new_prog == 0) 42 | return (B_FALSE); 43 | if (*prog != 0 && new_prog != *prog) 44 | glDeleteProgram(*prog); 45 | *prog = new_prog; 46 | 47 | return (B_TRUE); 48 | } 49 | 50 | #ifdef __cplusplus 51 | } 52 | #endif 53 | 54 | #endif /* _GLPRIV_H_ */ 55 | -------------------------------------------------------------------------------- /src/prototypes/prototype.c: -------------------------------------------------------------------------------- 1 | /* 2 | * CDDL HEADER START 3 | * 4 | * This file and its contents are supplied under the terms of the 5 | * Common Development and Distribution License ("CDDL"), version 1.0. 6 | * You may only use this file in accordance with the terms of version 7 | * 1.0 of the CDDL. 8 | * 9 | * A full copy of the text of the CDDL should have accompanied this 10 | * source. A copy of the CDDL is also available via the Internet at 11 | * http://www.illumos.org/license/CDDL. 12 | * 13 | * CDDL HEADER END 14 | */ 15 | /* 16 | * Copyright 2018 Saso Kiselkov. All rights reserved. 17 | */ 18 | 19 | #include "prototype.h" 20 | -------------------------------------------------------------------------------- /src/prototypes/prototype.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CDDL HEADER START 3 | * 4 | * This file and its contents are supplied under the terms of the 5 | * Common Development and Distribution License ("CDDL"), version 1.0. 6 | * You may only use this file in accordance with the terms of version 7 | * 1.0 of the CDDL. 8 | * 9 | * A full copy of the text of the CDDL should have accompanied this 10 | * source. A copy of the CDDL is also available via the Internet at 11 | * http://www.illumos.org/license/CDDL. 12 | * 13 | * CDDL HEADER END 14 | */ 15 | /* 16 | * Copyright 2018 Saso Kiselkov. All rights reserved. 17 | */ 18 | 19 | #ifndef _FILE_H_ 20 | #define _FILE_H_ 21 | 22 | #ifdef __cplusplus 23 | extern "C" { 24 | #endif 25 | 26 | #ifdef __cplusplus 27 | } 28 | #endif 29 | 30 | #endif /* _FILE_H_ */ 31 | -------------------------------------------------------------------------------- /src/standalone.c: -------------------------------------------------------------------------------- 1 | /* 2 | * CDDL HEADER START 3 | * 4 | * This file and its contents are supplied under the terms of the 5 | * Common Development and Distribution License ("CDDL"), version 1.0. 6 | * You may only use this file in accordance with the terms of version 7 | * 1.0 of the CDDL. 8 | * 9 | * A full copy of the text of the CDDL should have accompanied this 10 | * source. A copy of the CDDL is also available via the Internet at 11 | * http://www.illumos.org/license/CDDL. 12 | * 13 | * CDDL HEADER END 14 | */ 15 | /* 16 | * Copyright 2018 Saso Kiselkov. All rights reserved. 17 | */ 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | 34 | #include "../api/openwxr/wxr_intf.h" 35 | #include "../api/openwxr/xplane_api.h" 36 | 37 | #include "fontmgr.h" 38 | #include "standalone.h" 39 | 40 | #define MAX_SCREENS 4 41 | #define MAX_MODES 16 42 | #define MAX_COLORS 8 43 | 44 | #define EFIS_OFF_X 16 45 | #define EFIS_OFF_Y 15 46 | #define EFIS_WIDTH 194 47 | #define EFIS_HEIGHT 268 48 | #define MAX_DR_NAME 128 49 | 50 | #define NUM_DELAY_STEPS 10 51 | 52 | #define COLOR(c, scr) ((c) * ((pow(4.0, (scr)->brt / 0.75)) / 4.0)) 53 | #define CYAN_RGB(scr) COLOR(0, (scr)), COLOR(0.66, (scr)), COLOR(0.66, (scr)) 54 | 55 | static bool_t inited = B_FALSE; 56 | 57 | typedef enum { 58 | TEXT_ALIGN_LEFT, 59 | TEXT_ALIGN_CENTER, 60 | TEXT_ALIGN_RIGHT 61 | } text_align_t; 62 | 63 | typedef struct { 64 | char name[MAX_DR_NAME]; 65 | bool_t have_dr; 66 | dr_t dr; 67 | } delayed_dr_t; 68 | 69 | typedef struct { 70 | double value; 71 | double delay; 72 | double value_stack[NUM_DELAY_STEPS]; 73 | double stack_adv_t; 74 | } delayed_ctl_t; 75 | 76 | typedef enum { 77 | PANEL_RENDER_TYPE_2D = 0, 78 | PANEL_RENDER_TYPE_3D_UNLIT = 1, 79 | PANEL_RENDER_TYPE_3D_LIT = 2 80 | } panel_render_type_t; 81 | 82 | typedef struct wxr_sys_s wxr_sys_t; 83 | 84 | typedef struct { 85 | double x, y; 86 | double w, h; 87 | double underscan; 88 | struct wxr_sys_s *sys; 89 | mt_cairo_render_t *mtcr; 90 | double fps; 91 | 92 | double power; 93 | double power_on_rate; 94 | double power_off_rate; 95 | double brt; 96 | 97 | delayed_dr_t power_dr; 98 | delayed_dr_t power_sw_dr; 99 | delayed_ctl_t power_sw_ctl; 100 | delayed_dr_t brt_dr; 101 | double scr_temp; 102 | } wxr_scr_t; 103 | 104 | typedef struct { 105 | char name[16]; 106 | vect2_t stab_lim; 107 | unsigned num_colors; 108 | wxr_color_t colors[MAX_COLORS]; 109 | wxr_color_t base_colors[MAX_COLORS]; 110 | } mode_aux_info_t; 111 | 112 | struct wxr_sys_s { 113 | double power_on_time; 114 | double power_on_delay; 115 | 116 | mutex_t mode_lock; 117 | unsigned num_modes; 118 | unsigned cur_mode; 119 | wxr_conf_t modes[MAX_MODES]; 120 | mode_aux_info_t aux[MAX_MODES]; 121 | 122 | unsigned num_screens; 123 | wxr_scr_t screens[MAX_SCREENS]; 124 | 125 | unsigned efis_xywh[4]; 126 | 127 | delayed_dr_t power_dr; 128 | delayed_dr_t power_sw_dr; 129 | delayed_dr_t mode_dr; 130 | delayed_dr_t tilt_dr; 131 | delayed_dr_t range_dr; 132 | delayed_dr_t gain_dr; 133 | 134 | delayed_ctl_t power_sw_ctl; 135 | delayed_ctl_t mode_ctl; 136 | delayed_ctl_t range_ctl; 137 | delayed_ctl_t tilt_ctl; 138 | 139 | double range; 140 | double gain_auto_pos; 141 | double tilt; 142 | double tilt_rate; 143 | 144 | bool_t shared_egpws; 145 | const egpws_intf_t *terr; 146 | }; 147 | 148 | static wxr_sys_t sys = { 0 }; 149 | static XPLMWindowID debug_win = NULL; 150 | static XPLMCommandRef open_debug_cmd = NULL; 151 | 152 | static struct { 153 | dr_t lat, lon, elev; 154 | dr_t sim_time; 155 | dr_t panel_render_type; 156 | dr_t pitch, roll, hdg; 157 | } drs; 158 | 159 | static XPLMPluginID openwxr = XPLM_NO_PLUGIN_ID; 160 | static void *atmo = NULL; 161 | static void *wxr = NULL; 162 | static openwxr_intf_t *wxr_intf = NULL; 163 | 164 | static const egpws_conf_t egpws_conf = { .type = EGPWS_DB_ONLY }; 165 | static const egpws_range_t egpws_ranges[] = { 166 | { NM2MET(10), 100 }, 167 | { NM2MET(25), 175 }, 168 | { NM2MET(50), 250 }, 169 | { NM2MET(100), 500 }, 170 | { NM2MET(200), 1000 }, 171 | { NAN, NAN } 172 | }; 173 | 174 | #define WXR_RES_X 320 175 | #define WXR_RES_Y 240 176 | 177 | #define MIN_GAIN 0.5 /* gain argument to WXR */ 178 | #define MAX_GAIN 1.5 /* gain argument to WXR */ 179 | #define DFL_GAIN 1.0 /* gain argument to WXR */ 180 | 181 | static void draw_debug_win(XPLMWindowID win, void *refcon); 182 | 183 | #define DELAYED_DR_OP(delayed_dr, op) \ 184 | do { \ 185 | delayed_dr_t *dd = (delayed_dr); \ 186 | if (*dd->name != 0) { \ 187 | if (!dd->have_dr) { \ 188 | dd->have_dr = dr_find(&dd->dr, "%s", \ 189 | dd->name); \ 190 | } \ 191 | if (dd->have_dr) { \ 192 | op; \ 193 | } \ 194 | } \ 195 | } while (0) 196 | 197 | static void 198 | delayed_ctl_set(delayed_ctl_t *ctl, double new_value) 199 | { 200 | double now = dr_getf(&drs.sim_time); 201 | 202 | for (double delta = now - ctl->stack_adv_t; 203 | delta > ctl->delay / NUM_DELAY_STEPS; 204 | delta -= ctl->delay / NUM_DELAY_STEPS) { 205 | ctl->stack_adv_t = now; 206 | ctl->value = ctl->value_stack[0]; 207 | for (int i = 0; i < NUM_DELAY_STEPS - 1; i++) 208 | ctl->value_stack[i] = ctl->value_stack[i + 1]; 209 | ctl->value_stack[NUM_DELAY_STEPS - 1] = new_value; 210 | } 211 | } 212 | 213 | static double delayed_ctl_get(delayed_ctl_t *ctl) UNUSED_ATTR; 214 | 215 | static double 216 | delayed_ctl_get(delayed_ctl_t *ctl) 217 | { 218 | return (ctl->value); 219 | } 220 | 221 | static int 222 | delayed_ctl_geti(delayed_ctl_t *ctl) 223 | { 224 | return (round(ctl->value)); 225 | } 226 | 227 | static int 228 | open_debug_win(XPLMCommandRef ref, XPLMCommandPhase phase, void *refcon) 229 | { 230 | UNUSED(ref); 231 | UNUSED(phase); 232 | UNUSED(refcon); 233 | 234 | if (debug_win == NULL) { 235 | XPLMCreateWindow_t cr = { 236 | .structSize = sizeof (cr), .visible = B_TRUE, 237 | .left = 100, .top = 400, .right = 400, .bottom = 100, 238 | .drawWindowFunc = draw_debug_win, 239 | .decorateAsFloatingWindow = 240 | xplm_WindowDecorationRoundRectangle, 241 | .layer = xplm_WindowLayerFloatingWindows 242 | }; 243 | debug_win = XPLMCreateWindowEx(&cr); 244 | } 245 | XPLMSetWindowIsVisible(debug_win, B_TRUE); 246 | XPLMBringWindowToFront(debug_win); 247 | 248 | return (1); 249 | } 250 | 251 | static void 252 | wxr_config(float d_t, const wxr_conf_t *mode, mode_aux_info_t *aux) 253 | { 254 | unsigned range = 0; 255 | double tilt = 0, gain_ctl = 0.5; 256 | double gain = 0; 257 | bool_t power_on = B_TRUE, power_sw_on = B_TRUE, stby = B_FALSE; 258 | geo_pos3_t pos = 259 | GEO_POS3(dr_getf(&drs.lat), dr_getf(&drs.lon), dr_getf(&drs.elev)); 260 | vect3_t orient = 261 | VECT3(dr_getf(&drs.pitch), dr_getf(&drs.hdg), dr_getf(&drs.roll)); 262 | 263 | DELAYED_DR_OP(&sys.power_dr, 264 | power_on = (dr_geti(&sys.power_dr.dr) != 0)); 265 | DELAYED_DR_OP(&sys.power_sw_dr, 266 | power_sw_on = (dr_geti(&sys.power_sw_dr.dr) != 0)); 267 | delayed_ctl_set(&sys.power_sw_ctl, power_sw_on); 268 | power_sw_on = delayed_ctl_geti(&sys.power_sw_ctl); 269 | 270 | if (power_on && power_sw_on) { 271 | double now = dr_getf(&drs.sim_time); 272 | if (sys.power_on_time == 0) 273 | sys.power_on_time = now; 274 | stby = (now - sys.power_on_time < sys.power_on_delay); 275 | } else { 276 | stby = B_TRUE; 277 | sys.power_on_time = 0; 278 | } 279 | 280 | wxr_intf->set_standby(wxr, stby); 281 | wxr_intf->set_stab(wxr, aux->stab_lim.x, aux->stab_lim.y); 282 | wxr_intf->set_acf_pos(wxr, pos, orient); 283 | 284 | if (sys.num_screens > 0) 285 | wxr_intf->set_brightness(wxr, sys.screens[0].brt / 0.75); 286 | 287 | wxr_intf->set_colors(wxr, aux->colors, aux->num_colors); 288 | 289 | DELAYED_DR_OP(&sys.range_dr, range = dr_geti(&sys.range_dr.dr)); 290 | range = clampi(range, 0, mode->num_ranges - 1); 291 | delayed_ctl_set(&sys.range_ctl, range); 292 | range = delayed_ctl_geti(&sys.range_ctl); 293 | sys.range = mode->ranges[range]; 294 | 295 | if (wxr_intf->get_scale(wxr) != range) { 296 | bool_t new_scale = (mode->ranges[range] != 297 | mode->ranges[wxr_intf->get_scale(wxr)]); 298 | 299 | if (new_scale) 300 | wxr_intf->clear_screen(wxr); 301 | wxr_intf->set_scale(wxr, range); 302 | if (new_scale) 303 | wxr_intf->clear_screen(wxr); 304 | } 305 | 306 | DELAYED_DR_OP(&sys.tilt_dr, tilt = dr_getf(&sys.tilt_dr.dr)); 307 | delayed_ctl_set(&sys.tilt_ctl, tilt); 308 | tilt = delayed_ctl_get(&sys.tilt_ctl); 309 | FILTER_IN_LIN(sys.tilt, tilt, d_t, sys.tilt_rate); 310 | wxr_intf->set_ant_pitch(wxr, sys.tilt); 311 | 312 | DELAYED_DR_OP(&sys.gain_dr, 313 | gain_ctl = dr_getf(&sys.gain_dr.dr)); 314 | if (fabs(gain_ctl - sys.gain_auto_pos) < 1e-5) 315 | gain = DFL_GAIN; 316 | else 317 | gain = wavg(MIN_GAIN, MAX_GAIN, clamp(gain_ctl, 0, 1)); 318 | wxr_intf->set_gain(wxr, gain); 319 | } 320 | 321 | static float 322 | floop_cb(float d_t, float elapsed, int counter, void *refcon) 323 | { 324 | const wxr_conf_t *mode; 325 | mode_aux_info_t *aux; 326 | unsigned new_mode = 0; 327 | 328 | UNUSED(elapsed); 329 | UNUSED(counter); 330 | UNUSED(refcon); 331 | 332 | if (sys.shared_egpws && !sys.terr->is_inited()) 333 | return (-1); 334 | 335 | /* 336 | * Set up OpenGPWS as we need it: 337 | * 1) DB-ONLY mode (no active EGPWS callouts) 338 | * 2) enable sound playback (for our PWS callouts) 339 | * 3) position known to run the terrain DB 340 | * 4) nav systems on 341 | */ 342 | if (!sys.shared_egpws) { 343 | sys.terr->set_state(&egpws_conf); 344 | sys.terr->set_sound_inh(B_FALSE); 345 | sys.terr->set_ranges(egpws_ranges); 346 | sys.terr->set_pos_ok(B_TRUE); 347 | sys.terr->set_nav_on(B_TRUE, B_TRUE); 348 | } 349 | 350 | mutex_enter(&sys.mode_lock); 351 | DELAYED_DR_OP(&sys.mode_dr, new_mode = dr_geti(&sys.mode_dr.dr)); 352 | new_mode = MIN(new_mode, sys.num_modes - 1); 353 | delayed_ctl_set(&sys.mode_ctl, new_mode); 354 | sys.cur_mode = delayed_ctl_geti(&sys.mode_ctl); 355 | mutex_exit(&sys.mode_lock); 356 | 357 | mode = &sys.modes[sys.cur_mode]; 358 | aux = &sys.aux[sys.cur_mode]; 359 | if (wxr != NULL && mode->num_ranges == 0) { 360 | wxr_intf->fini(wxr); 361 | wxr = NULL; 362 | } 363 | if (wxr == NULL && mode->num_ranges != 0) { 364 | wxr = wxr_intf->init(mode, atmo); 365 | ASSERT(wxr != NULL); 366 | } 367 | if (wxr != NULL) 368 | wxr_config(d_t, mode, aux); 369 | 370 | for (unsigned i = 0; wxr != NULL && i < sys.num_screens; i++) { 371 | bool_t power = B_TRUE, sw = B_TRUE; 372 | wxr_scr_t *scr = &sys.screens[i]; 373 | double brt = 0.75; 374 | 375 | DELAYED_DR_OP(&scr->power_dr, 376 | power = (dr_geti(&scr->power_dr.dr) != 0)); 377 | DELAYED_DR_OP(&scr->power_sw_dr, 378 | sw = (dr_geti(&scr->power_sw_dr.dr) != 0)); 379 | delayed_ctl_set(&scr->power_sw_ctl, sw); 380 | sw = delayed_ctl_geti(&scr->power_sw_ctl); 381 | if (power && sw) { 382 | double rate = (scr->power_on_rate / 383 | (1 + 50 * POW3(scr->scr_temp))); 384 | 385 | FILTER_IN(scr->scr_temp, 1, d_t, 10); 386 | FILTER_IN(scr->power, 1, d_t, rate); 387 | } else { 388 | FILTER_IN(scr->scr_temp, 0, d_t, 600); 389 | FILTER_IN(scr->power, 0, d_t, scr->power_off_rate); 390 | } 391 | if (scr->power < 0.01 || scr->power > 0.99) 392 | mt_cairo_render_set_fps(scr->mtcr, scr->fps); 393 | else 394 | mt_cairo_render_set_fps(scr->mtcr, 20); 395 | 396 | DELAYED_DR_OP(&scr->brt_dr, brt = dr_getf(&scr->brt_dr.dr)); 397 | if (brt > scr->brt) 398 | FILTER_IN(scr->brt, brt, d_t, 1); 399 | else 400 | FILTER_IN(scr->brt, brt, d_t, 0.2); 401 | } 402 | 403 | return (-1); 404 | } 405 | 406 | static int 407 | draw_cb(XPLMDrawingPhase phase, int before, void *refcon) 408 | { 409 | egpws_render_t render = { .do_draw = B_FALSE }; 410 | 411 | UNUSED(phase); 412 | UNUSED(before); 413 | UNUSED(refcon); 414 | 415 | if (dr_geti(&drs.panel_render_type) != PANEL_RENDER_TYPE_3D_LIT) 416 | return (1); 417 | 418 | /* 419 | * Even though we don't draw tiles, we need to let OpenGPWS when 420 | * to perform tile setup. 421 | */ 422 | sys.terr->terr_render(&render); 423 | 424 | for (unsigned i = 0; wxr != NULL && i < sys.num_screens; i++) { 425 | wxr_scr_t *scr = &sys.screens[i]; 426 | double center_x = scr->x + scr->w / 2; 427 | double sz = scr->h * scr->underscan; 428 | 429 | wxr_intf->draw(wxr, VECT2(center_x - sz, scr->y), 430 | VECT2(2 * sz, sz)); 431 | mt_cairo_render_draw(scr->mtcr, VECT2(scr->x, scr->y), 432 | VECT2(scr->w, scr->h)); 433 | } 434 | 435 | return (1); 436 | } 437 | 438 | static void 439 | draw_debug_win(XPLMWindowID win, void *refcon) 440 | { 441 | int left, top, right, bottom, width, height; 442 | unsigned scr_id = (intptr_t)refcon; 443 | wxr_scr_t *scr; 444 | 445 | ASSERT3U(scr_id, <, sys.num_screens); 446 | scr = &sys.screens[scr_id]; 447 | 448 | XPLMGetWindowGeometry(win, &left, &top, &right, &bottom); 449 | width = right - left; 450 | height = top - bottom; 451 | 452 | if (wxr != NULL) 453 | wxr_intf->draw(wxr, VECT2(left, bottom), VECT2(width, height)); 454 | mt_cairo_render_draw(scr->mtcr, VECT2(left, bottom), 455 | VECT2(width, height)); 456 | } 457 | 458 | static void 459 | align_text(cairo_t *cr, const char *buf, double x, double y, text_align_t how) 460 | { 461 | cairo_text_extents_t te; 462 | 463 | cairo_text_extents(cr, buf, &te); 464 | switch (how) { 465 | case TEXT_ALIGN_LEFT: 466 | cairo_move_to(cr, x - te.x_bearing, 467 | y - te.height / 2 - te.y_bearing); 468 | break; 469 | case TEXT_ALIGN_CENTER: 470 | cairo_move_to(cr, x - te.width / 2 - te.x_bearing, 471 | y - te.height / 2 - te.y_bearing); 472 | break; 473 | case TEXT_ALIGN_RIGHT: 474 | cairo_move_to(cr, x - te.width - te.x_bearing, 475 | y - te.height / 2 - te.y_bearing); 476 | break; 477 | } 478 | } 479 | 480 | static void 481 | render_ui(cairo_t *cr, wxr_scr_t *scr) 482 | { 483 | enum { FONT_SZ = 20, LINE_HEIGHT = 20, TOP_OFFSET = -FONT_SZ / 5 }; 484 | char buf[16]; 485 | double dashes[] = { 5, 5 }; 486 | char mode_name[16]; 487 | 488 | cairo_set_source_rgb(cr, CYAN_RGB(scr)); 489 | cairo_set_line_width(cr, 1); 490 | for (int angle = -90; angle <= 90; angle += 30) { 491 | cairo_save(cr); 492 | cairo_rotate(cr, DEG2RAD(angle)); 493 | cairo_move_to(cr, 0, 0); 494 | cairo_rel_line_to(cr, 0, -WXR_RES_Y); 495 | cairo_stroke(cr); 496 | cairo_restore(cr); 497 | } 498 | 499 | cairo_set_dash(cr, dashes, 2, 0); 500 | for (int i = 0; i < 4; i++) { 501 | cairo_arc(cr, 0, 0, (WXR_RES_Y / 4) * (i + 1), DEG2RAD(180), 502 | DEG2RAD(360)); 503 | cairo_stroke(cr); 504 | } 505 | cairo_set_dash(cr, NULL, 0, 0); 506 | 507 | cairo_set_font_face(cr, fontmgr_get(FONTMGR_EFIS_FONT)); 508 | cairo_set_font_size(cr, FONT_SZ); 509 | 510 | snprintf(buf, sizeof (buf), "RNG %3.0f", MET2NM(sys.range)); 511 | align_text(cr, buf, -WXR_RES_X / 2, -WXR_RES_Y + TOP_OFFSET, 512 | TEXT_ALIGN_LEFT); 513 | cairo_show_text(cr, buf); 514 | 515 | mutex_enter(&sys.mode_lock); 516 | strlcpy(mode_name, sys.aux[sys.cur_mode].name, sizeof (mode_name)); 517 | mutex_exit(&sys.mode_lock); 518 | 519 | align_text(cr, mode_name, -WXR_RES_X / 2, -WXR_RES_Y + TOP_OFFSET + 520 | LINE_HEIGHT, TEXT_ALIGN_LEFT); 521 | cairo_show_text(cr, mode_name); 522 | 523 | snprintf(buf, sizeof (buf), "MRK %3.0f", MET2NM(sys.range / 4)); 524 | align_text(cr, buf, WXR_RES_X / 2, -WXR_RES_Y + TOP_OFFSET, 525 | TEXT_ALIGN_RIGHT); 526 | cairo_show_text(cr, buf); 527 | 528 | if (wxr != NULL) { 529 | double tilt = wxr_intf->get_ant_pitch(wxr); 530 | 531 | if (tilt >= 0.05) 532 | snprintf(buf, sizeof (buf), "%.1f\u2191", tilt); 533 | else if (tilt <= -0.05) 534 | snprintf(buf, sizeof (buf), "%.1f\u2193", ABS(tilt)); 535 | else 536 | snprintf(buf, sizeof (buf), "0.0\u00a0"); 537 | align_text(cr, buf, WXR_RES_X / 2, -WXR_RES_Y + TOP_OFFSET + 538 | LINE_HEIGHT, TEXT_ALIGN_RIGHT); 539 | cairo_show_text(cr, buf); 540 | } 541 | } 542 | 543 | static void 544 | render_cb(cairo_t *cr, unsigned w, unsigned h, void *userinfo) 545 | { 546 | wxr_scr_t *scr = userinfo; 547 | 548 | cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); 549 | cairo_paint(cr); 550 | cairo_set_operator(cr, CAIRO_OPERATOR_OVER); 551 | 552 | cairo_save(cr); 553 | 554 | cairo_scale(cr, w / (double)WXR_RES_X, h / (double)WXR_RES_Y); 555 | cairo_translate(cr, WXR_RES_X / 2, WXR_RES_Y); 556 | cairo_scale(cr, scr->underscan, scr->underscan); 557 | 558 | render_ui(cr, scr); 559 | 560 | cairo_restore(cr); 561 | 562 | if (scr->power < 0.99) { 563 | cairo_set_source_rgba(cr, 0, 0, 0, 1.0 - scr->power); 564 | cairo_paint(cr); 565 | } 566 | } 567 | 568 | static void 569 | parse_conf_file(const conf_t *conf) 570 | { 571 | const char *str; 572 | 573 | conf_get_i(conf, "efis/x", (int *)&sys.efis_xywh[0]); 574 | conf_get_i(conf, "efis/y", (int *)&sys.efis_xywh[1]); 575 | sys.efis_xywh[0] = clampi(sys.efis_xywh[0] + EFIS_OFF_X, 0, 2048); 576 | sys.efis_xywh[1] = clampi(sys.efis_xywh[1] + EFIS_OFF_Y, 0, 2048); 577 | sys.efis_xywh[2] = EFIS_WIDTH; 578 | sys.efis_xywh[3] = EFIS_HEIGHT; 579 | 580 | conf_get_i(conf, "num_modes", (int *)&sys.num_modes); 581 | sys.num_modes = clampi(sys.num_modes, 0, MAX_MODES); 582 | 583 | for (unsigned i = 0; i < sys.num_modes; i++) { 584 | wxr_conf_t *mode = &sys.modes[i]; 585 | mode_aux_info_t *aux = &sys.aux[i]; 586 | 587 | conf_get_i(conf, "res/x", (int *)&mode->res_x); 588 | conf_get_i(conf, "res/y", (int *)&mode->res_y); 589 | mode->res_x = clampi(mode->res_x, 64, 512); 590 | mode->res_y = clampi(mode->res_y, 64, 512); 591 | 592 | conf_get_d_v(conf, "mode/%d/beam_shape/x", 593 | &mode->beam_shape.x, i); 594 | conf_get_d_v(conf, "mode/%d/beam_shape/y", 595 | &mode->beam_shape.y, i); 596 | mode->beam_shape.x = clamp(mode->beam_shape.x, 1, 90); 597 | mode->beam_shape.y = clamp(mode->beam_shape.y, 1, 90); 598 | 599 | conf_get_d_v(conf, "mode/%d/scan_time", 600 | &mode->scan_time, i); 601 | mode->scan_time = clamp(mode->scan_time, 0.1, 100); 602 | 603 | conf_get_d_v(conf, "mode/%d/scan_angle", 604 | &mode->scan_angle, i); 605 | mode->scan_angle = clamp(mode->scan_angle, 1, 180); 606 | 607 | conf_get_d_v(conf, "mode/%d/smear/x", &mode->smear.x, i); 608 | conf_get_d_v(conf, "mode/%d/smear/y", &mode->smear.y, i); 609 | mode->smear.x = clamp(mode->smear.x, 0, 100); 610 | mode->smear.y = clamp(mode->smear.y, 0, 100); 611 | 612 | conf_get_d_v(conf, "mode/%d/parked_azi", 613 | &mode->parked_azi, i); 614 | mode->parked_azi = clamp(mode->parked_azi, 615 | -mode->scan_angle / 2, mode->scan_angle / 2); 616 | 617 | conf_get_i(conf, "num_ranges", (int *)&mode->num_ranges); 618 | mode->num_ranges = clampi(mode->num_ranges, 0, WXR_MAX_RANGES); 619 | for (unsigned j = 0; j < mode->num_ranges; j++) 620 | conf_get_d_v(conf, "range/%d", &mode->ranges[j], j); 621 | 622 | conf_get_d_v(conf, "mode/%d/stab_lim/x", &aux->stab_lim.x, 623 | i); 624 | conf_get_d_v(conf, "mode/%d/stab_lim/y", &aux->stab_lim.y, 625 | i); 626 | 627 | conf_get_i_v(conf, "mode/%d/num_colors", 628 | (int *)&aux->num_colors, i); 629 | aux->num_colors = MIN(aux->num_colors, MAX_COLORS); 630 | for (unsigned j = 0; j < aux->num_colors; j++) { 631 | conf_get_d_v(conf, "mode/%d/colors/%d/thresh", 632 | &aux->colors[j].min_val, i, j); 633 | if (conf_get_str_v(conf, "mode/%d/colors/%d/rgba", 634 | &str, i, j)) { 635 | (void) sscanf(str, "%x", &aux->colors[j].rgba); 636 | aux->colors[j].rgba = BE32(aux->colors[j].rgba); 637 | } 638 | /* back up the master color palette */ 639 | memcpy(aux->base_colors, aux->colors, 640 | sizeof (aux->base_colors)); 641 | } 642 | 643 | if (conf_get_str_v(conf, "mode/%d/name", &str, i)) 644 | strlcpy(aux->name, str, sizeof (aux->name)); 645 | } 646 | 647 | if (conf_get_str(conf, "power_dr", &str)) 648 | strlcpy(sys.power_dr.name, str, sizeof (sys.power_dr.name)); 649 | if (conf_get_str(conf, "power_sw_dr", &str)) { 650 | strlcpy(sys.power_sw_dr.name, str, 651 | sizeof (sys.power_sw_dr.name)); 652 | } 653 | conf_get_d(conf, "power_on_delay", &sys.power_on_delay); 654 | if (conf_get_str(conf, "range_dr", &str)) 655 | strlcpy(sys.range_dr.name, str, sizeof (sys.range_dr.name)); 656 | if (conf_get_str(conf, "tilt_dr", &str)) 657 | strlcpy(sys.tilt_dr.name, str, sizeof (sys.tilt_dr.name)); 658 | if (conf_get_str(conf, "mode_dr", &str)) 659 | strlcpy(sys.mode_dr.name, str, sizeof (sys.mode_dr.name)); 660 | if (conf_get_str(conf, "gain_dr", &str)) 661 | strlcpy(sys.gain_dr.name, str, sizeof (sys.gain_dr.name)); 662 | conf_get_d(conf, "gain_auto_pos", &sys.gain_auto_pos); 663 | conf_get_d(conf, "tilt_rate", &sys.tilt_rate); 664 | sys.tilt_rate = MAX(sys.tilt_rate, 1); 665 | 666 | conf_get_d(conf, "ctl/delay/power_sw", &sys.power_sw_ctl.delay); 667 | conf_get_d(conf, "ctl/delay/mode", &sys.mode_ctl.delay); 668 | conf_get_d(conf, "ctl/delay/tilt", &sys.tilt_ctl.delay); 669 | conf_get_d(conf, "ctl/delay/range", &sys.range_ctl.delay); 670 | 671 | conf_get_i(conf, "num_screens", (int *)&sys.num_screens); 672 | sys.num_screens = clampi(sys.num_screens, 0, MAX_SCREENS); 673 | for (unsigned i = 0; i < sys.num_screens; i++) { 674 | wxr_scr_t *scr = &sys.screens[i]; 675 | const char *str; 676 | 677 | conf_get_d_v(conf, "scr/%d/x", &scr->x, i); 678 | conf_get_d_v(conf, "scr/%d/y", &scr->y, i); 679 | conf_get_d_v(conf, "scr/%d/w", &scr->w, i); 680 | conf_get_d_v(conf, "scr/%d/h", &scr->h, i); 681 | 682 | conf_get_d_v(conf, "scr/%d/fps", &scr->fps, i); 683 | scr->fps = clamp(scr->fps, 0.1, 100); 684 | 685 | conf_get_d_v(conf, "ctl/delay/scr/%d/power_sw", 686 | &scr->power_sw_ctl.delay, i); 687 | 688 | scr->underscan = 1.0; 689 | conf_get_d_v(conf, "scr/%d/underscan", &scr->underscan, i); 690 | 691 | if (conf_get_str_v(conf, "scr/%d/brt_dr", &str, i)) { 692 | strlcpy(scr->brt_dr.name, str, 693 | sizeof (scr->brt_dr.name)); 694 | } 695 | 696 | if (conf_get_str_v(conf, "scr/%d/power_dr", &str, i)) { 697 | strlcpy(scr->power_dr.name, str, 698 | sizeof (scr->power_dr.name)); 699 | } 700 | if (conf_get_str_v(conf, "scr/%d/power_sw_dr", &str, i)) { 701 | strlcpy(scr->power_sw_dr.name, str, 702 | sizeof (scr->power_sw_dr.name)); 703 | } 704 | conf_get_d_v(conf, "scr/%d/power_on_rate", 705 | &scr->power_on_rate, i); 706 | conf_get_d_v(conf, "scr/%d/power_off_rate", 707 | &scr->power_off_rate, i); 708 | scr->power_on_rate = MAX(scr->power_on_rate, 0.05); 709 | scr->power_off_rate = MAX(scr->power_off_rate, 0.05); 710 | 711 | scr->mtcr = mt_cairo_render_init(scr->w, scr->h, scr->fps, 712 | NULL, render_cb, NULL, scr); 713 | scr->sys = &sys; 714 | } 715 | } 716 | 717 | bool_t 718 | sa_init(const conf_t *conf) 719 | { 720 | XPLMPluginID opengpws; 721 | 722 | ASSERT(!inited); 723 | inited = B_TRUE; 724 | 725 | memset(&sys, 0, sizeof (sys)); 726 | parse_conf_file(conf); 727 | mutex_init(&sys.mode_lock); 728 | 729 | open_debug_cmd = XPLMCreateCommand("openwxr/standalone_window", 730 | "Open OpenWXR standalone mode debug window"); 731 | ASSERT(open_debug_cmd != NULL); 732 | XPLMRegisterCommandHandler(open_debug_cmd, open_debug_win, 0, NULL); 733 | 734 | opengpws = XPLMFindPluginBySignature(OPENGPWS_PLUGIN_SIG); 735 | if (opengpws == XPLM_NO_PLUGIN_ID) { 736 | logMsg("WXR init failure: OpenGPWS plugin not found. " 737 | "Is it installed?"); 738 | goto errout; 739 | } 740 | XPLMSendMessageToPlugin(opengpws, EGPWS_GET_INTF, &sys.terr); 741 | 742 | openwxr = XPLMFindPluginBySignature(OPENWXR_PLUGIN_SIG); 743 | ASSERT(openwxr != XPLM_NO_PLUGIN_ID); 744 | 745 | XPLMSendMessageToPlugin(openwxr, OPENWXR_INTF_GET, &wxr_intf); 746 | ASSERT(wxr_intf != NULL); 747 | XPLMSendMessageToPlugin(openwxr, OPENWXR_ATMO_GET, &atmo); 748 | ASSERT(atmo != NULL); 749 | XPLMSendMessageToPlugin(openwxr, OPENWXR_ATMO_XP11_SET_EFIS, 750 | sys.efis_xywh); 751 | 752 | fdr_find(&drs.lat, "sim/flightmodel/position/latitude"); 753 | fdr_find(&drs.lon, "sim/flightmodel/position/longitude"); 754 | fdr_find(&drs.elev, "sim/flightmodel/position/elevation"); 755 | fdr_find(&drs.sim_time, "sim/time/total_running_time_sec"); 756 | fdr_find(&drs.panel_render_type, "sim/graphics/view/panel_render_type"); 757 | fdr_find(&drs.hdg, "sim/flightmodel/position/psi"); 758 | fdr_find(&drs.pitch, "sim/flightmodel/position/theta"); 759 | fdr_find(&drs.roll, "sim/flightmodel/position/phi"); 760 | 761 | XPLMRegisterFlightLoopCallback(floop_cb, -1, NULL); 762 | XPLMRegisterDrawCallback(draw_cb, xplm_Phase_Gauges, 0, NULL); 763 | 764 | return (B_TRUE); 765 | errout: 766 | sa_fini(); 767 | return (B_FALSE); 768 | } 769 | 770 | void 771 | sa_fini(void) 772 | { 773 | if (!inited) 774 | return; 775 | inited = B_FALSE; 776 | 777 | XPLMUnregisterCommandHandler(open_debug_cmd, open_debug_win, 0, NULL); 778 | 779 | for (unsigned i = 0; i < sys.num_screens; i++) { 780 | if (sys.screens[i].mtcr != NULL) 781 | mt_cairo_render_fini(sys.screens[i].mtcr); 782 | } 783 | 784 | if (wxr != NULL) { 785 | wxr_intf->fini(wxr); 786 | wxr = NULL; 787 | } 788 | wxr_intf = NULL; 789 | atmo = NULL; 790 | 791 | if (debug_win != NULL) { 792 | XPLMDestroyWindow(debug_win); 793 | debug_win = NULL; 794 | } 795 | 796 | XPLMUnregisterFlightLoopCallback(floop_cb, NULL); 797 | XPLMUnregisterDrawCallback(draw_cb, xplm_Phase_Gauges, 0, NULL); 798 | 799 | mutex_destroy(&sys.mode_lock); 800 | } 801 | -------------------------------------------------------------------------------- /src/standalone.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CDDL HEADER START 3 | * 4 | * This file and its contents are supplied under the terms of the 5 | * Common Development and Distribution License ("CDDL"), version 1.0. 6 | * You may only use this file in accordance with the terms of version 7 | * 1.0 of the CDDL. 8 | * 9 | * A full copy of the text of the CDDL should have accompanied this 10 | * source. A copy of the CDDL is also available via the Internet at 11 | * http://www.illumos.org/license/CDDL. 12 | * 13 | * CDDL HEADER END 14 | */ 15 | /* 16 | * Copyright 2018 Saso Kiselkov. All rights reserved. 17 | */ 18 | 19 | #ifndef _STANDALONE_H_ 20 | #define _STANDALONE_H_ 21 | 22 | #include 23 | 24 | #ifdef __cplusplus 25 | extern "C" { 26 | #endif 27 | 28 | bool_t sa_init(const conf_t *conf); 29 | void sa_fini(void); 30 | 31 | #ifdef __cplusplus 32 | } 33 | #endif 34 | 35 | #endif /* _STANDALONE_H_ */ 36 | -------------------------------------------------------------------------------- /src/symbols.version: -------------------------------------------------------------------------------- 1 | PLUGIN { 2 | global: XPluginStart; XPluginStop; XPluginEnable; XPluginDisable; XPluginReceiveMessage; 3 | local: *; 4 | }; 5 | -------------------------------------------------------------------------------- /src/wxr.c: -------------------------------------------------------------------------------- 1 | /* 2 | * CDDL HEADER START 3 | * 4 | * This file and its contents are supplied under the terms of the 5 | * Common Development and Distribution License ("CDDL"), version 1.0. 6 | * You may only use this file in accordance with the terms of version 7 | * 1.0 of the CDDL. 8 | * 9 | * A full copy of the text of the CDDL should have accompanied this 10 | * source. A copy of the CDDL is also available via the Internet at 11 | * http://www.illumos.org/license/CDDL. 12 | * 13 | * CDDL HEADER END 14 | */ 15 | /* 16 | * Copyright 2018 Saso Kiselkov. All rights reserved. 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #include 41 | 42 | #include "glpriv.h" 43 | #include "wxr.h" 44 | #include "xplane.h" 45 | 46 | #define TEX_UPD_INTVAL 40000 /* us, 25 fps */ 47 | #define WORKER_INTVAL 33333 /* us, 30 fps */ 48 | #define MAX_BEAM_ENERGY 1 /* dBmW */ 49 | #define EARTH_CIRC (2 * EARTH_MSL * M_PI) /* meters */ 50 | #define MAX_TERR_LAT 79 51 | #define GROUND_RETURN_MULT 0.2 /* energy multiplier */ 52 | #define SHADOW_ENERGY_THRESH 0.57 53 | #define ENERGY_SCALE_FACT 0.04 54 | #define PANEL_TEX_SZ 2048 /* pixels */ 55 | #define SCR_CLEAR_DELAY 200000 /* microseconds */ 56 | 57 | typedef struct { 58 | } wxr_prog_loc_t; 59 | 60 | struct wxr_s { 61 | const wxr_conf_t *conf; 62 | const atmo_t *atmo; 63 | 64 | /* only accessed by foreground thread */ 65 | unsigned cur_tex; 66 | GLuint tex[2]; 67 | GLuint pbo; 68 | GLuint shadow_tex[2]; 69 | GLuint shadow_pbo; 70 | GLsync upload_sync; 71 | uint64_t last_upload; 72 | GLint wxr_prog; 73 | struct { 74 | GLint pvm; 75 | GLint tex; 76 | GLint tex_size; 77 | GLint smear_mult; 78 | GLint brt; 79 | } wxr_prog_loc; 80 | glutils_quads_t wxr_scr_quads; 81 | vect2_t draw_pos; 82 | vect2_t draw_size; 83 | bool_t draw_vert; 84 | double brt; 85 | 86 | mutex_t lock; 87 | /* protected by lock above */ 88 | bool_t standby; 89 | bool_t vert_mode; 90 | geo_pos3_t acf_pos; 91 | vect3_t acf_orient; 92 | unsigned cur_range; 93 | double gain; 94 | double ant_pitch_req; 95 | unsigned azi_lim_left; 96 | unsigned azi_lim_right; 97 | double pitch_stab; 98 | double roll_stab; 99 | wxr_color_t * colors; 100 | size_t num_colors; 101 | uint64_t scr_clear_time; 102 | 103 | /* only accessed from worker thread */ 104 | unsigned ant_pos; 105 | unsigned ant_pos_vert; 106 | bool_t scan_right; 107 | scan_line_t sl; 108 | geo_pos2_t *tp_in_pts; 109 | egpws_terr_probe_t tp; 110 | 111 | /* unstructured, always safe to read & write */ 112 | uint32_t *samples; 113 | uint32_t *shadow_samples; 114 | bool_t beam_shadow; 115 | 116 | /* set only at wxr_t creation time */ 117 | uint64_t worker_intval; 118 | 119 | XPLMPluginID opengpws; 120 | const egpws_intf_t *terr; 121 | 122 | worker_t wk; 123 | }; 124 | 125 | static const shader_info_t smear_vert_info = { .filename = "smear.vert.spv" }; 126 | static const shader_info_t smear_frag_info = { .filename = "smear.frag.spv" }; 127 | static const shader_prog_info_t smear_prog_info = { 128 | .progname = "wxr_smear", 129 | .vert = &smear_vert_info, 130 | .frag = &smear_frag_info 131 | }; 132 | 133 | static void 134 | wxr_ant_return2neutral(wxr_t *wxr) 135 | { 136 | const wxr_conf_t *conf = wxr->conf; 137 | 138 | /* 139 | * Since advance_ant_pos always first increments or decrements the 140 | * variable before checking for reversal, we want to stay just one 141 | * step away from the eddge to allow for that. 142 | */ 143 | wxr->ant_pos = clampi((conf->res_x / conf->scan_angle) * 144 | (conf->parked_azi + conf->scan_angle / 2), 1, conf->res_x - 2); 145 | } 146 | 147 | static vect3_t 148 | randomize_normal(vect3_t norm) 149 | { 150 | double rx = 0.9 + ((double)crc64_rand() / UINT64_MAX) / 5; 151 | double ry = 0.9 + ((double)crc64_rand() / UINT64_MAX) / 5; 152 | double rz = 0.9 + ((double)crc64_rand() / UINT64_MAX) / 5; 153 | return (VECT3(norm.x * rx, norm.y * ry, norm.z * rz)); 154 | } 155 | 156 | static void 157 | advance_ant_pos(wxr_t *wxr) 158 | { 159 | /* 160 | * Move the antenna by one notch left/right (or up/down 161 | * when in vertical mode). 162 | */ 163 | if (wxr->scan_right) { 164 | if (!wxr->vert_mode) { 165 | if (wxr->ant_pos < wxr->conf->res_x - 1) 166 | wxr->ant_pos++; 167 | } else { 168 | if (wxr->ant_pos_vert < wxr->conf->res_x - 1) 169 | wxr->ant_pos_vert++; 170 | } 171 | if ((!wxr->vert_mode && (wxr->ant_pos == wxr->conf->res_x - 1 || 172 | wxr->ant_pos >= wxr->azi_lim_right)) || (wxr->vert_mode && 173 | wxr->ant_pos_vert == wxr->conf->res_x - 1)) 174 | wxr->scan_right = B_FALSE; 175 | } else { 176 | if (!wxr->vert_mode) { 177 | if (wxr->ant_pos != 0) 178 | wxr->ant_pos--; 179 | } else { 180 | if (wxr->ant_pos_vert != 0) 181 | wxr->ant_pos_vert--; 182 | } 183 | if ((!wxr->vert_mode && 184 | (wxr->ant_pos == 0 || wxr->ant_pos <= wxr->azi_lim_left)) || 185 | (wxr->vert_mode && wxr->ant_pos_vert == 0)) 186 | wxr->scan_right = B_TRUE; 187 | } 188 | ASSERT3U(wxr->ant_pos, <, wxr->conf->res_x); 189 | ASSERT3U(wxr->ant_pos_vert, <, wxr->conf->res_x); 190 | } 191 | 192 | static void 193 | prep_terr_probe_coords(wxr_t *wxr, vect2_t ant_dir, vect2_t degree_sz) 194 | { 195 | for (unsigned i = 0; i < wxr->conf->res_y; i++) { 196 | double d = ((double)i / wxr->conf->res_y) * wxr->sl.range; 197 | vect2_t disp_m = vect2_scmul(ant_dir, d); 198 | vect2_t disp_deg = VECT2(disp_m.x / degree_sz.x, 199 | disp_m.y / degree_sz.y); 200 | geo_pos2_t p = GEO_POS2(wxr->sl.origin.lat + disp_deg.y, 201 | wxr->sl.origin.lon + disp_deg.x); 202 | /* 203 | * Handle geo coordinate wrapping. 204 | */ 205 | p.lat = clamp(p.lat, -MAX_TERR_LAT, MAX_TERR_LAT); 206 | if (p.lon <= -180.0) 207 | p.lon += 360; 208 | else if (p.lon >= 180.0) 209 | p.lon -= 360.0; 210 | ASSERT(is_valid_lat(p.lat)); 211 | ASSERT(is_valid_lon(p.lon)); 212 | wxr->tp_in_pts[i] = p; 213 | } 214 | } 215 | 216 | static bool_t 217 | wxr_worker(void *userinfo) 218 | { 219 | wxr_t *wxr = userinfo; 220 | double ant_pitch_base, acf_hdg, acf_pitch; 221 | vect2_t degree_sz; 222 | double scan_time; 223 | double sample_sz = wxr->sl.range / wxr->sl.num_samples; 224 | double sample_sz_rat = sample_sz / 1000.0; 225 | double extra_pitch = 0, extra_roll = 0; 226 | size_t num_colors; 227 | wxr_color_t *colors; 228 | unsigned work_step; 229 | uint64_t now = microclock(); 230 | bool_t suppress_drawing; 231 | 232 | #ifdef WXR_PROFILE 233 | static uint64_t last_report_time = 0; 234 | static uint64_t total_time = 0; 235 | uint64_t start, end; 236 | 237 | start = now; 238 | #endif /* WXR_PROFILE */ 239 | 240 | mutex_enter(&wxr->lock); 241 | 242 | suppress_drawing = (now - wxr->scr_clear_time < SCR_CLEAR_DELAY); 243 | 244 | wxr->sl.origin = wxr->acf_pos; 245 | wxr->sl.shape = wxr->conf->beam_shape; 246 | wxr->sl.range = wxr->conf->ranges[wxr->cur_range]; 247 | wxr->sl.energy = MAX_BEAM_ENERGY; 248 | wxr->sl.max_range = wxr->conf->ranges[wxr->conf->num_ranges - 1]; 249 | wxr->sl.num_samples = wxr->conf->res_y; 250 | ant_pitch_base = wxr->ant_pitch_req; 251 | acf_hdg = wxr->acf_orient.y; 252 | acf_pitch = wxr->acf_orient.x; 253 | if (acf_pitch > wxr->pitch_stab) 254 | extra_pitch = acf_pitch - wxr->pitch_stab; 255 | else if (acf_pitch < -wxr->pitch_stab) 256 | extra_pitch = wxr->pitch_stab + acf_pitch; 257 | if (wxr->acf_orient.z > wxr->roll_stab) 258 | extra_roll = wxr->acf_orient.z - wxr->roll_stab; 259 | else if (wxr->acf_orient.z < -wxr->roll_stab) 260 | extra_roll = wxr->acf_orient.z + wxr->roll_stab; 261 | 262 | num_colors = wxr->num_colors; 263 | colors = safe_calloc(sizeof (*colors), num_colors); 264 | memcpy(colors, wxr->colors, sizeof (*colors) * num_colors); 265 | 266 | mutex_exit(&wxr->lock); 267 | 268 | degree_sz = VECT2( 269 | (EARTH_CIRC / 360.0) * cos(DEG2RAD(wxr->sl.origin.lat)), 270 | (EARTH_CIRC / 360.0)); 271 | 272 | /* 273 | * A word on terrain drawing. 274 | * 275 | * We need to pass LATxLON points to OpenGPWS to give us terrain 276 | * elevations, but since doing proper FPP-to-GEO transformations 277 | * for each point would be pretty expensive (tons of trig), we 278 | * fudge it by instead projecting lines at a fixed LATxLON 279 | * increment using our heading. Essentially, we are projecting 280 | * rhumb lines instead of true radials, but for the short terrain 281 | * distances that we care about (at most around 100km), that is 282 | * "close enough" that we don't need to care. 283 | */ 284 | if (wxr->tp.in_pts == NULL) { 285 | wxr->tp.num_pts = wxr->conf->res_y; 286 | wxr->tp_in_pts = safe_calloc(wxr->tp.num_pts, 287 | sizeof (*wxr->tp_in_pts)); 288 | wxr->tp.in_pts = wxr->tp_in_pts; 289 | wxr->tp.out_elev = safe_calloc(wxr->tp.num_pts, 290 | sizeof (*wxr->tp.out_elev)); 291 | wxr->tp.out_norm = safe_calloc(wxr->tp.num_pts, 292 | sizeof (*wxr->tp.out_norm)); 293 | wxr->tp.out_water = safe_calloc(wxr->tp.num_pts, 294 | sizeof (*wxr->tp.out_water)); 295 | } 296 | 297 | /* 298 | * We want to maintain a constant scan rate, but in vertical mode 299 | * we often scan a different sector size, so adjust the scan time 300 | * so that we scan a constant degrees/second rate. 301 | */ 302 | if (!wxr->vert_mode) { 303 | scan_time = wxr->conf->scan_time; 304 | } else { 305 | scan_time = (wxr->conf->scan_angle_vert / 306 | wxr->conf->scan_angle) * wxr->conf->scan_time; 307 | } 308 | 309 | work_step = MAX(1, round(wxr->conf->res_x * 310 | (USEC2SEC(wxr->worker_intval) / scan_time))); 311 | for (unsigned i = 0; i < work_step; i++) { 312 | enum { NUM_VERT_SECTORS = 10 }; 313 | double ant_hdg, ant_pitch_up_down; 314 | int off; 315 | double energy_spent[NUM_VERT_SECTORS]; 316 | vect2_t ant_dir, ant_dir_neg; 317 | double sin_ant_pitch[NUM_VERT_SECTORS + 1]; 318 | double ant_pitch = ant_pitch_base; 319 | double cos_ant_pitch; 320 | 321 | CTASSERT(NUM_VERT_SECTORS > 1); 322 | memset(energy_spent, 0, sizeof (energy_spent)); 323 | 324 | advance_ant_pos(wxr); 325 | if (suppress_drawing) 326 | continue; 327 | 328 | if (!wxr->vert_mode) 329 | off = wxr->ant_pos * wxr->conf->res_y; 330 | else 331 | off = wxr->ant_pos_vert * wxr->conf->res_y; 332 | 333 | wxr->sl.ant_rhdg = (wxr->conf->scan_angle * 334 | ((wxr->ant_pos / (double)wxr->conf->res_x) - 0.5)) * 335 | cos(DEG2RAD(extra_roll)); 336 | ant_hdg = acf_hdg + wxr->sl.ant_rhdg; 337 | if (wxr->vert_mode) { 338 | UNUSED(acf_pitch); 339 | ant_pitch = 340 | -(wxr->conf->scan_angle_vert * 341 | ((wxr->ant_pos_vert / (double)wxr->conf->res_x) - 342 | 0.5)); 343 | ant_pitch = clamp(ant_pitch, -90, 90); 344 | } 345 | ant_pitch += extra_pitch; 346 | wxr->sl.dir = VECT2(ant_hdg, ant_pitch); 347 | ant_pitch_up_down = ant_pitch; 348 | cos_ant_pitch = cos(DEG2RAD(ant_pitch)); 349 | wxr->sl.vert_scan = wxr->vert_mode; 350 | 351 | wxr->atmo->probe(&wxr->sl); 352 | 353 | ant_dir = hdg2dir(ant_hdg); 354 | ant_dir_neg = vect2_neg(ant_dir); 355 | for (int j = 0; j < NUM_VERT_SECTORS + 1; j++) { 356 | double angle = ant_pitch_up_down - 357 | wxr->conf->beam_shape.y / 2 + 358 | (wxr->conf->beam_shape.y / NUM_VERT_SECTORS) * j; 359 | sin_ant_pitch[j] = sin(DEG2RAD(angle)); 360 | } 361 | prep_terr_probe_coords(wxr, ant_dir, degree_sz); 362 | wxr->terr->terr_probe(&wxr->tp); 363 | 364 | /* 365 | * No need to lock the samples, worst case is we will 366 | * draw a partially updated scan line - no big deal. 367 | */ 368 | for (unsigned j = 0; j < wxr->conf->res_y; j++) { 369 | double energy[NUM_VERT_SECTORS]; 370 | double abs_energy = 0; 371 | double energy_spent_total = 0; 372 | /* Distance of point along scan line from antenna. */ 373 | double d = ((double)j / wxr->conf->res_y) * 374 | wxr->sl.range * cos_ant_pitch; 375 | int64_t elev_rand_lim = iter_fract(d, 0, 100000, 376 | B_TRUE) * 3000 + 10; 377 | int64_t elev_rand = (crc64_rand() % elev_rand_lim) - 378 | (elev_rand_lim / 2); 379 | double terr_elev = wxr->tp.out_elev[j] + elev_rand; 380 | vect2_t ant_dir_neg_m = vect2_scmul(ant_dir_neg, d); 381 | /* Reverse vector from ground point to the antenna. */ 382 | vect3_t back_v = vect3_unit(VECT3(ant_dir_neg_m.x, 383 | ant_dir_neg_m.y, wxr->sl.origin.elev - terr_elev), 384 | NULL); 385 | double ground_absorb[NUM_VERT_SECTORS]; 386 | double ground_return[NUM_VERT_SECTORS]; 387 | double ground_return_total = 0; 388 | vect3_t norm; 389 | double fract_dir; 390 | 391 | for (int k = 0; k < NUM_VERT_SECTORS; k++) { 392 | energy[k] = wxr->sl.energy_out[j] / 393 | NUM_VERT_SECTORS; 394 | } 395 | 396 | norm = randomize_normal(wxr->tp.out_norm[j]); 397 | fract_dir = vect3_dotprod(back_v, norm); 398 | fract_dir = clamp(fract_dir, 0, 1); 399 | 400 | for (int k = 0; k < NUM_VERT_SECTORS; k++) { 401 | /* How perpendicular is the ground to us */ 402 | double elev_min; 403 | double elev_max; 404 | /* 405 | * Fraction of how much of the beam is below 406 | * ground. 407 | */ 408 | double fract_hit; 409 | 410 | elev_min = wxr->sl.origin.elev + 411 | sin_ant_pitch[k] * d; 412 | elev_max = wxr->sl.origin.elev + 413 | sin_ant_pitch[k + 1] * d; 414 | /* 415 | * At extreme antenna angles, the top/bottom 416 | * distinction can break, so to avoid that, we 417 | * manually flip the coordinates in this case 418 | * and add 0.1m to elev_max to guarantee that 419 | * it cannot be <= elev_min. 420 | */ 421 | if (elev_min > elev_max) { 422 | double tmp = elev_max; 423 | elev_max = elev_min; 424 | elev_min = tmp; 425 | } 426 | elev_max += 0.1; 427 | fract_hit = iter_fract(terr_elev, elev_min, 428 | elev_max, B_FALSE) / 5; 429 | fract_hit = clamp(fract_hit, 0, 1); 430 | ground_absorb[k] = ((1 - energy_spent[k]) * 431 | fract_hit) * sample_sz_rat * 0.1; 432 | ground_return[k] = ((1 - energy_spent[k]) * 433 | fract_hit * (fract_dir + 0.8) / 434 | NUM_VERT_SECTORS) * GROUND_RETURN_MULT * 435 | (1 - wxr->tp.out_water[j] * 0.95); 436 | } 437 | 438 | for (int k = 0; k < NUM_VERT_SECTORS; k++) { 439 | abs_energy += energy[k]; 440 | ground_return_total += ground_return[k]; 441 | energy_spent[k] += energy[k] + ground_absorb[k]; 442 | energy_spent_total += energy_spent[k]; 443 | } 444 | abs_energy = ((abs_energy / sample_sz_rat) + 445 | ground_return_total) * wxr->gain; 446 | 447 | if (energy_spent_total / NUM_VERT_SECTORS > 448 | SHADOW_ENERGY_THRESH && wxr->beam_shadow) { 449 | wxr->shadow_samples[off + j] = 450 | BE32(0x70707070u); 451 | } else { 452 | wxr->shadow_samples[off + j] = 0x00u; 453 | } 454 | wxr->samples[off + j] = 0x00u; 455 | for (size_t k = 0; k < num_colors; k++) { 456 | if (abs_energy / ENERGY_SCALE_FACT >= 457 | colors[k].min_val) { 458 | wxr->samples[off + j] = colors[k].rgba; 459 | break; 460 | } 461 | } 462 | } 463 | } 464 | 465 | free(colors); 466 | 467 | #ifdef WXR_PROFILE 468 | end = microclock(); 469 | total_time += (end - start); 470 | if (now - last_report_time > 1000000) { 471 | printf("load: %.3f%%\n", (100.0 * total_time) / 472 | (now - last_report_time)); 473 | last_report_time = now; 474 | total_time = 0; 475 | } 476 | #endif /* WXR_PROFILE */ 477 | 478 | return (B_TRUE); 479 | } 480 | 481 | wxr_t * 482 | wxr_init(const wxr_conf_t *conf, const atmo_t *atmo) 483 | { 484 | wxr_t *wxr = safe_calloc(1, sizeof (*wxr)); 485 | 486 | ASSERT(conf->num_ranges != 0); 487 | ASSERT3U(conf->num_ranges, <, WXR_MAX_RANGES); 488 | ASSERT3U(conf->res_x, >=, WXR_MIN_RES); 489 | ASSERT3U(conf->res_y, >=, WXR_MIN_RES); 490 | ASSERT3F(conf->beam_shape.x, >, 0); 491 | ASSERT3F(conf->beam_shape.y, >, 0); 492 | ASSERT3F(conf->scan_time, >, 0); 493 | ASSERT3F(conf->scan_angle, >, 0); 494 | ASSERT3F(conf->scan_angle_vert, >=, 0); 495 | ASSERT3F(ABS(conf->parked_azi), <=, conf->scan_angle / 2); 496 | ASSERT(atmo->probe != NULL); 497 | 498 | mutex_init(&wxr->lock); 499 | 500 | wxr->conf = conf; 501 | wxr->atmo = atmo; 502 | wxr->gain = 1.0; 503 | wxr->brt = 1.0; 504 | /* 505 | * 4 vertices per quad, 2 coords per vertex 506 | */ 507 | wxr->samples = safe_calloc(conf->res_x * conf->res_y, 508 | sizeof (*wxr->samples)); 509 | wxr->shadow_samples = safe_calloc(conf->res_x * conf->res_y, 510 | sizeof (*wxr->samples)); 511 | wxr_ant_return2neutral(wxr); 512 | wxr->azi_lim_right = conf->res_x - 1; 513 | wxr->sl.energy_out = safe_calloc(wxr->conf->res_y, sizeof (double)); 514 | wxr->sl.doppler_out = safe_calloc(wxr->conf->res_y, sizeof (double)); 515 | wxr->atmo->set_range(wxr->conf->ranges[0]); 516 | 517 | (void)wxr_reload_gl_progs(wxr); 518 | 519 | wxr->opengpws = XPLMFindPluginBySignature(OPENGPWS_PLUGIN_SIG); 520 | if (wxr->opengpws != XPLM_NO_PLUGIN_ID) { 521 | XPLMSendMessageToPlugin(wxr->opengpws, EGPWS_GET_INTF, 522 | &wxr->terr); 523 | } 524 | 525 | wxr->worker_intval = MAX( 526 | SEC2USEC(wxr->conf->scan_time / wxr->conf->res_x), WORKER_INTVAL); 527 | 528 | worker_init(&wxr->wk, wxr_worker, wxr->worker_intval, wxr, 529 | "OpenWXR-worker"); 530 | 531 | return (wxr); 532 | } 533 | 534 | void 535 | wxr_fini(wxr_t *wxr) 536 | { 537 | if (!wxr->standby) 538 | worker_fini(&wxr->wk); 539 | 540 | free(wxr->colors); 541 | if (wxr->tex[0] != 0) 542 | glDeleteTextures(2, wxr->tex); 543 | if (wxr->pbo != 0) 544 | glDeleteBuffers(1, &wxr->pbo); 545 | if (wxr->shadow_tex[0] != 0) 546 | glDeleteTextures(2, wxr->shadow_tex); 547 | if (wxr->shadow_pbo != 0) 548 | glDeleteBuffers(1, &wxr->shadow_pbo); 549 | 550 | glutils_destroy_quads(&wxr->wxr_scr_quads); 551 | 552 | free(wxr->samples); 553 | free(wxr->shadow_samples); 554 | free(wxr->sl.energy_out); 555 | free(wxr->sl.doppler_out); 556 | free(wxr->tp_in_pts); 557 | free(wxr->tp.out_elev); 558 | free(wxr->tp.out_norm); 559 | free(wxr->tp.out_water); 560 | 561 | if (wxr->wxr_prog != 0) 562 | glDeleteProgram(wxr->wxr_prog); 563 | 564 | mutex_destroy(&wxr->lock); 565 | 566 | free(wxr); 567 | } 568 | 569 | void 570 | wxr_set_acf_pos(wxr_t *wxr, geo_pos3_t pos, vect3_t orient) 571 | { 572 | ASSERT(!IS_NULL_GEO_POS(pos)); 573 | ASSERT(!IS_NULL_VECT(orient)); 574 | 575 | mutex_enter(&wxr->lock); 576 | wxr->acf_pos = pos; 577 | wxr->acf_orient = orient; 578 | mutex_exit(&wxr->lock); 579 | } 580 | 581 | void 582 | wxr_set_scale(wxr_t *wxr, unsigned range_idx) 583 | { 584 | double range; 585 | 586 | ASSERT3U(range_idx, <, wxr->conf->num_ranges); 587 | 588 | mutex_enter(&wxr->lock); 589 | wxr->cur_range = range_idx; 590 | range = wxr->conf->ranges[wxr->cur_range]; 591 | mutex_exit(&wxr->lock); 592 | 593 | wxr->atmo->set_range(range); 594 | } 595 | 596 | unsigned 597 | wxr_get_scale(const wxr_t *wxr) 598 | { 599 | return (wxr->cur_range); 600 | } 601 | 602 | /* 603 | * `left' and `right' are in degrees from 0 (straight head). 604 | */ 605 | void 606 | wxr_set_azimuth_limits(wxr_t *wxr, double left, double right) 607 | { 608 | ASSERT3F(left, >=, -wxr->conf->scan_angle / 2); 609 | ASSERT3F(right, <=, wxr->conf->scan_angle / 2); 610 | 611 | mutex_enter(&wxr->lock); 612 | wxr->azi_lim_left = MAX(((left + wxr->conf->scan_angle / 2) / 613 | wxr->conf->scan_angle) * wxr->conf->res_x, 0); 614 | wxr->azi_lim_right = MIN(((right + wxr->conf->scan_angle / 2) / 615 | wxr->conf->scan_angle) * wxr->conf->res_x, wxr->conf->res_x - 1); 616 | mutex_exit(&wxr->lock); 617 | } 618 | 619 | double 620 | wxr_get_ant_azimuth(const wxr_t *wxr) 621 | { 622 | return ((((double)wxr->ant_pos / wxr->conf->res_x) - 0.5) * 623 | wxr->conf->scan_angle); 624 | } 625 | 626 | void 627 | wxr_set_ant_pitch(wxr_t *wxr, double angle) 628 | { 629 | ASSERT3F(angle, <=, 90); 630 | ASSERT3F(angle, >=, -90); 631 | 632 | mutex_enter(&wxr->lock); 633 | wxr->ant_pitch_req = angle; 634 | mutex_exit(&wxr->lock); 635 | } 636 | 637 | double 638 | wxr_get_ant_pitch(const wxr_t *wxr) 639 | { 640 | /* TODO: this is broken */ 641 | if (!wxr->vert_mode) { 642 | return (wxr->ant_pitch_req); 643 | } else { 644 | return (-((wxr->ant_pos_vert / (double)wxr->conf->res_x) - 645 | 0.5) * wxr->conf->scan_angle_vert); 646 | } 647 | } 648 | 649 | void 650 | wxr_set_gain(wxr_t *wxr, double gain) 651 | { 652 | ASSERT3F(gain, >=, 0.0); 653 | 654 | mutex_enter(&wxr->lock); 655 | wxr->gain = gain; 656 | mutex_exit(&wxr->lock); 657 | } 658 | 659 | double 660 | wxr_get_gain(const wxr_t *wxr) 661 | { 662 | return (wxr->gain); 663 | } 664 | 665 | /* 666 | * Sets how many degrees the radar auto-compensates for pitching and rolling 667 | * of the aircraft by counter-pitching & tilting the radar antenna to 668 | * maintain constant absolute antenna pitch & scanning across the horizon. 669 | * Pass 0 for either value for no stabilization. 670 | */ 671 | void 672 | wxr_set_stab(wxr_t *wxr, double pitch, double roll) 673 | { 674 | ASSERT3F(pitch, >=, 0); 675 | ASSERT3F(pitch, <=, 90); 676 | ASSERT3F(roll, >=, 0); 677 | ASSERT3F(roll, <=, 90); 678 | 679 | mutex_enter(&wxr->lock); 680 | wxr->pitch_stab = pitch; 681 | wxr->roll_stab = roll; 682 | mutex_exit(&wxr->lock); 683 | } 684 | 685 | void 686 | wxr_get_stab(const wxr_t *wxr, bool_t *pitch, bool_t *roll) 687 | { 688 | *pitch = wxr->pitch_stab; 689 | *roll = wxr->roll_stab; 690 | } 691 | 692 | static void 693 | apply_pbo_tex(GLuint pbo, GLuint tex, GLuint res_x, GLuint res_y) 694 | { 695 | ASSERT(pbo != 0); 696 | ASSERT(tex != 0); 697 | glBindTexture(GL_TEXTURE_2D, tex); 698 | glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo); 699 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, res_x, res_y, 0, GL_RGBA, 700 | GL_UNSIGNED_BYTE, NULL); 701 | } 702 | 703 | static void 704 | async_xfer_setup(GLuint pbo, void *buf, size_t sz) 705 | { 706 | void *ptr; 707 | 708 | glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo); 709 | glBufferData(GL_PIXEL_UNPACK_BUFFER, sz, 0, GL_STREAM_DRAW); 710 | ptr = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); 711 | if (ptr != NULL) { 712 | memcpy(ptr, buf, sz); 713 | glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); 714 | } else { 715 | logMsg("Error uploading WXR texture: " 716 | "glMapBuffer returned NULL"); 717 | } 718 | } 719 | 720 | GLuint 721 | wxr_get_cur_tex(wxr_t *wxr, bool_t shadow_tex) 722 | { 723 | uint64_t now = microclock(); 724 | 725 | if (wxr->last_upload + TEX_UPD_INTVAL > now && wxr->upload_sync == 0) 726 | /* Previous upload still valid & nothing in flight */ 727 | goto out; 728 | 729 | if (wxr->upload_sync != 0) { 730 | if (glClientWaitSync(wxr->upload_sync, 0, 0) != 731 | GL_TIMEOUT_EXPIRED) { 732 | /* Texture upload complete, apply the texture */ 733 | glDeleteSync(wxr->upload_sync); 734 | wxr->upload_sync = 0; 735 | wxr->cur_tex = !wxr->cur_tex; 736 | 737 | apply_pbo_tex(wxr->pbo, wxr->tex[wxr->cur_tex], 738 | wxr->conf->res_x, wxr->conf->res_y); 739 | apply_pbo_tex(wxr->shadow_pbo, 740 | wxr->shadow_tex[wxr->cur_tex], 741 | wxr->conf->res_x, wxr->conf->res_y); 742 | 743 | glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); 744 | } 745 | } else { 746 | /* 747 | * Set up a new async upload, it will most likely complete 748 | * when we come through here again. But we memorize the 749 | * current time as the time of the upload, so that we are 750 | * not slipping frame timing. 751 | */ 752 | size_t sz = wxr->conf->res_x * wxr->conf->res_y * 753 | sizeof (*wxr->samples); 754 | 755 | async_xfer_setup(wxr->pbo, wxr->samples, sz); 756 | async_xfer_setup(wxr->shadow_pbo, wxr->shadow_samples, sz); 757 | wxr->upload_sync = 758 | glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); 759 | glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); 760 | wxr->last_upload = now; 761 | } 762 | 763 | out: 764 | if (!shadow_tex) 765 | return (wxr->tex[wxr->cur_tex]); 766 | else 767 | return (wxr->shadow_tex[wxr->cur_tex]); 768 | } 769 | 770 | static void 771 | setup_tex_common(GLenum target) 772 | { 773 | glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 774 | glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 775 | glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 776 | glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 777 | } 778 | 779 | static void 780 | wxr_bind_tex(wxr_t *wxr, bool_t shadow_tex) 781 | { 782 | if (wxr->pbo == 0) { 783 | glGenBuffers(1, &wxr->pbo); 784 | glGenBuffers(1, &wxr->shadow_pbo); 785 | } 786 | 787 | if (wxr->tex[0] != 0) { 788 | GLuint tex = wxr_get_cur_tex(wxr, shadow_tex); 789 | ASSERT(tex != 0); 790 | glActiveTexture(GL_TEXTURE0); 791 | glBindTexture(GL_TEXTURE_2D, tex); 792 | } else { 793 | /* initial texture upload, do a sync upload */ 794 | ASSERT(wxr->cur_tex == 0); 795 | 796 | glGenTextures(2, wxr->tex); 797 | glGenTextures(2, wxr->shadow_tex); 798 | 799 | for (int i = 0; i < 2; i++) { 800 | glBindTexture(GL_TEXTURE_2D, wxr->tex[i]); 801 | setup_tex_common(GL_TEXTURE_2D); 802 | glBindTexture(GL_TEXTURE_2D, wxr->shadow_tex[i]); 803 | setup_tex_common(GL_TEXTURE_2D); 804 | } 805 | 806 | glActiveTexture(GL_TEXTURE0); 807 | 808 | glBindTexture(GL_TEXTURE_2D, wxr->tex[0]); 809 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 810 | wxr->conf->res_x, wxr->conf->res_y, 0, 811 | GL_RGBA, GL_UNSIGNED_BYTE, wxr->samples); 812 | 813 | glBindTexture(GL_TEXTURE_2D, wxr->shadow_tex[0]); 814 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 815 | wxr->conf->res_x, wxr->conf->res_y, 0, 816 | GL_RGBA, GL_UNSIGNED_BYTE, wxr->shadow_samples); 817 | 818 | if (!shadow_tex) 819 | glBindTexture(GL_TEXTURE_2D, wxr->tex[0]); 820 | else 821 | glBindTexture(GL_TEXTURE_2D, wxr->shadow_tex[0]); 822 | } 823 | } 824 | 825 | static void 826 | wxr_draw_arc_recache(wxr_t *wxr, vect2_t pos, vect2_t size, bool_t vert) 827 | { 828 | double scan_angle = !vert ? wxr->conf->scan_angle : 829 | wxr->conf->scan_angle_vert; 830 | unsigned num_coords = ceil(scan_angle) * 4; 831 | vect2_t vtx[num_coords], tex[num_coords]; 832 | 833 | for (unsigned i = 0, j = 0; i < num_coords; i += 4, j++) { 834 | /* Draw 1-degree increments */ 835 | double fract1 = (double)j / scan_angle; 836 | double fract2 = (double)(j + 1) / scan_angle; 837 | double angle1 = DEG2RAD((fract1 * scan_angle) - 838 | (scan_angle / 2)); 839 | double angle2 = DEG2RAD((fract1 * scan_angle) - 840 | (scan_angle / 2) + 1); 841 | 842 | /* 843 | * Draw the quad in clockwise vertex order. 844 | */ 845 | 846 | /* lower-left */ 847 | if (!vert) 848 | vtx[i] = VECT2(pos.x + size.x / 2, pos.y); 849 | else 850 | vtx[i] = VECT2(pos.x, pos.y + size.y / 2); 851 | tex[i] = VECT2(0, fract1); 852 | 853 | /* upper-left */ 854 | if (!vert) { 855 | vtx[i + 1].x = (pos.x + size.x / 2) + 856 | (sin(angle1) * (size.x / 2)); 857 | vtx[i + 1].y = pos.y + (cos(angle1) * size.y); 858 | } else { 859 | vtx[i + 1].x = pos.x + (cos(angle1) * size.x); 860 | vtx[i + 1].y = (pos.y + size.y / 2) - 861 | (sin(angle1) * (size.y / 2)); 862 | } 863 | tex[i + 1] = VECT2(1, fract1); 864 | 865 | /* upper-right */ 866 | if (!vert) { 867 | vtx[i + 2].x = (pos.x + size.x / 2) + 868 | (sin(angle2) * (size.x / 2)); 869 | vtx[i + 2].y = pos.y + (cos(angle2) * size.y); 870 | } else { 871 | vtx[i + 2].x = pos.x + (cos(angle2) * size.x); 872 | vtx[i + 2].y = (pos.y + size.y / 2) - 873 | (sin(angle2) * (size.y / 2)); 874 | } 875 | tex[i + 2] = VECT2(1, fract2); 876 | 877 | /* lower-right */ 878 | if (!vert) 879 | vtx[i + 3] = VECT2(pos.x + size.x / 2, pos.y); 880 | else 881 | vtx[i + 3] = VECT2(pos.x, pos.y + size.y / 2); 882 | tex[i + 3] = VECT2(0, fract2); 883 | } 884 | 885 | glutils_destroy_quads(&wxr->wxr_scr_quads); 886 | glutils_init_2D_quads(&wxr->wxr_scr_quads, vtx, tex, num_coords); 887 | } 888 | 889 | static void 890 | wxr_draw_arc(wxr_t *wxr, vect2_t pos, vect2_t size) 891 | { 892 | GLfloat pvm[16]; 893 | 894 | if (!VECT2_EQ(pos, wxr->draw_pos) || !VECT2_EQ(size, wxr->draw_size) || 895 | wxr->draw_vert != wxr->vert_mode) { 896 | wxr_draw_arc_recache(wxr, pos, size, wxr->vert_mode); 897 | wxr->draw_pos = pos; 898 | wxr->draw_size = size; 899 | wxr->draw_vert = wxr->vert_mode; 900 | } 901 | 902 | glUseProgram(wxr->wxr_prog); 903 | 904 | glutils_vp2pvm(pvm); 905 | 906 | glUniformMatrix4fv(wxr->wxr_prog_loc.pvm, 1, GL_FALSE, pvm); 907 | glUniform1i(wxr->wxr_prog_loc.tex, 0); 908 | glUniform2f(wxr->wxr_prog_loc.tex_size, 909 | wxr->conf->res_x, wxr->conf->res_y); 910 | glUniform1f(wxr->wxr_prog_loc.smear_mult, 911 | wxr->vert_mode ? wxr->conf->smear.y : wxr->conf->smear.x); 912 | glUniform1f(wxr->wxr_prog_loc.brt, wxr->brt); 913 | 914 | glutils_draw_quads(&wxr->wxr_scr_quads, wxr->wxr_prog); 915 | 916 | glUseProgram(0); 917 | } 918 | 919 | static void 920 | wxr_draw_square(wxr_t *wxr, vect2_t pos, vect2_t size) 921 | { 922 | GLfloat pvm[16]; 923 | 924 | glUseProgram(wxr->wxr_prog); 925 | 926 | glutils_vp2pvm(pvm); 927 | 928 | glUniformMatrix4fv(wxr->wxr_prog_loc.pvm, 1, GL_FALSE, pvm); 929 | glUniform1i(wxr->wxr_prog_loc.tex, 0); 930 | glUniform2f(wxr->wxr_prog_loc.tex_size, 931 | wxr->conf->res_x, wxr->conf->res_y); 932 | 933 | if (!VECT2_EQ(pos, wxr->draw_pos) || !VECT2_EQ(size, wxr->draw_size) || 934 | wxr->draw_vert != wxr->vert_mode) { 935 | vect2_t vtx[4]; 936 | vect2_t tex[4] = { 937 | VECT2(0, 0), VECT2(1, 0), VECT2(1, 1), VECT2(0, 1) 938 | }; 939 | 940 | if (!wxr->vert_mode) { 941 | vtx[0] = VECT2(pos.x, pos.y); 942 | vtx[1] = VECT2(pos.x, pos.y + size.y); 943 | vtx[2] = VECT2(pos.x + size.x, pos.y + size.y); 944 | vtx[3] = VECT2(pos.x + size.x, pos.y); 945 | } else { 946 | vtx[0] = VECT2(pos.x, pos.y + size.y); 947 | vtx[1] = VECT2(pos.x + size.x, pos.y + size.y); 948 | vtx[2] = VECT2(pos.x + size.x, pos.y); 949 | vtx[3] = VECT2(pos.x, pos.y); 950 | } 951 | glutils_destroy_quads(&wxr->wxr_scr_quads); 952 | glutils_init_2D_quads(&wxr->wxr_scr_quads, vtx, tex, 4); 953 | 954 | wxr->draw_pos = pos; 955 | wxr->draw_size = size; 956 | wxr->draw_vert = wxr->vert_mode; 957 | } 958 | 959 | glutils_draw_quads(&wxr->wxr_scr_quads, wxr->wxr_prog); 960 | 961 | glUseProgram(0); 962 | } 963 | 964 | void 965 | wxr_draw(wxr_t *wxr, vect2_t pos, vect2_t size) 966 | { 967 | XPLMSetGraphicsState(0, 1, 0, 1, 1, 1, 1); 968 | glutils_reset_errors(); 969 | wxr_bind_tex(wxr, B_FALSE); 970 | if (wxr->conf->disp_type == WXR_DISP_ARC) { 971 | wxr_draw_arc(wxr, pos, size); 972 | } else { 973 | ASSERT3U(wxr->conf->disp_type, ==, WXR_DISP_SQUARE); 974 | wxr_draw_square(wxr, pos, size); 975 | } 976 | wxr_bind_tex(wxr, B_TRUE); 977 | if (wxr->conf->disp_type == WXR_DISP_ARC) 978 | wxr_draw_arc(wxr, pos, size); 979 | else 980 | wxr_draw_square(wxr, pos, size); 981 | } 982 | 983 | void 984 | wxr_set_beam_shadow(wxr_t *wxr, bool_t flag) 985 | { 986 | wxr->beam_shadow = flag; 987 | } 988 | 989 | bool_t 990 | wxr_get_beam_shadow(const wxr_t *wxr) 991 | { 992 | return (wxr->beam_shadow); 993 | } 994 | 995 | void 996 | wxr_set_standby(wxr_t *wxr, bool_t flag) 997 | { 998 | if (wxr->standby == flag) 999 | return; 1000 | 1001 | wxr->standby = flag; 1002 | wxr->vert_mode = B_FALSE; 1003 | if (flag) { 1004 | worker_fini(&wxr->wk); 1005 | wxr_ant_return2neutral(wxr); 1006 | memset(wxr->samples, 0, sizeof (*wxr->samples) * 1007 | wxr->conf->res_x * wxr->conf->res_y); 1008 | memset(wxr->shadow_samples, 0, sizeof (*wxr->samples) * 1009 | wxr->conf->res_x * wxr->conf->res_y); 1010 | } else { 1011 | worker_init(&wxr->wk, wxr_worker, wxr->worker_intval, wxr, 1012 | "OpenWXR-worker"); 1013 | } 1014 | } 1015 | 1016 | bool_t 1017 | wxr_get_standby(const wxr_t *wxr) 1018 | { 1019 | return (wxr->standby); 1020 | } 1021 | 1022 | void 1023 | wxr_set_brightness(wxr_t *wxr, double brt) 1024 | { 1025 | wxr->brt = brt; 1026 | } 1027 | 1028 | double 1029 | wxr_get_brightness(const wxr_t *wxr) 1030 | { 1031 | return (wxr->brt); 1032 | } 1033 | 1034 | void 1035 | wxr_clear_screen(wxr_t *wxr) 1036 | { 1037 | if (!wxr->standby) 1038 | mutex_enter(&wxr->wk.lock); 1039 | 1040 | mutex_enter(&wxr->lock); 1041 | memset(wxr->samples, 0, 1042 | sizeof (*wxr->samples) * wxr->conf->res_x * wxr->conf->res_y); 1043 | memset(wxr->shadow_samples, 0, 1044 | sizeof (*wxr->samples) * wxr->conf->res_x * wxr->conf->res_y); 1045 | wxr->scr_clear_time = microclock(); 1046 | mutex_exit(&wxr->lock); 1047 | 1048 | if (!wxr->standby) 1049 | mutex_exit(&wxr->wk.lock); 1050 | } 1051 | 1052 | void 1053 | wxr_set_vert_mode(wxr_t *wxr, bool_t flag, double azimuth) 1054 | { 1055 | ASSERT3F(wxr->conf->scan_angle_vert, >, 0); 1056 | 1057 | if (!wxr->standby) 1058 | mutex_enter(&wxr->wk.lock); 1059 | 1060 | mutex_enter(&wxr->lock); 1061 | 1062 | ASSERT3F(ABS(azimuth), <=, wxr->conf->scan_angle / 2); 1063 | 1064 | if (flag) { 1065 | wxr->ant_pos = clampi(((azimuth + wxr->conf->scan_angle / 2) / 1066 | wxr->conf->scan_angle) * wxr->conf->res_x, 0, 1067 | wxr->conf->res_x - 1); 1068 | } 1069 | if (flag && !wxr->vert_mode) { 1070 | wxr->vert_mode = B_TRUE; 1071 | wxr->ant_pos_vert = clampi(((wxr->ant_pitch_req + 1072 | wxr->conf->scan_angle_vert / 2) / 1073 | wxr->conf->scan_angle_vert) * wxr->conf->res_x, 0, 1074 | wxr->conf->res_x - 1); 1075 | memset(wxr->samples, 0, sizeof (*wxr->samples) * 1076 | wxr->conf->res_x * wxr->conf->res_y); 1077 | } else if (!flag && wxr->vert_mode) { 1078 | wxr->vert_mode = B_FALSE; 1079 | memset(wxr->samples, 0, sizeof (*wxr->samples) * 1080 | wxr->conf->res_x * wxr->conf->res_y); 1081 | memset(wxr->shadow_samples, 0, sizeof (*wxr->samples) * 1082 | wxr->conf->res_x * wxr->conf->res_y); 1083 | } 1084 | 1085 | mutex_exit(&wxr->lock); 1086 | 1087 | if (!wxr->standby) 1088 | mutex_exit(&wxr->wk.lock); 1089 | } 1090 | 1091 | bool_t 1092 | wxr_get_vert_mode(const wxr_t *wxr) 1093 | { 1094 | return (wxr->vert_mode); 1095 | } 1096 | 1097 | /* 1098 | * Colors should be in big-endian RGBA ('R' in top bits, 'A' in bottom bits). 1099 | */ 1100 | void 1101 | wxr_set_colors(wxr_t *wxr, const wxr_color_t *colors, size_t num) 1102 | { 1103 | if (wxr->num_colors == 0 || 1104 | memcmp(colors, wxr->colors, 1105 | wxr->num_colors * sizeof (*colors)) != 0) { 1106 | mutex_enter(&wxr->lock); 1107 | free(wxr->colors); 1108 | wxr->colors = calloc(num, sizeof (*colors)); 1109 | memcpy(wxr->colors, colors, num * sizeof (*colors)); 1110 | wxr->num_colors = num; 1111 | mutex_exit(&wxr->lock); 1112 | } 1113 | } 1114 | 1115 | bool_t 1116 | wxr_reload_gl_progs(wxr_t *wxr) 1117 | { 1118 | if (!reload_gl_prog(&wxr->wxr_prog, &smear_prog_info)) 1119 | return (B_FALSE); 1120 | 1121 | wxr->wxr_prog_loc.pvm = glGetUniformLocation(wxr->wxr_prog, "pvm"); 1122 | wxr->wxr_prog_loc.tex = glGetUniformLocation(wxr->wxr_prog, "tex"); 1123 | wxr->wxr_prog_loc.tex_size = 1124 | glGetUniformLocation(wxr->wxr_prog, "tex_size"); 1125 | wxr->wxr_prog_loc.smear_mult = 1126 | glGetUniformLocation(wxr->wxr_prog, "smear_mult"); 1127 | wxr->wxr_prog_loc.brt = glGetUniformLocation(wxr->wxr_prog, "brt"); 1128 | 1129 | return (B_TRUE); 1130 | } 1131 | -------------------------------------------------------------------------------- /src/wxr.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CDDL HEADER START 3 | * 4 | * This file and its contents are supplied under the terms of the 5 | * Common Development and Distribution License ("CDDL"), version 1.0. 6 | * You may only use this file in accordance with the terms of version 7 | * 1.0 of the CDDL. 8 | * 9 | * A full copy of the text of the CDDL should have accompanied this 10 | * source. A copy of the CDDL is also available via the Internet at 11 | * http://www.illumos.org/license/CDDL. 12 | * 13 | * CDDL HEADER END 14 | */ 15 | /* 16 | * Copyright 2018 Saso Kiselkov. All rights reserved. 17 | */ 18 | 19 | #ifndef _WXR_H_ 20 | #define _WXR_H_ 21 | 22 | #include 23 | 24 | #include "atmo.h" 25 | #include 26 | #include 27 | 28 | #ifdef __cplusplus 29 | extern "C" { 30 | #endif 31 | 32 | wxr_t *wxr_init(const wxr_conf_t *conf, const atmo_t *atmo); 33 | void wxr_fini(wxr_t *wxr); 34 | 35 | void wxr_set_acf_pos(wxr_t *wxr, geo_pos3_t pos, vect3_t orient); 36 | void wxr_draw(wxr_t *wxr, vect2_t pos, vect2_t size); 37 | 38 | void wxr_set_scale(wxr_t *wxr, unsigned range_idx); 39 | unsigned wxr_get_scale(const wxr_t *wxr); 40 | 41 | void wxr_set_azimuth_limits(wxr_t *wxr, double left, double right); 42 | double wxr_get_ant_azimuth(const wxr_t *wxr); 43 | 44 | void wxr_set_ant_pitch(wxr_t *wxr, double angle); 45 | double wxr_get_ant_pitch(const wxr_t *wxr); 46 | 47 | void wxr_set_gain(wxr_t *wxr, double gain); 48 | double wxr_get_gain(const wxr_t *wxr); 49 | 50 | void wxr_set_stab(wxr_t *wxr, double pitch, double roll); 51 | void wxr_get_stab(const wxr_t *wxr, bool_t *pitch, bool_t *roll); 52 | 53 | void wxr_set_beam_shadow(wxr_t *wxr, bool_t flag); 54 | bool_t wxr_get_beam_shadow(const wxr_t *wxr); 55 | 56 | void wxr_set_standby(wxr_t *wxr, bool_t flag); 57 | bool_t wxr_get_standby(const wxr_t *wxr); 58 | 59 | void wxr_set_vert_mode(wxr_t *wxr, bool_t flag, double azimuth); 60 | bool_t wxr_get_vert_mode(const wxr_t *wxr); 61 | 62 | void wxr_set_gnd_sense(wxr_t *wxr, bool_t flag); 63 | bool_t wxr_get_gnd_sense(const wxr_t *wxr); 64 | 65 | void wxr_set_brightness(wxr_t *wxr, double brt); 66 | double wxr_get_brightness(const wxr_t *wxr); 67 | 68 | void wxr_clear_screen(wxr_t *wxr); 69 | 70 | void wxr_set_colors(wxr_t *wxr, const wxr_color_t *colors, size_t num); 71 | 72 | bool_t wxr_reload_gl_progs(wxr_t *wxr); 73 | 74 | #ifdef __cplusplus 75 | } 76 | #endif 77 | 78 | #endif /* _WXR_H_ */ 79 | -------------------------------------------------------------------------------- /src/xplane.c: -------------------------------------------------------------------------------- 1 | /* 2 | * CDDL HEADER START 3 | * 4 | * This file and its contents are supplied under the terms of the 5 | * Common Development and Distribution License ("CDDL"), version 1.0. 6 | * You may only use this file in accordance with the terms of version 7 | * 1.0 of the CDDL. 8 | * 9 | * A full copy of the text of the CDDL should have accompanied this 10 | * source. A copy of the CDDL is also available via the Internet at 11 | * http://www.illumos.org/license/CDDL. 12 | * 13 | * CDDL HEADER END 14 | */ 15 | /* 16 | * Copyright 2022 Saso Kiselkov. All rights reserved. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #include "atmo_xp11.h" 40 | #include "dbg_log.h" 41 | #include "fontmgr.h" 42 | #include 43 | #include "standalone.h" 44 | #include "wxr.h" 45 | #include "xplane.h" 46 | 47 | #define PLUGIN_NAME "OpenWXR by Saso Kiselkov" 48 | #define PLUGIN_DESCRIPTION \ 49 | "An open-source generic weather radar simulation" 50 | 51 | char xpdir[512]; 52 | char plugindir[512]; 53 | 54 | static int xp_ver, xplm_ver; 55 | XPLMHostApplicationID host_id; 56 | static atmo_t *atmo = NULL; 57 | 58 | static openwxr_intf_t openwxr_intf = { 59 | .init = wxr_init, 60 | .fini = wxr_fini, 61 | .set_acf_pos = wxr_set_acf_pos, 62 | .set_scale = wxr_set_scale, 63 | .get_scale = wxr_get_scale, 64 | .set_azimuth_limits = wxr_set_azimuth_limits, 65 | .get_ant_azimuth = wxr_get_ant_azimuth, 66 | .set_ant_pitch = wxr_set_ant_pitch, 67 | .get_ant_pitch = wxr_get_ant_pitch, 68 | .set_gain = wxr_set_gain, 69 | .get_gain = wxr_get_gain, 70 | .set_stab = wxr_set_stab, 71 | .get_stab = wxr_get_stab, 72 | .set_beam_shadow = wxr_set_beam_shadow, 73 | .get_beam_shadow = wxr_get_beam_shadow, 74 | .set_standby = wxr_set_standby, 75 | .get_standby = wxr_get_standby, 76 | .draw = wxr_draw, 77 | .clear_screen = wxr_clear_screen, 78 | .set_vert_mode = wxr_set_vert_mode, 79 | .get_vert_mode = wxr_get_vert_mode, 80 | .set_colors = wxr_set_colors, 81 | .get_brightness = wxr_get_brightness, 82 | .set_brightness = wxr_set_brightness, 83 | .reload_gl_progs = wxr_reload_gl_progs 84 | }; 85 | 86 | static conf_t * 87 | load_config_file(void) 88 | { 89 | char *confpath = mkpathname(xpdir, plugindir, "OpenWXR.cfg", NULL); 90 | conf_t *conf = NULL; 91 | 92 | if (file_exists(confpath, NULL)) { 93 | int errline; 94 | 95 | conf = conf_read_file(confpath, &errline); 96 | if (conf == NULL) { 97 | if (errline < 0) { 98 | logMsg("Error reading configuration %s: cannot " 99 | "open configuration file.", confpath); 100 | } else { 101 | logMsg("Error reading configuration %s: syntax " 102 | "error on line %d.", confpath, errline); 103 | } 104 | } 105 | } 106 | lacf_free(confpath); 107 | 108 | return (conf); 109 | } 110 | 111 | PLUGIN_API int 112 | XPluginStart(char *name, char *sig, char *desc) 113 | { 114 | char *p; 115 | conf_t *conf = NULL; 116 | GLenum err; 117 | 118 | log_init(XPLMDebugString, "OpenWXR"); 119 | crc64_init(); 120 | crc64_srand(microclock()); 121 | logMsg("This is OpenWXR (" PLUGIN_VERSION ") libacfutils-%s", 122 | libacfutils_version); 123 | 124 | /* Always use Unix-native paths on the Mac! */ 125 | XPLMEnableFeature("XPLM_USE_NATIVE_PATHS", 1); 126 | 127 | XPLMGetSystemPath(xpdir); 128 | XPLMGetPluginInfo(XPLMGetMyID(), NULL, plugindir, NULL, NULL); 129 | 130 | #if IBM 131 | fix_pathsep(xpdir); 132 | fix_pathsep(plugindir); 133 | #endif /* IBM */ 134 | 135 | /* cut off the trailing path component (our filename) */ 136 | if ((p = strrchr(plugindir, DIRSEP)) != NULL) 137 | *p = '\0'; 138 | /* 139 | * Cut off an optional '32' or '64' trailing component. Please note 140 | * that XPLM 3.0 now supports OS-specific suffixes, so clamp those 141 | * away as well. 142 | */ 143 | if ((p = strrchr(plugindir, DIRSEP)) != NULL) { 144 | if (strcmp(p + 1, "64") == 0 || strcmp(p + 1, "32") == 0 || 145 | strcmp(p + 1, "win_x64") == 0 || 146 | strcmp(p + 1, "mac_x64") == 0 || 147 | strcmp(p + 1, "lin_x64") == 0) 148 | *p = '\0'; 149 | } 150 | 151 | /* 152 | * Now we strip a leading xpdir from plugindir, so that now plugindir 153 | * will be relative to X-Plane's root directory. 154 | */ 155 | if (strstr(plugindir, xpdir) == plugindir) { 156 | int xpdir_len = strlen(xpdir); 157 | int plugindir_len = strlen(plugindir); 158 | memmove(plugindir, &plugindir[xpdir_len], 159 | plugindir_len - xpdir_len + 1); 160 | } 161 | 162 | strcpy(name, PLUGIN_NAME); 163 | strcpy(sig, OPENWXR_PLUGIN_SIG); 164 | strcpy(desc, PLUGIN_DESCRIPTION); 165 | 166 | XPLMGetVersions(&xp_ver, &xplm_ver, &host_id); 167 | 168 | err = glewInit(); 169 | if (err != GLEW_OK) { 170 | /* Problem: glewInit failed, something is seriously wrong. */ 171 | logMsg("FATAL ERROR: cannot initialize libGLEW: %s", 172 | glewGetErrorString(err)); 173 | return (0); 174 | } 175 | if (!GLEW_VERSION_2_1) { 176 | logMsg("FATAL ERROR: your system doesn't support OpenGL 2.1"); 177 | return (0); 178 | } 179 | 180 | conf = load_config_file(); 181 | if (conf == NULL) 182 | conf = conf_create_empty(); 183 | dbg_log_init(conf); 184 | conf_free(conf); 185 | 186 | /* 187 | * Must go ahead of XPluginEnable to always have an atmosphere 188 | * ready for when external avionics start creating wxr_t instances. 189 | */ 190 | atmo = atmo_xp11_init(); 191 | if (atmo == NULL) 192 | return (0); 193 | 194 | return (1); 195 | } 196 | 197 | PLUGIN_API void 198 | XPluginStop(void) 199 | { 200 | /* 201 | * Must wait with shutdown until all instances of wxr_t have 202 | * been shut down by external avionics, so we can't do this 203 | * in XPluginDisable. 204 | */ 205 | atmo_xp11_fini(); 206 | } 207 | 208 | PLUGIN_API int 209 | XPluginEnable(void) 210 | { 211 | conf_t *conf = load_config_file(); 212 | bool_t standalone = B_FALSE; 213 | 214 | if (conf == NULL) 215 | return (1); 216 | 217 | conf_get_b(conf, "standalone", &standalone); 218 | if (standalone) { 219 | mt_cairo_render_glob_init(true); 220 | if (!fontmgr_init(xpdir, plugindir)) 221 | goto errout; 222 | if (!sa_init(conf)) 223 | goto errout; 224 | } 225 | 226 | conf_free(conf); 227 | return (1); 228 | errout: 229 | conf_free(conf); 230 | XPluginDisable(); 231 | return (0); 232 | } 233 | 234 | PLUGIN_API void 235 | XPluginDisable(void) 236 | { 237 | sa_fini(); 238 | fontmgr_fini(); 239 | } 240 | 241 | PLUGIN_API void 242 | XPluginReceiveMessage(XPLMPluginID from, int msg, void *param) 243 | { 244 | UNUSED(from); 245 | 246 | switch (msg) { 247 | case OPENWXR_INTF_GET: 248 | ASSERT(param != NULL); 249 | *(openwxr_intf_t **)param = &openwxr_intf; 250 | break; 251 | case OPENWXR_ATMO_GET: 252 | ASSERT(param != NULL); 253 | ASSERT(atmo != NULL); 254 | *(atmo_t **)param = atmo; 255 | break; 256 | case OPENWXR_ATMO_XP11_SET_EFIS: { 257 | unsigned *coords = param; 258 | atmo_xp11_set_efis_pos(coords[0], coords[1], 259 | coords[2], coords[3]); 260 | break; 261 | } 262 | } 263 | } 264 | 265 | const char * 266 | get_xpdir(void) 267 | { 268 | return (xpdir); 269 | } 270 | 271 | const char * 272 | get_plugindir(void) 273 | { 274 | return (plugindir); 275 | } 276 | 277 | int 278 | get_xpver(void) 279 | { 280 | return (xp_ver); 281 | } 282 | 283 | #if IBM 284 | BOOL WINAPI 285 | DllMain(HINSTANCE hinst, DWORD reason, LPVOID resvd) 286 | { 287 | UNUSED(hinst); 288 | UNUSED(resvd); 289 | lacf_glew_dllmain_hook(reason); 290 | return (TRUE); 291 | } 292 | #endif /* IBM */ 293 | -------------------------------------------------------------------------------- /src/xplane.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CDDL HEADER START 3 | * 4 | * This file and its contents are supplied under the terms of the 5 | * Common Development and Distribution License ("CDDL"), version 1.0. 6 | * You may only use this file in accordance with the terms of version 7 | * 1.0 of the CDDL. 8 | * 9 | * A full copy of the text of the CDDL should have accompanied this 10 | * source. A copy of the CDDL is also available via the Internet at 11 | * http://www.illumos.org/license/CDDL. 12 | * 13 | * CDDL HEADER END 14 | */ 15 | /* 16 | * Copyright 2017 Saso Kiselkov. All rights reserved. 17 | */ 18 | 19 | #ifndef _OPENWXR_XPLANE_H_ 20 | #define _OPENWXR_XPLANE_H_ 21 | 22 | #include 23 | #include 24 | 25 | #ifdef __cplusplus 26 | extern "C" { 27 | #endif 28 | 29 | /* 30 | * X-Plane-specific plugin hooks. 31 | */ 32 | PLUGIN_API int XPluginStart(char *name, char *sig, char *desc); 33 | PLUGIN_API void XPluginStop(void); 34 | PLUGIN_API int XPluginEnable(void); 35 | PLUGIN_API void XPluginDisable(void); 36 | PLUGIN_API void XPluginReceiveMessage(XPLMPluginID from, int msg, void *param); 37 | 38 | const char *get_xpdir(void); 39 | const char *get_plugindir(void); 40 | 41 | int get_xpver(void); 42 | 43 | #ifdef __cplusplus 44 | } 45 | #endif 46 | 47 | #endif /* _OPENWXR_XPLANE_H_ */ 48 | --------------------------------------------------------------------------------