diff --git a/.gitignore b/.gitignore index 5fc18bb07..3e87dcc43 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,7 @@ one openpilot notebooks xx +yy hyperthneed panda_jungle provisioning diff --git a/Jenkinsfile b/Jenkinsfile index bcdb7c99f..d12c8e494 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,7 +1,7 @@ def phone(String ip, String step_label, String cmd) { withCredentials([file(credentialsId: 'id_rsa', variable: 'key_file')]) { def ssh_cmd = """ -ssh -tt -o StrictHostKeyChecking=no -i ${key_file} -p 8022 'comma@${ip}' /usr/bin/bash <<'EOF' +ssh -tt -o StrictHostKeyChecking=no -i ${key_file} 'comma@${ip}' /usr/bin/bash <<'END' set -e @@ -29,7 +29,7 @@ cd ${env.TEST_DIR} || true ${cmd} exit 0 -EOF""" +END""" sh script: ssh_cmd, label: step_label } @@ -57,38 +57,29 @@ pipeline { } stages { - - stage('Build release2') { - agent { - docker { - image 'python:3.7.3' - args '--user=root' - } - } + stage('build releases') { when { branch 'devel-staging' } - steps { - phone_steps("eon-build", [ - ["build release2-staging & dashcam-staging", "PUSH=1 $SOURCE_DIR/release/build_release.sh"], - ]) - } - } - stage('Build release3') { - agent { - docker { - image 'python:3.7.3' - args '--user=root' + parallel { + stage('release2') { + agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } + steps { + phone_steps("eon-build", [ + ["build release2-staging & dashcam-staging", "PUSH=1 $SOURCE_DIR/release/build_release.sh"], + ]) + } + } + + stage('release3') { + agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } + steps { + phone_steps("tici", [ + ["build release3-staging & dashcam3-staging", "PUSH=1 $SOURCE_DIR/release/build_release.sh"], + ]) + } } - } - when { - branch 'devel-staging' - } - steps { - phone_steps("tici", [ - ["build release3-staging & dashcam3-staging", "PUSH=1 $SOURCE_DIR/release/build_release.sh"], - ]) } } @@ -105,43 +96,8 @@ pipeline { } stages { - - /* - stage('PC tests') { - agent { - dockerfile { - filename 'Dockerfile.openpilotci' - args '--privileged --shm-size=1G --user=root' - } - } - stages { - stage('Build') { - steps { - sh 'scons -j$(nproc)' - } - } - } - post { - always { - // fix permissions since docker runs as another user - sh "chmod -R 777 ." - } - } - } - */ - stage('On-device Tests') { - agent { - docker { - /* - filename 'Dockerfile.ondevice_ci' - args "--privileged -v /dev:/dev --shm-size=1G --user=root" - */ - image 'python:3.7.3' - args '--user=root' - } - } - + agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } stages { stage('parallel tests') { parallel { @@ -247,6 +203,15 @@ pipeline { } } + stage('C3: replay') { + steps { + phone_steps("tici-party", [ + ["build", "cd selfdrive/manager && ./build.py"], + ["model replay", "cd selfdrive/test/process_replay && ./model_replay.py"], + ]) + } + } + } } diff --git a/README.md b/README.md index b9a58f6a1..b012883d7 100755 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![](https://user-images.githubusercontent.com/37757984/127420744-89ca219c-8f8e-46d3-bccf-c1cb53b81bb1.png) +![](https://i.imgur.com/b0ZyIx5.jpg) Table of Contents ======================= @@ -21,16 +21,16 @@ What is openpilot? - - - - + + + + - - - - + + + +
@@ -40,7 +40,7 @@ Running in a car To use openpilot in a car, you need four things * This software. It's free and available right here. -* One of [the 140+ supported cars](docs/CARS.md). We support Honda, Toyota, Hyundai, Nissan, Kia, Chrysler, Lexus, Acura, Audi, VW, and more. If your car is not supported, but has adaptive cruise control and lane keeping assist, it's likely able to run openpilot. +* One of [the 150+ supported cars](docs/CARS.md). We support Honda, Toyota, Hyundai, Nissan, Kia, Chrysler, Lexus, Acura, Audi, VW, and more. If your car is not supported, but has adaptive cruise control and lane keeping assist, it's likely able to run openpilot. * A supported device to run this software. This can be a [comma two](https://comma.ai/shop/products/two), [comma three](https://comma.ai/shop/products/three), or if you like to experiment, a [Ubuntu computer with webcams](https://github.com/commaai/openpilot/tree/master/tools/webcam). * A way to connect to your car. With a comma two or three, you need only a [car harness](https://comma.ai/shop/products/car-harness). With an EON Gold or PC, you also need a [black panda](https://comma.ai/shop/products/panda). diff --git a/RELEASES.md b/RELEASES.md index 12a90fba6..5d4f114b8 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,22 @@ +Version 0.8.12 (2021-12-15) +======================== + * New driving model + * Improved behavior around exits + * Better pose accuracy at high speeds, allowing max speed of 90mph + * Fully incorporated comma three data into all parts of training stack + * Improved follow distance + * Better longitudinal policy, especially in low speed traffic + * New alert sounds + * AGNOS 3 + * Display burn in mitigation + * Improved audio amplifier configuration + * System reliability improvements + * Update Python to 3.8.10 + * Raw logs upload moved to connect.comma.ai + * Fixed HUD alerts on newer Honda Bosch thanks to csouers! + * Audi Q3 2020-21 support thanks to jyoung8607! + * Lexus RC 2020 support thanks to ErichMoraga! + Version 0.8.11 (2021-11-29) ======================== * Support for CAN FD on the red panda diff --git a/SConstruct b/SConstruct index 67acd3eac..14450cec9 100644 --- a/SConstruct +++ b/SConstruct @@ -60,7 +60,6 @@ if arch == "aarch64" and TICI: arch = "larch64" USE_WEBCAM = os.getenv("USE_WEBCAM") is not None -USE_FRAME_STREAM = os.getenv("USE_FRAME_STREAM") is not None lenv = { "PATH": os.environ['PATH'], @@ -91,7 +90,6 @@ if arch == "aarch64" or arch == "larch64": "/usr/lib", "/system/vendor/lib64", "/system/comma/usr/lib", - "#third_party/nanovg", f"#third_party/acados/{arch}/lib", ] @@ -210,7 +208,6 @@ env = Environment( "#third_party/linux/include", "#third_party/snpe/include", "#third_party/mapbox-gl-native-qt/include", - "#third_party/nanovg", "#third_party/qrcode", "#third_party", "#cereal", @@ -352,7 +349,7 @@ if GetOption("clazy"): qt_env['ENV']['CLAZY_IGNORE_DIRS'] = qt_dirs[0] qt_env['ENV']['CLAZY_CHECKS'] = ','.join(checks) -Export('env', 'qt_env', 'arch', 'real_arch', 'SHARED', 'USE_WEBCAM', 'USE_FRAME_STREAM') +Export('env', 'qt_env', 'arch', 'real_arch', 'SHARED', 'USE_WEBCAM') SConscript(['selfdrive/common/SConscript']) Import('_common', '_gpucommon', '_gpu_libs') @@ -387,7 +384,7 @@ rednose_config = { }, } -if arch != "aarch64": +if arch not in ["aarch64", "larch64"]: rednose_config['to_build'].update({ 'gnss': ('#selfdrive/locationd/models/gnss_kf.py', True, []), 'loc_4': ('#selfdrive/locationd/models/loc_kf.py', True, []), diff --git a/cereal/car.capnp b/cereal/car.capnp index ce81c7eaf..5c8c339b7 100644 --- a/cereal/car.capnp +++ b/cereal/car.capnp @@ -351,14 +351,17 @@ struct CarControl { enum AudibleAlert { none @0; - chimeEngage @1; - chimeDisengage @2; - chimeError @3; - chimeWarning1 @4; # unused - chimeWarningRepeat @5; - chimeWarningRepeatInfinite @6; - chimePrompt @7; - chimeWarning2RepeatInfinite @8; + + engage @1; + disengage @2; + refuse @3; + + warningSoft @4; + warningImmediate @5; + + prompt @6; + promptRepeat @7; + promptDistracted @8; } } @@ -379,6 +382,7 @@ struct CarParams { enableDsu @5 :Bool; # driving support unit enableApgs @6 :Bool; # advanced parking guidance system enableBsm @56 :Bool; # blind spot monitoring + flags @64 :UInt32; # flags for car specific quirks minEnableSpeed @7 :Float32; minSteerSpeed @8 :Float32; @@ -442,6 +446,8 @@ struct CarParams { fingerprintSource @49: FingerprintSource; networkLocation @50 :NetworkLocation; # Where Panda/C2 is integrated into the car's CAN network + wheelSpeedFactor @63 :Float32; # Multiplier on wheels speeds to computer actual speeds + struct SafetyConfig { safetyModel @0 :SafetyModel; safetyParam @1 :Int16; @@ -520,7 +526,7 @@ struct CarParams { allOutput @17; gmAscm @18; noOutput @19; # like silent but without silent CAN TXs - hondaBoschHarness @20; + hondaBosch @20; volkswagenPq @21; subaruLegacy @22; # pre-Global platform hyundaiLegacy @23; diff --git a/cereal/log.capnp b/cereal/log.capnp index 92ec6429d..473a9f6b8 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -611,6 +611,8 @@ struct ControlsState @0x97ff69c53601abf1 { delta @8 :Float32; output @9 :Float32; saturated @10 :Bool; + steeringAngleDesiredDeg @11 :Float32; + steeringRateDesiredDeg @12 :Float32; } struct LateralPIDState { @@ -623,6 +625,7 @@ struct ControlsState @0x97ff69c53601abf1 { f @6 :Float32; output @7 :Float32; saturated @8 :Bool; + steeringAngleDesiredDeg @9 :Float32; } struct LateralLQRState { @@ -632,6 +635,7 @@ struct ControlsState @0x97ff69c53601abf1 { output @3 :Float32; lqrOutput @4 :Float32; saturated @5 :Bool; + steeringAngleDesiredDeg @6 :Float32; } struct LateralAngleState { @@ -639,6 +643,7 @@ struct ControlsState @0x97ff69c53601abf1 { steeringAngleDeg @1 :Float32; output @2 :Float32; saturated @3 :Bool; + steeringAngleDesiredDeg @4 :Float32; } struct LateralDebugState { @@ -1480,6 +1485,7 @@ struct Event { # navigation navInstruction @82 :NavInstruction; navRoute @83 :NavRoute; + navThumbnail @84: Thumbnail; # *********** debug *********** testJoystick @52 :Joystick; diff --git a/cereal/services.py b/cereal/services.py index 7b9287780..89f437359 100755 --- a/cereal/services.py +++ b/cereal/services.py @@ -64,6 +64,7 @@ services = { "uploaderState": (True, 0., 1), "navInstruction": (True, 0.), "navRoute": (True, 0.), + "navThumbnail": (True, 0.), # debug "testJoystick": (False, 0.), diff --git a/cereal/visionipc/visionbuf.h b/cereal/visionipc/visionbuf.h index 418cbacb9..b27a08b26 100644 --- a/cereal/visionipc/visionbuf.h +++ b/cereal/visionipc/visionbuf.h @@ -15,9 +15,9 @@ enum VisionStreamType { VISION_STREAM_RGB_BACK, VISION_STREAM_RGB_FRONT, VISION_STREAM_RGB_WIDE, - VISION_STREAM_YUV_BACK, - VISION_STREAM_YUV_FRONT, - VISION_STREAM_YUV_WIDE, + VISION_STREAM_ROAD, + VISION_STREAM_DRIVER, + VISION_STREAM_WIDE_ROAD, VISION_STREAM_RGB_MAP, VISION_STREAM_MAX, }; diff --git a/cereal/visionipc/visionipc_client.cc b/cereal/visionipc/visionipc_client.cc index da406067a..ece239356 100644 --- a/cereal/visionipc/visionipc_client.cc +++ b/cereal/visionipc/visionipc_client.cc @@ -55,7 +55,7 @@ bool VisionIpcClient::connect(bool blocking){ VisionBuf bufs[VISIONIPC_MAX_FDS]; r = ipc_sendrecv_with_fds(false, socket_fd, &bufs, sizeof(bufs), fds, VISIONIPC_MAX_FDS, &num_buffers); - assert(num_buffers > 0); + assert(num_buffers >= 0); assert(r == sizeof(VisionBuf) * num_buffers); // Import buffers diff --git a/cereal/visionipc/visionipc_pyx.pyx b/cereal/visionipc/visionipc_pyx.pyx index c0ba4c3ee..3168f1e66 100644 --- a/cereal/visionipc/visionipc_pyx.pyx +++ b/cereal/visionipc/visionipc_pyx.pyx @@ -19,9 +19,9 @@ cpdef enum VisionStreamType: VISION_STREAM_RGB_BACK VISION_STREAM_RGB_FRONT VISION_STREAM_RGB_WIDE - VISION_STREAM_YUV_BACK - VISION_STREAM_YUV_FRONT - VISION_STREAM_YUV_WIDE + VISION_STREAM_ROAD + VISION_STREAM_DRIVER + VISION_STREAM_WIDE_ROAD cdef class VisionIpcServer: diff --git a/cereal/visionipc/visionipc_tests.cc b/cereal/visionipc/visionipc_tests.cc index a68bda0b8..4d1df05f5 100644 --- a/cereal/visionipc/visionipc_tests.cc +++ b/cereal/visionipc/visionipc_tests.cc @@ -13,10 +13,10 @@ static void zmq_sleep(int milliseconds=1000){ TEST_CASE("Connecting"){ VisionIpcServer server("camerad"); - server.create_buffers(VISION_STREAM_YUV_BACK, 1, false, 100, 100); + server.create_buffers(VISION_STREAM_ROAD, 1, false, 100, 100); server.start_listener(); - VisionIpcClient client = VisionIpcClient("camerad", VISION_STREAM_YUV_BACK, false); + VisionIpcClient client = VisionIpcClient("camerad", VISION_STREAM_ROAD, false); REQUIRE(client.connect()); REQUIRE(client.connected); @@ -25,10 +25,10 @@ TEST_CASE("Connecting"){ TEST_CASE("Check buffers"){ size_t width = 100, height = 200, num_buffers = 5; VisionIpcServer server("camerad"); - server.create_buffers(VISION_STREAM_YUV_BACK, num_buffers, false, width, height); + server.create_buffers(VISION_STREAM_ROAD, num_buffers, false, width, height); server.start_listener(); - VisionIpcClient client = VisionIpcClient("camerad", VISION_STREAM_YUV_BACK, false); + VisionIpcClient client = VisionIpcClient("camerad", VISION_STREAM_ROAD, false); REQUIRE(client.connect()); REQUIRE(client.buffers[0].width == width); @@ -39,11 +39,11 @@ TEST_CASE("Check buffers"){ TEST_CASE("Check yuv/rgb"){ VisionIpcServer server("camerad"); - server.create_buffers(VISION_STREAM_YUV_BACK, 1, false, 100, 100); + server.create_buffers(VISION_STREAM_ROAD, 1, false, 100, 100); server.create_buffers(VISION_STREAM_RGB_BACK, 1, true, 100, 100); server.start_listener(); - VisionIpcClient client_yuv = VisionIpcClient("camerad", VISION_STREAM_YUV_BACK, false); + VisionIpcClient client_yuv = VisionIpcClient("camerad", VISION_STREAM_ROAD, false); VisionIpcClient client_rgb = VisionIpcClient("camerad", VISION_STREAM_RGB_BACK, false); client_yuv.connect(); client_rgb.connect(); @@ -54,14 +54,14 @@ TEST_CASE("Check yuv/rgb"){ TEST_CASE("Send single buffer"){ VisionIpcServer server("camerad"); - server.create_buffers(VISION_STREAM_YUV_BACK, 1, true, 100, 100); + server.create_buffers(VISION_STREAM_ROAD, 1, true, 100, 100); server.start_listener(); - VisionIpcClient client = VisionIpcClient("camerad", VISION_STREAM_YUV_BACK, false); + VisionIpcClient client = VisionIpcClient("camerad", VISION_STREAM_ROAD, false); REQUIRE(client.connect()); zmq_sleep(); - VisionBuf * buf = server.get_buffer(VISION_STREAM_YUV_BACK); + VisionBuf * buf = server.get_buffer(VISION_STREAM_ROAD); REQUIRE(buf != nullptr); *((uint64_t*)buf->addr) = 1234; @@ -83,14 +83,14 @@ TEST_CASE("Send single buffer"){ TEST_CASE("Test no conflate"){ VisionIpcServer server("camerad"); - server.create_buffers(VISION_STREAM_YUV_BACK, 1, true, 100, 100); + server.create_buffers(VISION_STREAM_ROAD, 1, true, 100, 100); server.start_listener(); - VisionIpcClient client = VisionIpcClient("camerad", VISION_STREAM_YUV_BACK, false); + VisionIpcClient client = VisionIpcClient("camerad", VISION_STREAM_ROAD, false); REQUIRE(client.connect()); zmq_sleep(); - VisionBuf * buf = server.get_buffer(VISION_STREAM_YUV_BACK); + VisionBuf * buf = server.get_buffer(VISION_STREAM_ROAD); REQUIRE(buf != nullptr); VisionIpcBufExtra extra = {0}; @@ -111,14 +111,14 @@ TEST_CASE("Test no conflate"){ TEST_CASE("Test conflate"){ VisionIpcServer server("camerad"); - server.create_buffers(VISION_STREAM_YUV_BACK, 1, true, 100, 100); + server.create_buffers(VISION_STREAM_ROAD, 1, true, 100, 100); server.start_listener(); - VisionIpcClient client = VisionIpcClient("camerad", VISION_STREAM_YUV_BACK, true); + VisionIpcClient client = VisionIpcClient("camerad", VISION_STREAM_ROAD, true); REQUIRE(client.connect()); zmq_sleep(); - VisionBuf * buf = server.get_buffer(VISION_STREAM_YUV_BACK); + VisionBuf * buf = server.get_buffer(VISION_STREAM_ROAD); REQUIRE(buf != nullptr); VisionIpcBufExtra extra = {0}; diff --git a/common/api/__init__.py b/common/api/__init__.py index 5b1d66cf7..8b83dfc64 100644 --- a/common/api/__init__.py +++ b/common/api/__init__.py @@ -3,7 +3,7 @@ import os import requests from datetime import datetime, timedelta from common.basedir import PERSIST -from selfdrive.version import version +from selfdrive.version import get_version API_HOST = os.getenv('API_HOST', 'https://api.commadotai.com') @@ -34,13 +34,13 @@ class Api(): if isinstance(token, bytes): token = token.decode('utf8') return token - + def api_get(endpoint, method='GET', timeout=None, access_token=None, **params): headers = {} if access_token is not None: - headers['Authorization'] = "JWT "+access_token + headers['Authorization'] = "JWT " + access_token - headers['User-Agent'] = "openpilot-" + version + headers['User-Agent'] = "openpilot-" + get_version() return requests.request(method, API_HOST + "/" + endpoint, timeout=timeout, headers=headers, params=params) diff --git a/common/cython_hacks.py b/common/cython_hacks.py deleted file mode 100644 index d0e154746..000000000 --- a/common/cython_hacks.py +++ /dev/null @@ -1,23 +0,0 @@ -import os -import sysconfig -from Cython.Distutils import build_ext - -def get_ext_filename_without_platform_suffix(filename): - name, ext = os.path.splitext(filename) - ext_suffix = sysconfig.get_config_var('EXT_SUFFIX') - - if ext_suffix == ext: - return filename - - ext_suffix = ext_suffix.replace(ext, '') - idx = name.find(ext_suffix) - - if idx == -1: - return filename - else: - return name[:idx] + ext - -class BuildExtWithoutPlatformSuffix(build_ext): - def get_ext_filename(self, ext_name): - filename = super().get_ext_filename(ext_name) - return get_ext_filename_without_platform_suffix(filename) diff --git a/common/transformations/model.py b/common/transformations/model.py index 1215909fc..5d6458fad 100644 --- a/common/transformations/model.py +++ b/common/transformations/model.py @@ -84,6 +84,12 @@ bigmodel_frame_from_road_frame = np.dot(bigmodel_intrinsics, bigmodel_frame_from_calib_frame = np.dot(bigmodel_intrinsics, get_view_frame_from_calib_frame(0, 0, 0, 0)) +sbigmodel_frame_from_road_frame = np.dot(sbigmodel_intrinsics, + get_view_frame_from_road_frame(0, 0, 0, model_height)) + +sbigmodel_frame_from_calib_frame = np.dot(sbigmodel_intrinsics, + get_view_frame_from_calib_frame(0, 0, 0, 0)) + medmodel_frame_from_road_frame = np.dot(medmodel_intrinsics, get_view_frame_from_road_frame(0, 0, 0, model_height)) diff --git a/docs/CARS.md b/docs/CARS.md index 30f3131c7..9c762ee7b 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -7,8 +7,8 @@ | Acura | ILX 2016-19 | AcuraWatch Plus | openpilot | 25mph1 | 25mph | | Acura | RDX 2016-18 | AcuraWatch Plus | openpilot | 25mph1 | 12mph | | Acura | RDX 2019-21 | All | Stock | 0mph | 3mph | -| Honda | Accord 2018-20 | All | Stock | 0mph | 3mph | -| Honda | Accord Hybrid 2018-20 | All | Stock | 0mph | 3mph | +| Honda | Accord 2018-21 | All | Stock | 0mph | 3mph | +| Honda | Accord Hybrid 2018-21 | All | Stock | 0mph | 3mph | | Honda | Civic Hatchback 2017-21 | Honda Sensing | Stock | 0mph | 12mph | | Honda | Civic Coupe 2016-18 | Honda Sensing | openpilot | 0mph | 12mph | | Honda | Civic Coupe 2019-20 | All | Stock | 0mph | 2mph2 | @@ -34,9 +34,10 @@ | Lexus | ES Hybrid 2017-18 | LSS | Stock3| 0mph | 0mph | | Lexus | ES Hybrid 2019-21 | All | openpilot | 0mph | 0mph | | Lexus | IS 2017-2019 | All | Stock | 22mph | 0mph | -| Lexus | NX 2018 | All | Stock3| 0mph | 0mph | +| Lexus | NX 2018-2019 | All | Stock3| 0mph | 0mph | | Lexus | NX 2020 | All | openpilot | 0mph | 0mph | | Lexus | NX Hybrid 2018-19 | All | Stock3| 0mph | 0mph | +| Lexus | RC 2020 | All | Stock | 22mph | 0mph | | Lexus | RX 2016-18 | All | Stock3| 0mph | 0mph | | Lexus | RX 2020-21 | All | openpilot | 0mph | 0mph | | Lexus | RX Hybrid 2016-19 | All | Stock3| 0mph | 0mph | @@ -49,7 +50,7 @@ | Toyota | Camry 2021-22 | All | openpilot | 0mph4 | 0mph | | Toyota | Camry Hybrid 2018-20 | All | Stock | 0mph4 | 0mph | | Toyota | Camry Hybrid 2021-22 | All | openpilot | 0mph | 0mph | -| Toyota | C-HR 2017-20 | All | Stock | 0mph | 0mph | +| Toyota | C-HR 2017-21 | All | Stock | 0mph | 0mph | | Toyota | C-HR Hybrid 2017-19 | All | Stock | 0mph | 0mph | | Toyota | Corolla 2017-19 | All | Stock3| 20mph1 | 0mph | | Toyota | Corolla 2020-22 | All | openpilot | 0mph | 0mph | @@ -82,6 +83,7 @@ | Audi | A3 2014-19 | ACC + Lane Assist | Stock | 0mph | 0mph | | Audi | A3 Sportback e-tron 2017-18 | ACC + Lane Assist | Stock | 0mph | 0mph | | Audi | Q2 2018 | ACC + Lane Assist | Stock | 0mph | 0mph | +| Audi | Q3 2020-21 | ACC + Lane Assist | Stock | 0mph | 0mph | | Audi | S3 2015 | ACC + Lane Assist | Stock | 0mph | 0mph | | Buick | Regal 20181 | Adaptive Cruise | openpilot | 0mph | 7mph | | Cadillac | ATS 20181 | Adaptive Cruise | openpilot | 0mph | 7mph | @@ -120,7 +122,7 @@ | Jeep | Grand Cherokee 2019-20 | Adaptive Cruise | Stock | 0mph | 39mph | | Kia | Ceed 2019 | SCC + LKAS | Stock | 0mph | 0mph | | Kia | Forte 2018-21 | SCC + LKAS | Stock | 0mph | 0mph | -| Kia | K5 2021 | SCC + LFA | Stock | 0mph | 0mph | +| Kia | K5 2021-22 | SCC + LFA | Stock | 0mph | 0mph | | Kia | Niro EV 2019-21 | SCC + LKAS | Stock | 0mph | 0mph | | Kia | Niro Hybrid 2021 | SCC + LKAS | Stock | 0mph | 0mph | | Kia | Niro PHEV 2019 | SCC + LKAS | Stock | 10mph | 32mph | diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 8dcdeee50..b9da6c576 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1,16 +1,35 @@ # How to contribute -Our software is open source so you can solve your own problems without needing help from others. And if you solve a problem and are so kind, you can upstream it for the rest of the world to use. +Our software is open source so you can solve your own problems without needing help from others. And if you solve a problem and are so kind, you can upstream it for the rest of the world to use. Check out our [post about externalization](https://blog.comma.ai/a-2020-theme-externalization/). -Most open source development activity is coordinated through our [GitHub Discussions](https://github.com/commaai/openpilot/discussions) and [Discord](https://discord.comma.ai). A lot of documentation is available on our [blog](https://blog.comma.ai/). +Most open source development activity is coordinated through our [GitHub Discussions](https://github.com/commaai/openpilot/discussions) and [Discord](https://discord.comma.ai). A lot of documentation is available at https://docs.comma.ai and on our [blog](https://blog.comma.ai/). -## Getting Started +### Getting Started * Setup your [development environment](../tools/) * Join our [Discord](https://discord.comma.ai) * Make sure you have a [GitHub account](https://github.com/signup/free) * Fork [our repositories](https://github.com/commaai) on GitHub +## Pull Requests + +Pull requests should be against the master branch. Welcomed contributions include bug reports, car ports, and any [open issue](https://github.com/commaai/openpilot/issues). If you're unsure about a contribution, feel free to open a discussion, issue, or draft PR to discuss the problem you're trying to solve. + +A good pull request has all of the following: +* a clearly stated purpose +* every line changed directly contributes to the stated purpose +* verification, i.e. how did you test your PR? +* justification + * if you've optimized something, post benchmarks to prove it's better + * if you've improved your car's tuning, post before and after plots +* passes the CI tests + +### Car Ports + +We've released a [Model Port guide](https://blog.comma.ai/openpilot-port-guide-for-toyota-models/) for porting to Toyota/Lexus models. + +If you port openpilot to a substantially new car brand, see this more generic [Brand Port guide](https://blog.comma.ai/how-to-write-a-car-port-for-openpilot/). + ## Testing ### Automated Testing @@ -20,27 +39,3 @@ All PRs and commits are automatically checked by GitHub Actions. Check out `.git ### Code Style and Linting Code is automatically checked for style by GitHub Actions as part of the automated tests. You can also run these tests yourself by running `pre-commit run --all`. - -## Car Ports - -We've released a [Model Port guide](https://blog.comma.ai/openpilot-port-guide-for-toyota-models/) for porting to Toyota/Lexus models. - -If you port openpilot to a substantially new car brand, see this more generic [Brand Port guide](https://blog.comma.ai/how-to-write-a-car-port-for-openpilot/). - -## Pull Requests - -Pull requests should be against the master branch. Before running master on in-car hardware, you'll need to clone the submodules too. That can be done by recursively cloning the repository: -``` -git clone https://github.com/commaai/openpilot.git --recursive -``` -Or alternatively, when on the master branch: -``` -git submodule update --init -``` -The reasons for having submodules on a dedicated repository and our new development philosophy can be found in our [post about externalization](https://blog.comma.ai/a-2020-theme-externalization/). -Modules that are in seperate repositories include: -* cereal -* laika -* opendbc -* panda -* rednose diff --git a/launch_env.sh b/launch_env.sh index 47d98cc92..cb0a0572d 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -11,7 +11,7 @@ if [ -z "$REQUIRED_NEOS_VERSION" ]; then fi if [ -z "$AGNOS_VERSION" ]; then - export AGNOS_VERSION="2" + export AGNOS_VERSION="3" fi if [ -z "$PASSIVE" ]; then diff --git a/models/supercombo.dlc b/models/supercombo.dlc index 3a4657590..02d0a56f8 100644 Binary files a/models/supercombo.dlc and b/models/supercombo.dlc differ diff --git a/opendbc/acura_ilx_2016_can_generated.dbc b/opendbc/acura_ilx_2016_can_generated.dbc index 3ad7520cb..065fe7d5a 100644 --- a/opendbc/acura_ilx_2016_can_generated.dbc +++ b/opendbc/acura_ilx_2016_can_generated.dbc @@ -323,4 +323,3 @@ VAL_ 506 FCW 3 "fcw" 2 "fcw" 1 "fcw" 0 "no_fcw" ; VAL_ 780 HUD_LEAD 3 "no_car" 2 "solid_car" 1 "dashed_car" 0 "no_car" ; VAL_ 829 BEEP 3 "single_beep" 2 "triple_beep" 1 "repeated_beep" 0 "no_beep" ; -CM_ "CHFFR_METRIC 342 STEER_ANGLE STEER_ANGLE 0.36 180; CHFFR_METRIC 380 ENGINE_RPM ENGINE_RPM 1 0; CHFFR_METRIC 804 ENGINE_TEMPERATURE ENGINE_TEMPERATURE 1 0"; diff --git a/opendbc/acura_rdx_2018_can_generated.dbc b/opendbc/acura_rdx_2018_can_generated.dbc index 48dbddd80..2acbe3fd7 100644 --- a/opendbc/acura_rdx_2018_can_generated.dbc +++ b/opendbc/acura_rdx_2018_can_generated.dbc @@ -310,4 +310,3 @@ VAL_ 392 GEAR 26 "S" 4 "D" 3 "N" 2 "R" 1 "P" ; VAL_ 399 STEER_STATUS 6 "tmp_fault" 5 "fault_1" 4 "no_torque_alert_2" 3 "low_speed_lockout" 2 "no_torque_alert_1" 0 "normal" ; VAL_ 422 CRUISE_BUTTONS 7 "tbd" 6 "tbd" 5 "tbd" 4 "accel_res" 3 "decel_set" 2 "cancel" 1 "main" 0 "none" ; -CM_ "CHFFR_METRIC 342 STEER_ANGLE STEER_ANGLE 0.36 180; CHFFR_METRIC 380 ENGINE_RPM ENGINE_RPM 1 0; CHFFR_METRIC 804 ENGINE_TEMPERATURE ENGINE_TEMPERATURE 1 0"; diff --git a/opendbc/acura_rdx_2020_can_generated.dbc b/opendbc/acura_rdx_2020_can_generated.dbc index a114e4e13..3fadba455 100644 --- a/opendbc/acura_rdx_2020_can_generated.dbc +++ b/opendbc/acura_rdx_2020_can_generated.dbc @@ -432,4 +432,3 @@ VAL_ 806 CMBS_BUTTON 3 "pressed" 0 "released" ; VAL_ 891 WIPERS 4 "High" 2 "Low" 0 "Off" ; VAL_ 829 BEEP 3 "single_beep" 2 "triple_beep" 1 "repeated_beep" 0 "no_beep" ; -CM_ "CHFFR_METRIC 330 STEER_ANGLE STEER_ANGLE 0.36 180; CHFFR_METRIC 380 ENGINE_RPM ENGINE_RPM 1 0; CHFFR_METRIC 804 ENGINE_TEMPERATURE ENGINE_TEMPERATURE 1 0"; diff --git a/opendbc/honda_accord_2018_can_generated.dbc b/opendbc/honda_accord_2018_can_generated.dbc index 1c78b38b6..0b79ceef4 100644 --- a/opendbc/honda_accord_2018_can_generated.dbc +++ b/opendbc/honda_accord_2018_can_generated.dbc @@ -489,4 +489,3 @@ VAL_ 806 CMBS_BUTTON 3 "pressed" 0 "released" ; VAL_ 891 WIPERS 4 "High" 2 "Low" 0 "Off" ; VAL_ 829 BEEP 3 "single_beep" 2 "triple_beep" 1 "repeated_beep" 0 "no_beep" ; -CM_ "CHFFR_METRIC 330 STEER_ANGLE STEER_ANGLE 0.36 180; CHFFR_METRIC 380 ENGINE_RPM ENGINE_RPM 1 0; CHFFR_METRIC 804 ENGINE_TEMPERATURE ENGINE_TEMPERATURE 1 0"; diff --git a/opendbc/honda_civic_hatchback_ex_2017_can_generated.dbc b/opendbc/honda_civic_hatchback_ex_2017_can_generated.dbc index fe9a13af8..cb2a3592a 100644 --- a/opendbc/honda_civic_hatchback_ex_2017_can_generated.dbc +++ b/opendbc/honda_civic_hatchback_ex_2017_can_generated.dbc @@ -490,4 +490,3 @@ VAL_ 806 CMBS_BUTTON 3 "pressed" 0 "released" ; VAL_ 891 WIPERS 4 "High" 2 "Low" 0 "Off" ; VAL_ 829 BEEP 3 "single_beep" 2 "triple_beep" 1 "repeated_beep" 0 "no_beep" ; -CM_ "CHFFR_METRIC 330 STEER_ANGLE STEER_ANGLE 0.36 180; CHFFR_METRIC 380 ENGINE_RPM ENGINE_RPM 1 0; CHFFR_METRIC 804 ENGINE_TEMPERATURE ENGINE_TEMPERATURE 1 0"; diff --git a/opendbc/honda_civic_sedan_16_diesel_2019_can_generated.dbc b/opendbc/honda_civic_sedan_16_diesel_2019_can_generated.dbc index 8e7199cc9..0ddb0bd40 100644 --- a/opendbc/honda_civic_sedan_16_diesel_2019_can_generated.dbc +++ b/opendbc/honda_civic_sedan_16_diesel_2019_can_generated.dbc @@ -485,4 +485,3 @@ VAL_ 806 CMBS_BUTTON 3 "pressed" 0 "released" ; VAL_ 891 WIPERS 4 "High" 2 "Low" 0 "Off" ; VAL_ 829 BEEP 3 "single_beep" 2 "triple_beep" 1 "repeated_beep" 0 "no_beep" ; -CM_ "CHFFR_METRIC 330 STEER_ANGLE STEER_ANGLE 0.36 180; CHFFR_METRIC 380 ENGINE_RPM ENGINE_RPM 1 0; CHFFR_METRIC 804 ENGINE_TEMPERATURE ENGINE_TEMPERATURE 1 0"; diff --git a/opendbc/honda_civic_touring_2016_can_generated.dbc b/opendbc/honda_civic_touring_2016_can_generated.dbc index 56168a6fb..9315c822a 100644 --- a/opendbc/honda_civic_touring_2016_can_generated.dbc +++ b/opendbc/honda_civic_touring_2016_can_generated.dbc @@ -394,4 +394,3 @@ VAL_ 884 DASHBOARD_ALERT 0 "none" 51 "acc_problem" 55 "cmbs_problem" 75 "key_not VAL_ 891 WIPERS 4 "High" 2 "Low" 0 "Off" ; VAL_ 927 ACC_ALERTS 29 "esp_active_acc_canceled" 10 "b_pedal_applied" 9 "speed_too_low" 8 "speed_too_high" 7 "p_brake_applied" 6 "gear_no_d" 5 "seatbelt" 4 "too_steep_downhill" 3 "too_steep_uphill" 2 "too_close" 1 "no_vehicle_ahead" ; -CM_ "CHFFR_METRIC 330 STEER_ANGLE STEER_ANGLE 0.36 180; CHFFR_METRIC 380 ENGINE_RPM ENGINE_RPM 1 0; CHFFR_METRIC 804 ENGINE_TEMPERATURE ENGINE_TEMPERATURE 1 0"; diff --git a/opendbc/honda_crv_ex_2017_can_generated.dbc b/opendbc/honda_crv_ex_2017_can_generated.dbc index cef50babc..3be8848bd 100644 --- a/opendbc/honda_crv_ex_2017_can_generated.dbc +++ b/opendbc/honda_crv_ex_2017_can_generated.dbc @@ -496,4 +496,3 @@ VAL_ 806 CMBS_BUTTON 3 "pressed" 0 "released" ; VAL_ 891 WIPERS 4 "High" 2 "Low" 0 "Off" ; VAL_ 829 BEEP 3 "single_beep" 2 "triple_beep" 1 "repeated_beep" 0 "no_beep" ; -CM_ "CHFFR_METRIC 330 STEER_ANGLE STEER_ANGLE 0.36 180; CHFFR_METRIC 380 ENGINE_RPM ENGINE_RPM 1 0; CHFFR_METRIC 804 ENGINE_TEMPERATURE ENGINE_TEMPERATURE 1 0"; diff --git a/opendbc/honda_crv_executive_2016_can_generated.dbc b/opendbc/honda_crv_executive_2016_can_generated.dbc index d4249ea02..55611dd9e 100644 --- a/opendbc/honda_crv_executive_2016_can_generated.dbc +++ b/opendbc/honda_crv_executive_2016_can_generated.dbc @@ -316,4 +316,3 @@ VAL_ 422 LIGHTS_SETTING 3 "high_beam" 2 "low_beam" 1 "position" 0 "no_lights" ; VAL_ 422 CRUISE_SETTING 3 "distance_adj" 2 "tbd" 1 "lkas_button" 0 "none" ; VAL_ 891 WIPERS 4 "High" 2 "Low" 0 "Off" ; -CM_ "CHFFR_METRIC 342 STEER_ANGLE STEER_ANGLE 0.36 180; CHFFR_METRIC 380 ENGINE_RPM ENGINE_RPM 1 0; CHFFR_METRIC 804 ENGINE_TEMPERATURE ENGINE_TEMPERATURE 1 0"; diff --git a/opendbc/honda_crv_hybrid_2019_can_generated.dbc b/opendbc/honda_crv_hybrid_2019_can_generated.dbc index 4a6ad4b82..01a24c96c 100644 --- a/opendbc/honda_crv_hybrid_2019_can_generated.dbc +++ b/opendbc/honda_crv_hybrid_2019_can_generated.dbc @@ -478,4 +478,3 @@ VAL_ 806 CMBS_BUTTON 3 "pressed" 0 "released" ; VAL_ 891 WIPERS 4 "High" 2 "Low" 0 "Off" ; VAL_ 829 BEEP 3 "single_beep" 2 "triple_beep" 1 "repeated_beep" 0 "no_beep" ; -CM_ "CHFFR_METRIC 330 STEER_ANGLE STEER_ANGLE 0.36 180; CHFFR_METRIC 380 ENGINE_RPM ENGINE_RPM 1 0; CHFFR_METRIC 804 ENGINE_TEMPERATURE ENGINE_TEMPERATURE 1 0"; diff --git a/opendbc/honda_crv_touring_2016_can_generated.dbc b/opendbc/honda_crv_touring_2016_can_generated.dbc index 35a3da361..45d85cad7 100644 --- a/opendbc/honda_crv_touring_2016_can_generated.dbc +++ b/opendbc/honda_crv_touring_2016_can_generated.dbc @@ -320,4 +320,3 @@ VAL_ 422 LIGHTS_SETTING 3 "high_beam" 2 "low_beam" 1 "position" 0 "no_lights" ; VAL_ 422 CRUISE_SETTING 3 "distance_adj" 2 "tbd" 1 "lkas_button" 0 "none" ; VAL_ 891 WIPERS 4 "High" 2 "Low" 0 "Off" ; -CM_ "CHFFR_METRIC 342 STEER_ANGLE STEER_ANGLE 0.36 180; CHFFR_METRIC 380 ENGINE_RPM ENGINE_RPM 1 0; CHFFR_METRIC 804 ENGINE_TEMPERATURE ENGINE_TEMPERATURE 1 0"; diff --git a/opendbc/honda_fit_ex_2018_can_generated.dbc b/opendbc/honda_fit_ex_2018_can_generated.dbc index 0459cf5b8..df47a001d 100644 --- a/opendbc/honda_fit_ex_2018_can_generated.dbc +++ b/opendbc/honda_fit_ex_2018_can_generated.dbc @@ -345,4 +345,3 @@ VAL_ 422 CRUISE_BUTTONS 7 "tbd" 6 "tbd" 5 "tbd" 4 "accel_res" 3 "decel_set" 2 "c VAL_ 422 LIGHTS_SETTING 3 "high_beam" 2 "low_beam" 1 "position" 0 "no_lights" ; VAL_ 422 CRUISE_SETTING 3 "distance_adj" 2 "tbd" 1 "lkas_button" 0 "none" ; -CM_ "CHFFR_METRIC 342 STEER_ANGLE STEER_ANGLE 0.36 180; CHFFR_METRIC 380 ENGINE_RPM ENGINE_RPM 1 0; CHFFR_METRIC 804 ENGINE_TEMPERATURE ENGINE_TEMPERATURE 1 0"; diff --git a/opendbc/honda_insight_ex_2019_can_generated.dbc b/opendbc/honda_insight_ex_2019_can_generated.dbc index 024f68d3c..5bb0ab01c 100644 --- a/opendbc/honda_insight_ex_2019_can_generated.dbc +++ b/opendbc/honda_insight_ex_2019_can_generated.dbc @@ -474,4 +474,3 @@ BO_ 1029 DOORS_STATUS: 8 BDY VAL_ 419 GEAR 10 "R" 1 "D" 0 "P"; VAL_ 419 GEAR_SHIFTER 32 "D" 16 "N" 8 "R" 4 "P" ; -CM_ "CHFFR_METRIC 330 STEER_ANGLE STEER_ANGLE 0.36 180; CHFFR_METRIC 380 ENGINE_RPM ENGINE_RPM 1 0; CHFFR_METRIC 804 ENGINE_TEMPERATURE ENGINE_TEMPERATURE 1 0"; diff --git a/opendbc/honda_odyssey_exl_2018_generated.dbc b/opendbc/honda_odyssey_exl_2018_generated.dbc index a27fc5be4..586d2f8ab 100644 --- a/opendbc/honda_odyssey_exl_2018_generated.dbc +++ b/opendbc/honda_odyssey_exl_2018_generated.dbc @@ -357,4 +357,3 @@ VAL_ 891 WIPERS 4 "High" 2 "Low" 0 "Off" ; VAL_ 927 ACC_ALERTS 29 "esp_active_acc_canceled" 10 "b_pedal_applied" 9 "speed_too_low" 8 "speed_too_high" 7 "p_brake_applied" 6 "gear_no_d" 5 "seatbelt" 4 "too_steep_downhill" 3 "too_steep_uphill" 2 "too_close" 1 "no_vehicle_ahead" ; VAL_ 806 CMBS_BUTTON 3 "pressed" 0 "released" ; -CM_ "CHFFR_METRIC 342 STEER_ANGLE STEER_ANGLE 0.36 180; CHFFR_METRIC 380 ENGINE_RPM ENGINE_RPM 1 0; CHFFR_METRIC 804 ENGINE_TEMPERATURE ENGINE_TEMPERATURE 1 0"; diff --git a/opendbc/honda_pilot_touring_2017_can_generated.dbc b/opendbc/honda_pilot_touring_2017_can_generated.dbc index e9217d3e4..2cc61de8b 100644 --- a/opendbc/honda_pilot_touring_2017_can_generated.dbc +++ b/opendbc/honda_pilot_touring_2017_can_generated.dbc @@ -313,4 +313,3 @@ VAL_ 422 CRUISE_BUTTONS 7 "tbd" 6 "tbd" 5 "tbd" 4 "accel_res" 3 "decel_set" 2 "c VAL_ 422 LIGHTS_SETTING 3 "high_beam" 2 "low_beam" 1 "position" 0 "no_lights" ; VAL_ 422 CRUISE_SETTING 3 "distance_adj" 2 "tbd" 1 "lkas_button" 0 "none" ; -CM_ "CHFFR_METRIC 342 STEER_ANGLE STEER_ANGLE 0.36 180; CHFFR_METRIC 380 ENGINE_RPM ENGINE_RPM 1 0; CHFFR_METRIC 804 ENGINE_TEMPERATURE ENGINE_TEMPERATURE 1 0"; diff --git a/opendbc/honda_ridgeline_black_edition_2017_can_generated.dbc b/opendbc/honda_ridgeline_black_edition_2017_can_generated.dbc index 02989aabc..19ded5414 100644 --- a/opendbc/honda_ridgeline_black_edition_2017_can_generated.dbc +++ b/opendbc/honda_ridgeline_black_edition_2017_can_generated.dbc @@ -308,4 +308,3 @@ VAL_ 422 CRUISE_BUTTONS 7 "tbd" 6 "tbd" 5 "tbd" 4 "accel_res" 3 "decel_set" 2 "c VAL_ 422 LIGHTS_SETTING 3 "high_beam" 2 "low_beam" 1 "position" 0 "no_lights" ; VAL_ 422 CRUISE_SETTING 3 "distance_adj" 2 "tbd" 1 "lkas_button" 0 "none" ; -CM_ "CHFFR_METRIC 342 STEER_ANGLE STEER_ANGLE 0.36 180; CHFFR_METRIC 380 ENGINE_RPM ENGINE_RPM 1 0; CHFFR_METRIC 804 ENGINE_TEMPERATURE ENGINE_TEMPERATURE 1 0"; diff --git a/opendbc/lexus_ct200h_2018_pt_generated.dbc b/opendbc/lexus_ct200h_2018_pt_generated.dbc index 883f2ee0a..06fcd3dc1 100644 --- a/opendbc/lexus_ct200h_2018_pt_generated.dbc +++ b/opendbc/lexus_ct200h_2018_pt_generated.dbc @@ -370,7 +370,6 @@ VAL_ 1162 TSGN3 0 "none" 1 "speed sign" 2 "0 unlimited" 7 "unlimited" 16 "highwa VAL_ 1162 SPLSGN3 15 "conditional blank" 4 "wet road" 5 "rain" 0 "none"; -CM_ "CHFFR_METRIC 37 STEER_ANGLE STEER_ANGLE 0.36 180"; CM_ "Imported file _comma.dbc starts here"; diff --git a/opendbc/lexus_is_2018_pt_generated.dbc b/opendbc/lexus_is_2018_pt_generated.dbc index 30e8cbbaf..571dfee5d 100644 --- a/opendbc/lexus_is_2018_pt_generated.dbc +++ b/opendbc/lexus_is_2018_pt_generated.dbc @@ -370,7 +370,6 @@ VAL_ 1162 TSGN3 0 "none" 1 "speed sign" 2 "0 unlimited" 7 "unlimited" 16 "highwa VAL_ 1162 SPLSGN3 15 "conditional blank" 4 "wet road" 5 "rain" 0 "none"; -CM_ "CHFFR_METRIC 37 STEER_ANGLE STEER_ANGLE 0.36 180"; CM_ "Imported file _comma.dbc starts here"; diff --git a/opendbc/lexus_nx300_2018_pt_generated.dbc b/opendbc/lexus_nx300_2018_pt_generated.dbc index 7b4fcaa13..5351a4f49 100644 --- a/opendbc/lexus_nx300_2018_pt_generated.dbc +++ b/opendbc/lexus_nx300_2018_pt_generated.dbc @@ -370,7 +370,6 @@ VAL_ 1162 TSGN3 0 "none" 1 "speed sign" 2 "0 unlimited" 7 "unlimited" 16 "highwa VAL_ 1162 SPLSGN3 15 "conditional blank" 4 "wet road" 5 "rain" 0 "none"; -CM_ "CHFFR_METRIC 37 STEER_ANGLE STEER_ANGLE 0.36 180"; CM_ "Imported file _comma.dbc starts here"; diff --git a/opendbc/lexus_nx300h_2018_pt_generated.dbc b/opendbc/lexus_nx300h_2018_pt_generated.dbc index c7cb4616d..0ec03a45c 100644 --- a/opendbc/lexus_nx300h_2018_pt_generated.dbc +++ b/opendbc/lexus_nx300h_2018_pt_generated.dbc @@ -370,7 +370,6 @@ VAL_ 1162 TSGN3 0 "none" 1 "speed sign" 2 "0 unlimited" 7 "unlimited" 16 "highwa VAL_ 1162 SPLSGN3 15 "conditional blank" 4 "wet road" 5 "rain" 0 "none"; -CM_ "CHFFR_METRIC 37 STEER_ANGLE STEER_ANGLE 0.36 180"; CM_ "Imported file _comma.dbc starts here"; diff --git a/opendbc/lexus_rx_350_2016_pt_generated.dbc b/opendbc/lexus_rx_350_2016_pt_generated.dbc index 7a1b1a704..e4ea2289b 100644 --- a/opendbc/lexus_rx_350_2016_pt_generated.dbc +++ b/opendbc/lexus_rx_350_2016_pt_generated.dbc @@ -370,7 +370,6 @@ VAL_ 1162 TSGN3 0 "none" 1 "speed sign" 2 "0 unlimited" 7 "unlimited" 16 "highwa VAL_ 1162 SPLSGN3 15 "conditional blank" 4 "wet road" 5 "rain" 0 "none"; -CM_ "CHFFR_METRIC 37 STEER_ANGLE STEER_ANGLE 0.36 180"; CM_ "Imported file _comma.dbc starts here"; diff --git a/opendbc/lexus_rx_hybrid_2017_pt_generated.dbc b/opendbc/lexus_rx_hybrid_2017_pt_generated.dbc index b481318a1..fd9f217f2 100644 --- a/opendbc/lexus_rx_hybrid_2017_pt_generated.dbc +++ b/opendbc/lexus_rx_hybrid_2017_pt_generated.dbc @@ -370,7 +370,6 @@ VAL_ 1162 TSGN3 0 "none" 1 "speed sign" 2 "0 unlimited" 7 "unlimited" 16 "highwa VAL_ 1162 SPLSGN3 15 "conditional blank" 4 "wet road" 5 "rain" 0 "none"; -CM_ "CHFFR_METRIC 37 STEER_ANGLE STEER_ANGLE 0.36 180"; CM_ "Imported file _comma.dbc starts here"; diff --git a/opendbc/tesla_can.dbc b/opendbc/tesla_can.dbc index 57a63e128..cfa86aac1 100644 --- a/opendbc/tesla_can.dbc +++ b/opendbc/tesla_can.dbc @@ -748,4 +748,3 @@ VAL_ 1160 DAS_steeringControlType 1 "ANGLE_CONTROL" 3 "DISABLED" 0 "NONE" 2 "RES VAL_ 1160 DAS_steeringHapticRequest 1 "ACTIVE" 0 "IDLE" ; -CM_ "CHFFR_METRIC 1160 DAS_steeringAngleRequest STEER_ANGLE 0.1098666 180; CHFFR_METRIC 264 DI_motorRPM ENGINE_RPM 1 0"; diff --git a/opendbc/toyota_avalon_2017_pt_generated.dbc b/opendbc/toyota_avalon_2017_pt_generated.dbc index 8043d75ae..901616d58 100644 --- a/opendbc/toyota_avalon_2017_pt_generated.dbc +++ b/opendbc/toyota_avalon_2017_pt_generated.dbc @@ -370,7 +370,6 @@ VAL_ 1162 TSGN3 0 "none" 1 "speed sign" 2 "0 unlimited" 7 "unlimited" 16 "highwa VAL_ 1162 SPLSGN3 15 "conditional blank" 4 "wet road" 5 "rain" 0 "none"; -CM_ "CHFFR_METRIC 37 STEER_ANGLE STEER_ANGLE 0.36 180"; CM_ "Imported file _comma.dbc starts here"; diff --git a/opendbc/toyota_camry_hybrid_2018_pt_generated.dbc b/opendbc/toyota_camry_hybrid_2018_pt_generated.dbc index 72ef797a3..55ebbb6a4 100644 --- a/opendbc/toyota_camry_hybrid_2018_pt_generated.dbc +++ b/opendbc/toyota_camry_hybrid_2018_pt_generated.dbc @@ -370,7 +370,6 @@ VAL_ 1162 TSGN3 0 "none" 1 "speed sign" 2 "0 unlimited" 7 "unlimited" 16 "highwa VAL_ 1162 SPLSGN3 15 "conditional blank" 4 "wet road" 5 "rain" 0 "none"; -CM_ "CHFFR_METRIC 37 STEER_ANGLE STEER_ANGLE 0.36 180"; CM_ "Imported file _comma.dbc starts here"; diff --git a/opendbc/toyota_corolla_2017_pt_generated.dbc b/opendbc/toyota_corolla_2017_pt_generated.dbc index 9ff7b5852..5fa0e8399 100644 --- a/opendbc/toyota_corolla_2017_pt_generated.dbc +++ b/opendbc/toyota_corolla_2017_pt_generated.dbc @@ -370,7 +370,6 @@ VAL_ 1162 TSGN3 0 "none" 1 "speed sign" 2 "0 unlimited" 7 "unlimited" 16 "highwa VAL_ 1162 SPLSGN3 15 "conditional blank" 4 "wet road" 5 "rain" 0 "none"; -CM_ "CHFFR_METRIC 37 STEER_ANGLE STEER_ANGLE 0.36 180"; CM_ "Imported file _comma.dbc starts here"; diff --git a/opendbc/toyota_highlander_2017_pt_generated.dbc b/opendbc/toyota_highlander_2017_pt_generated.dbc index 7c279f0da..6745ef9a0 100644 --- a/opendbc/toyota_highlander_2017_pt_generated.dbc +++ b/opendbc/toyota_highlander_2017_pt_generated.dbc @@ -370,7 +370,6 @@ VAL_ 1162 TSGN3 0 "none" 1 "speed sign" 2 "0 unlimited" 7 "unlimited" 16 "highwa VAL_ 1162 SPLSGN3 15 "conditional blank" 4 "wet road" 5 "rain" 0 "none"; -CM_ "CHFFR_METRIC 37 STEER_ANGLE STEER_ANGLE 0.36 180"; CM_ "Imported file _comma.dbc starts here"; diff --git a/opendbc/toyota_highlander_hybrid_2018_pt_generated.dbc b/opendbc/toyota_highlander_hybrid_2018_pt_generated.dbc index a2b5da03a..c8870edfd 100644 --- a/opendbc/toyota_highlander_hybrid_2018_pt_generated.dbc +++ b/opendbc/toyota_highlander_hybrid_2018_pt_generated.dbc @@ -370,7 +370,6 @@ VAL_ 1162 TSGN3 0 "none" 1 "speed sign" 2 "0 unlimited" 7 "unlimited" 16 "highwa VAL_ 1162 SPLSGN3 15 "conditional blank" 4 "wet road" 5 "rain" 0 "none"; -CM_ "CHFFR_METRIC 37 STEER_ANGLE STEER_ANGLE 0.36 180"; CM_ "Imported file _comma.dbc starts here"; diff --git a/opendbc/toyota_nodsu_hybrid_pt_generated.dbc b/opendbc/toyota_nodsu_hybrid_pt_generated.dbc index a6d4dc6d7..8102d48cc 100644 --- a/opendbc/toyota_nodsu_hybrid_pt_generated.dbc +++ b/opendbc/toyota_nodsu_hybrid_pt_generated.dbc @@ -370,7 +370,6 @@ VAL_ 1162 TSGN3 0 "none" 1 "speed sign" 2 "0 unlimited" 7 "unlimited" 16 "highwa VAL_ 1162 SPLSGN3 15 "conditional blank" 4 "wet road" 5 "rain" 0 "none"; -CM_ "CHFFR_METRIC 37 STEER_ANGLE STEER_ANGLE 0.36 180"; CM_ "Imported file _comma.dbc starts here"; diff --git a/opendbc/toyota_nodsu_pt_generated.dbc b/opendbc/toyota_nodsu_pt_generated.dbc index c84fa415f..58ca9c51a 100644 --- a/opendbc/toyota_nodsu_pt_generated.dbc +++ b/opendbc/toyota_nodsu_pt_generated.dbc @@ -370,7 +370,6 @@ VAL_ 1162 TSGN3 0 "none" 1 "speed sign" 2 "0 unlimited" 7 "unlimited" 16 "highwa VAL_ 1162 SPLSGN3 15 "conditional blank" 4 "wet road" 5 "rain" 0 "none"; -CM_ "CHFFR_METRIC 37 STEER_ANGLE STEER_ANGLE 0.36 180"; CM_ "Imported file _comma.dbc starts here"; diff --git a/opendbc/toyota_prius_2017_pt_generated.dbc b/opendbc/toyota_prius_2017_pt_generated.dbc index af9603c7d..c3857583c 100644 --- a/opendbc/toyota_prius_2017_pt_generated.dbc +++ b/opendbc/toyota_prius_2017_pt_generated.dbc @@ -370,7 +370,6 @@ VAL_ 1162 TSGN3 0 "none" 1 "speed sign" 2 "0 unlimited" 7 "unlimited" 16 "highwa VAL_ 1162 SPLSGN3 15 "conditional blank" 4 "wet road" 5 "rain" 0 "none"; -CM_ "CHFFR_METRIC 37 STEER_ANGLE STEER_ANGLE 0.36 180"; CM_ "Imported file _comma.dbc starts here"; diff --git a/opendbc/toyota_rav4_2017_pt_generated.dbc b/opendbc/toyota_rav4_2017_pt_generated.dbc index 41021cce3..f70b86982 100644 --- a/opendbc/toyota_rav4_2017_pt_generated.dbc +++ b/opendbc/toyota_rav4_2017_pt_generated.dbc @@ -370,7 +370,6 @@ VAL_ 1162 TSGN3 0 "none" 1 "speed sign" 2 "0 unlimited" 7 "unlimited" 16 "highwa VAL_ 1162 SPLSGN3 15 "conditional blank" 4 "wet road" 5 "rain" 0 "none"; -CM_ "CHFFR_METRIC 37 STEER_ANGLE STEER_ANGLE 0.36 180"; CM_ "Imported file _comma.dbc starts here"; diff --git a/opendbc/toyota_rav4_hybrid_2017_pt_generated.dbc b/opendbc/toyota_rav4_hybrid_2017_pt_generated.dbc index 9dba21a70..e6345fa8c 100644 --- a/opendbc/toyota_rav4_hybrid_2017_pt_generated.dbc +++ b/opendbc/toyota_rav4_hybrid_2017_pt_generated.dbc @@ -370,7 +370,6 @@ VAL_ 1162 TSGN3 0 "none" 1 "speed sign" 2 "0 unlimited" 7 "unlimited" 16 "highwa VAL_ 1162 SPLSGN3 15 "conditional blank" 4 "wet road" 5 "rain" 0 "none"; -CM_ "CHFFR_METRIC 37 STEER_ANGLE STEER_ANGLE 0.36 180"; CM_ "Imported file _comma.dbc starts here"; diff --git a/opendbc/toyota_sienna_xle_2018_pt_generated.dbc b/opendbc/toyota_sienna_xle_2018_pt_generated.dbc index 05e282b07..9141a34b0 100644 --- a/opendbc/toyota_sienna_xle_2018_pt_generated.dbc +++ b/opendbc/toyota_sienna_xle_2018_pt_generated.dbc @@ -370,7 +370,6 @@ VAL_ 1162 TSGN3 0 "none" 1 "speed sign" 2 "0 unlimited" 7 "unlimited" 16 "highwa VAL_ 1162 SPLSGN3 15 "conditional blank" 4 "wet road" 5 "rain" 0 "none"; -CM_ "CHFFR_METRIC 37 STEER_ANGLE STEER_ANGLE 0.36 180"; CM_ "Imported file _comma.dbc starts here"; diff --git a/opendbc/vw_mqb_2010.dbc b/opendbc/vw_mqb_2010.dbc index 3a032c056..2302035d8 100644 --- a/opendbc/vw_mqb_2010.dbc +++ b/opendbc/vw_mqb_2010.dbc @@ -33,7 +33,7 @@ NS_ : BS_: -BU_: Airbag_MQB BAP_Tester_MQB BMS_MQB Datenlogger_MQB Gateway_MQB Getriebe_DQ_Hybrid_MQB Getriebe_DQ_MQB LEH_MQB Motor_Diesel_MQB Motor_Hybrid_MQB Motor_Otto_MQB SAK_MQB Waehlhebel_MQB Vector__XXX 9 l c i XXX +BU_: Airbag_MQB BAP_Tester_MQB BMS_MQB Datenlogger_MQB Gateway_MQB Getriebe_DQ_Hybrid_MQB Getriebe_DQ_MQB LEH_MQB Motor_Diesel_MQB Motor_Hybrid_MQB Motor_Otto_MQB SAK_MQB Waehlhebel_MQB Vector__XXX l c i XXX BO_ 290 ACC_06: 8 Gateway_MQB @@ -1253,19 +1253,17 @@ BO_ 780 ACC_02: 8 XXX SG_ ACC_Status_Anzeige : 61|3@1+ (1.0,0.0) [0.0|7] "" XXX BO_ 302 ACC_07: 8 XXX - SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX - SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX - SG_ XXX_Maybe_Engine_Start_Request : 12|2@1+ (1,0) [0|1] "" XXX - SG_ XXX_Always_1 : 14|1@1+ (1,0) [0|1] "" XXX - SG_ XXX_Maybe_Engine_Stop_Release : 15|1@1+ (1,0) [0|1] "" XXX - SG_ XXX_Unknown : 16|8@1+ (1,0) [0|255] "" XXX - SG_ ACC_Engaged : 27|1@1+ (1,0) [0|1] "Boolean" XXX - SG_ ACC_Anhalten : 28|1@1+ (1,0) [0|1] "Boolean" XXX - SG_ ACC_Anhaltevorgang : 29|1@1+ (1,0) [0|1] "Boolean" XXX - SG_ ACC_Anfahrvorgang : 30|1@1+ (1,0) [0|1] "Boolean" XXX - SG_ ACC_Anfahren : 31|1@1+ (1,0) [0|1] "Boolean" XXX - SG_ XXX_Lead_Car_Relative_Speed : 32|8@1+ (1,-153) [0|255] "" XXX - SG_ ACC_Sollbeschleunigung_01 : 53|11@1+ (0.005,-7.22) [-7.22|3.005] "Unit_MeterPerSeconSquar" XXX + SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX + SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX + SG_ ACC_Distance_to_Stop : 12|11@1+ (0.01,0) [0|1] "m" XXX + SG_ ACC_Hold_Request : 23|1@1+ (1,0) [0|1] "x" XXX + SG_ ACC_Boost_Request : 24|1@1+ (1,0) [0|1] "" XXX + SG_ ACC_Freewheel_Request : 25|1@1+ (1,0) [0|1] "" XXX + SG_ ACC_Freewheel_Type : 26|2@1+ (1,0) [0|3] "enum" XXX + SG_ ACC_Hold_Type : 28|3@1+ (1,0) [0|15] "enum" XXX + SG_ ACC_Hold_Release : 31|1@1+ (1,0) [0|1] "" XXX + SG_ ACC_Accel_Secondary : 32|8@1+ (0.03,-4.6) [-4.6|2.99] "m/s2" XXX + SG_ ACC_Accel_TSK : 53|11@1+ (0.005,-7.22) [-7.22|3.005] "m/s2" XXX BO_ 264 Fahrwerk_01: 8 XXX SG_ Fahrwerk_01_BZ : 8|4@1+ (1,0) [0|15] "" XXX @@ -1371,25 +1369,24 @@ BO_ 391 EV_Gearshift: 8 XXX SG_ RegenBrakingMode : 12|2@1+ (1,0) [0|3] "" XXX CM_ SG_ 134 LWI_Lenkradwinkel "Steering angle WITH variable ratio effect included"; -CM_ SG_ 159 EPS_HCA_Status "Status of Heading Control Assist feature" +CM_ SG_ 159 EPS_HCA_Status "Status of Heading Control Assist feature"; CM_ SG_ 159 EPS_Lenkmoment "Steering input by driver, torque"; CM_ SG_ 159 EPS_VZ_Lenkmoment "Steering input by driver, direction"; CM_ SG_ 159 EPS_Berechneter_LW "Raw steering angle, degrees"; -CM_ SG_ 159 EPS_VZ_BLW "Raw steering angle, direction" +CM_ SG_ 159 EPS_VZ_BLW "Raw steering angle, direction"; CM_ SG_ 173 COUNTERXX "Message not renamed to COUNTER because J533 rate-limiting makes it look like messages are being lost"; -CM_ SG_ 294 3 "May be zero when sent by older cameras"; -CM_ SG_ 294 7 "May be zero when sent by older cameras"; -CM_ SG_ 294 254 "May be zero when sent by older cameras"; +CM_ SG_ 294 SET_ME_0X3 "May be zero when sent by older cameras"; +CM_ SG_ 294 SET_ME_0X07 "May be zero when sent by older cameras"; +CM_ SG_ 294 SET_ME_0XFE "May be zero when sent by older cameras"; CM_ SG_ 294 Assist_Torque "Heading control input, torque"; CM_ SG_ 294 Assist_VZ "Heading control input, direction (sign)"; CM_ SG_ 294 HCA_Available "Must be 1 for steering rack to accept HCA commands"; -CM_ SG_ 302 XXX_Unknown "Weird format but changes with some other bits, maybe a data-level checksum?"; -CM_ SG_ 302 ACC_Engaged "ACC engaged and regulating speed"; -CM_ SG_ 302 ACC_Sollbeschleunigung_01 "Requested acceleration, mirrors ACC_06.ACC_Sollbeschleunigung_02"; -CM_ SG_ 302 ACC_Anfahren "Start up, mirrors ACC_06.Anfahren"; -CM_ SG_ 302 ACC_Anhaltevorgang "Triggers/requests ESP standstill, reflected in ESP_Anhaltevorgang_ACC_aktiv"; -CM_ SG_ 302 ACC_Anfahrvorgang "Releases ESP hold"; -CM_ SG_ 302 ACC_Anhalten "Halt, mirrors ACC_06.Anhalten"; +CM_ SG_ 302 ACC_Hold_Request "Active request for ABS brake hold in ACC_Hold_Type"; +CM_ SG_ 302 ACC_Boost_Request "Hybrid engine start related"; +CM_ SG_ 302 ACC_Freewheel_Request "Active request for DSG sailing/coasting in ACC_Freewheel_Type"; +CM_ SG_ 302 ACC_Hold_Release "Request to ABS to release brake hold"; +CM_ SG_ 302 ACC_Accel_Secondary "Target acceleration of the secondary controller"; +CM_ SG_ 302 ACC_Accel_TSK "Mirror of request to TSK to implement a target acceleration"; CM_ SG_ 870 Hazard_Switch "Four-way flashers active"; CM_ SG_ 870 Comfort_Signal_Left "Comfort turn signal active, left"; CM_ SG_ 870 Comfort_Signal_Right "Comfort turn signal active, right"; @@ -1415,6 +1412,9 @@ CM_ SG_ 391 GearPosition "Traditional PRND plus B-mode aggressive regen, B-mode CM_ SG_ 960 ZAS_Kl_15 "Indicates ignition on"; VAL_ 159 EPS_HCA_Status 0 "disabled" 1 "initializing" 2 "fault" 3 "ready" 4 "rejected" 5 "active"; VAL_ 173 GE_Fahrstufe 5 "P" 6 "R" 7 "N" 8 "D" 9 "S" 10 "E" 14 "T"; +VAL_ 288 TSK_Status 0 "init" 1 "disabled" 2 "enabled" 3 "regulating" 4 "accel_pedal_override" 5 "brake_only" 6 "temp_fault" 7 "perm_fault"; +VAL_ 302 ACC_Freewheel_Type 0 "freewheel_released" 1 "freewheel_not_permitted" 2 "freewheel_not_released" 3 "freewheel_requested" ; +VAL_ 302 ACC_Hold_Type 0 "no_request" 1 "hold" 2 "park" 3 "hold_standby" 4 "startup" 5 "loosen_over_ramp" ; VAL_ 391 GearPosition 2 "P" 3 "R" 4 "N" 5 "D" 6 "D"; VAL_ 391 RegenBrakingMode 0 "default" 1 "B1" 2 "B2" 3 "B3"; VAL_ 870 Fast_Send_Rate_Active 0 "1 Hz" 1 "50 Hz"; diff --git a/panda/board/can_definitions.h b/panda/board/can_definitions.h index 552cf3ff9..074f4e7a6 100644 --- a/panda/board/can_definitions.h +++ b/panda/board/can_definitions.h @@ -25,5 +25,5 @@ typedef struct { #define CANPACKET_HEAD_SIZE 5U -#define WORD_TO_BYTE_ARRAY(dst8, src32) 0[dst8] = ((src32) & 0xFF); 1[dst8] = (((src32) >> 8) & 0xFF); 2[dst8] = (((src32) >> 16) & 0xFF); 3[dst8] = (((src32) >> 24) & 0xFF) -#define BYTE_ARRAY_TO_WORD(dst32, src8) ((dst32) = 0[src8] | (1[src8] << 8) | (2[src8] << 16) | (3[src8] << 24)) +#define WORD_TO_BYTE_ARRAY(dst8, src32) 0[dst8] = ((src32) & 0xFFU); 1[dst8] = (((src32) >> 8U) & 0xFFU); 2[dst8] = (((src32) >> 16U) & 0xFFU); 3[dst8] = (((src32) >> 24U) & 0xFFU) +#define BYTE_ARRAY_TO_WORD(dst32, src8) ((dst32) = 0[src8] | (1[src8] << 8U) | (2[src8] << 16U) | (3[src8] << 24U)) diff --git a/panda/board/drivers/bxcan.h b/panda/board/drivers/bxcan.h index 1d1745cd3..4733491d0 100644 --- a/panda/board/drivers/bxcan.h +++ b/panda/board/drivers/bxcan.h @@ -112,7 +112,7 @@ void process_can(uint8_t can_number) { to_push.returned = 1U; to_push.rejected = 0U; to_push.extended = (CAN->sTxMailBox[0].TIR >> 2) & 0x1U; - to_push.addr = (to_push.extended != 0) ? (CAN->sTxMailBox[0].TIR >> 3) : (CAN->sTxMailBox[0].TIR >> 21); + to_push.addr = (to_push.extended != 0U) ? (CAN->sTxMailBox[0].TIR >> 3) : (CAN->sTxMailBox[0].TIR >> 21); to_push.data_len_code = CAN->sTxMailBox[0].TDTR & 0xFU; to_push.bus = bus_number; WORD_TO_BYTE_ARRAY(&to_push.data[0], CAN->sTxMailBox[0].TDLR); @@ -141,7 +141,7 @@ void process_can(uint8_t can_number) { if (can_pop(can_queues[bus_number], &to_send)) { can_tx_cnt += 1; // only send if we have received a packet - CAN->sTxMailBox[0].TIR = ((to_send.extended != 0) ? (to_send.addr << 3) : (to_send.addr << 21)) | (to_send.extended << 2); + CAN->sTxMailBox[0].TIR = ((to_send.extended != 0U) ? (to_send.addr << 3) : (to_send.addr << 21)) | (to_send.extended << 2); CAN->sTxMailBox[0].TDTR = to_send.data_len_code; BYTE_ARRAY_TO_WORD(CAN->sTxMailBox[0].TDLR, &to_send.data[0]); BYTE_ARRAY_TO_WORD(CAN->sTxMailBox[0].TDHR, &to_send.data[4]); @@ -173,7 +173,7 @@ void can_rx(uint8_t can_number) { to_push.returned = 0U; to_push.rejected = 0U; to_push.extended = (CAN->sFIFOMailBox[0].RIR >> 2) & 0x1U; - to_push.addr = (to_push.extended != 0) ? (CAN->sFIFOMailBox[0].RIR >> 3) : (CAN->sFIFOMailBox[0].RIR >> 21); + to_push.addr = (to_push.extended != 0U) ? (CAN->sFIFOMailBox[0].RIR >> 3) : (CAN->sFIFOMailBox[0].RIR >> 21); to_push.data_len_code = CAN->sFIFOMailBox[0].RDTR & 0xFU; to_push.bus = bus_number; WORD_TO_BYTE_ARRAY(&to_push.data[0], CAN->sFIFOMailBox[0].RDLR); diff --git a/panda/board/drivers/can_common.h b/panda/board/drivers/can_common.h index 9ee7415a1..5ec840568 100644 --- a/panda/board/drivers/can_common.h +++ b/panda/board/drivers/can_common.h @@ -206,18 +206,18 @@ void ignition_can_hook(CANPacket_t *to_push) { // since the 0x1F1 signal can briefly go low immediately after ignition if ((addr == 0x160) && (len == 5)) { // this message isn't all zeros when ignition is on - ignition_cadillac = GET_BYTES_04(to_push) != 0; + ignition_cadillac = GET_BYTES_04(to_push) != 0U; } if ((addr == 0x1F1) && (len == 8)) { // Bit 5 is ignition "on" - bool ignition_gm = ((GET_BYTE(to_push, 0) & 0x20) != 0); + bool ignition_gm = ((GET_BYTE(to_push, 0) & 0x20U) != 0U); ignition_can = ignition_gm || ignition_cadillac; } // Tesla exception if ((addr == 0x348) && (len == 8)) { // GTW_status - ignition_can = (GET_BYTE(to_push, 0) & 0x1) != 0; + ignition_can = (GET_BYTE(to_push, 0) & 0x1U) != 0U; } // Mazda exception diff --git a/panda/board/drivers/fdcan.h b/panda/board/drivers/fdcan.h index 601fc2d69..c5428d027 100644 --- a/panda/board/drivers/fdcan.h +++ b/panda/board/drivers/fdcan.h @@ -60,13 +60,13 @@ void process_can(uint8_t can_number) { canfd_fifo *fifo; fifo = (canfd_fifo *)(TxFIFOSA + (tx_index * FDCAN_TX_FIFO_EL_SIZE)); - fifo->header[0] = (to_send.extended << 30) | ((to_send.extended != 0) ? (to_send.addr) : (to_send.addr << 18)); + fifo->header[0] = (to_send.extended << 30) | ((to_send.extended != 0U) ? (to_send.addr) : (to_send.addr << 18)); fifo->header[1] = (to_send.data_len_code << 16) | (bus_config[can_number].canfd_enabled << 21) | (bus_config[can_number].brs_enabled << 20); uint8_t data_len_w = (dlc_to_len[to_send.data_len_code] / 4U); - data_len_w += ((dlc_to_len[to_send.data_len_code] % 4) > 0) ? 1U : 0U; + data_len_w += ((dlc_to_len[to_send.data_len_code] % 4U) > 0U) ? 1U : 0U; for (unsigned int i = 0; i < data_len_w; i++) { - BYTE_ARRAY_TO_WORD(fifo->data_word[i], &to_send.data[i*4]); + BYTE_ARRAY_TO_WORD(fifo->data_word[i], &to_send.data[i*4U]); } CANx->TXBAR = (1UL << tx_index); @@ -142,14 +142,14 @@ void can_rx(uint8_t can_number) { to_push.returned = 0U; to_push.rejected = 0U; to_push.extended = (fifo->header[0] >> 30) & 0x1U; - to_push.addr = ((to_push.extended != 0) ? (fifo->header[0] & 0x1FFFFFFFU) : ((fifo->header[0] >> 18) & 0x7FFU)); + to_push.addr = ((to_push.extended != 0U) ? (fifo->header[0] & 0x1FFFFFFFU) : ((fifo->header[0] >> 18) & 0x7FFU)); to_push.bus = bus_number; to_push.data_len_code = ((fifo->header[1] >> 16) & 0xFU); uint8_t data_len_w = (dlc_to_len[to_push.data_len_code] / 4U); - data_len_w += ((dlc_to_len[to_push.data_len_code] % 4) > 0) ? 1U : 0U; + data_len_w += ((dlc_to_len[to_push.data_len_code] % 4U) > 0U) ? 1U : 0U; for (unsigned int i = 0; i < data_len_w; i++) { - WORD_TO_BYTE_ARRAY(&to_push.data[i*4], fifo->data_word[i]); + WORD_TO_BYTE_ARRAY(&to_push.data[i*4U], fifo->data_word[i]); } // forwarding (panda only) diff --git a/panda/board/drivers/gmlan_alt.h b/panda/board/drivers/gmlan_alt.h index 9b95e0dd8..2e5225691 100644 --- a/panda/board/drivers/gmlan_alt.h +++ b/panda/board/drivers/gmlan_alt.h @@ -91,7 +91,7 @@ int get_bit_message(char *out, CANPacket_t *to_bang) { int dlc_len = GET_LEN(to_bang); len = append_int(pkt, len, 0, 1); // Start-of-frame - if (to_bang->extended != 0) { + if (to_bang->extended != 0U) { // extended identifier len = append_int(pkt, len, GET_ADDR(to_bang) >> 18, 11); // Identifier len = append_int(pkt, len, 3, 2); // SRR+IDE diff --git a/panda/board/early_init.h b/panda/board/early_init.h index 0841fc876..ae9a90362 100644 --- a/panda/board/early_init.h +++ b/panda/board/early_init.h @@ -38,9 +38,9 @@ void early_initialization(void) { // if wrong chip, reboot volatile unsigned int id = DBGMCU->IDCODE; - if ((id & 0xFFFU) != MCU_IDCODE) { - enter_bootloader_mode = ENTER_BOOTLOADER_MAGIC; - } + if ((id & 0xFFFU) != MCU_IDCODE) { + enter_bootloader_mode = ENTER_BOOTLOADER_MAGIC; + } // setup interrupt table SCB->VTOR = (uint32_t)&g_pfnVectors; diff --git a/panda/board/pedal/README b/panda/board/pedal/README index cf779db25..9e004d6bd 100644 --- a/panda/board/pedal/README +++ b/panda/board/pedal/README @@ -1,8 +1,8 @@ -This is the firmware for the comma pedal. It borrows a lot from panda. +# pedal -The comma pedal is a gas pedal interceptor for Honda/Acura. It allows you to "virtually" press the pedal. +This is the firmware for the comma pedal. -This is the open source software. Note that it is not ready to use yet. +The comma pedal is a gas pedal interceptor for Honda/Acura and Toyota/Lexus. It allows you to "virtually" press the pedal and borrows a lot from panda. == Test Plan == diff --git a/panda/board/safety.h b/panda/board/safety.h index a833dc1b1..1f1bcba72 100644 --- a/panda/board/safety.h +++ b/panda/board/safety.h @@ -33,7 +33,7 @@ #define SAFETY_ALLOUTPUT 17U #define SAFETY_GM_ASCM 18U #define SAFETY_NOOUTPUT 19U -#define SAFETY_HONDA_BOSCH_HARNESS 20U +#define SAFETY_HONDA_BOSCH 20U #define SAFETY_VOLKSWAGEN_PQ 21U #define SAFETY_SUBARU_LEGACY 22U #define SAFETY_HYUNDAI_LEGACY 23U @@ -241,7 +241,7 @@ const safety_hook_config safety_hook_registry[] = { {SAFETY_TOYOTA, &toyota_hooks}, {SAFETY_ELM327, &elm327_hooks}, {SAFETY_GM, &gm_hooks}, - {SAFETY_HONDA_BOSCH_HARNESS, &honda_bosch_harness_hooks}, + {SAFETY_HONDA_BOSCH, &honda_bosch_hooks}, {SAFETY_HYUNDAI, &hyundai_hooks}, {SAFETY_CHRYSLER, &chrysler_hooks}, {SAFETY_SUBARU, &subaru_hooks}, diff --git a/panda/board/safety/safety_chrysler.h b/panda/board/safety/safety_chrysler.h index 35474f346..f94f75e7b 100644 --- a/panda/board/safety/safety_chrysler.h +++ b/panda/board/safety/safety_chrysler.h @@ -19,7 +19,7 @@ AddrCheckStruct chrysler_addr_checks[] = { addr_checks chrysler_rx_checks = {chrysler_addr_checks, CHRYSLER_ADDR_CHECK_LEN}; static uint8_t chrysler_get_checksum(CANPacket_t *to_push) { - int checksum_byte = GET_LEN(to_push) - 1; + int checksum_byte = GET_LEN(to_push) - 1U; return (uint8_t)(GET_BYTE(to_push, checksum_byte)); } @@ -67,7 +67,7 @@ static int chrysler_rx_hook(CANPacket_t *to_push) { chrysler_get_checksum, chrysler_compute_checksum, chrysler_get_counter); - if (valid && (GET_BUS(to_push) == 0)) { + if (valid && (GET_BUS(to_push) == 0U)) { int addr = GET_ADDR(to_push); // Measured eps torque @@ -80,7 +80,7 @@ static int chrysler_rx_hook(CANPacket_t *to_push) { // enter controls on rising edge of ACC, exit controls on ACC off if (addr == 500) { - int cruise_engaged = ((GET_BYTE(to_push, 2) & 0x38) >> 3) == 7; + int cruise_engaged = ((GET_BYTE(to_push, 2) & 0x38U) >> 3) == 7U; if (cruise_engaged && !cruise_engaged_prev) { controls_allowed = 1; } @@ -100,12 +100,12 @@ static int chrysler_rx_hook(CANPacket_t *to_push) { // exit controls on rising edge of gas press if (addr == 308) { - gas_pressed = ((GET_BYTE(to_push, 5) & 0x7F) != 0) && ((int)vehicle_speed > CHRYSLER_GAS_THRSLD); + gas_pressed = ((GET_BYTE(to_push, 5) & 0x7FU) != 0U) && ((int)vehicle_speed > CHRYSLER_GAS_THRSLD); } // exit controls on rising edge of brake press if (addr == 320) { - brake_pressed = (GET_BYTE(to_push, 0) & 0x7) == 5; + brake_pressed = (GET_BYTE(to_push, 0) & 0x7U) == 5U; if (brake_pressed && (!brake_pressed_prev || vehicle_moving)) { controls_allowed = 0; } @@ -174,7 +174,7 @@ static int chrysler_tx_hook(CANPacket_t *to_send) { // FORCE CANCEL: only the cancel button press is allowed if (addr == 571) { - if ((GET_BYTE(to_send, 0) != 1) || ((GET_BYTE(to_send, 1) & 1) == 1)) { + if ((GET_BYTE(to_send, 0) != 1U) || ((GET_BYTE(to_send, 1) & 1U) == 1U)) { tx = 0; } } diff --git a/panda/board/safety/safety_ford.h b/panda/board/safety/safety_ford.h index 6a8f05838..ccdc54e6e 100644 --- a/panda/board/safety/safety_ford.h +++ b/panda/board/safety/safety_ford.h @@ -24,8 +24,8 @@ static int ford_rx_hook(CANPacket_t *to_push) { // state machine to enter and exit controls if (addr == 0x83) { - bool cancel = GET_BYTE(to_push, 1) & 0x1; - bool set_or_resume = GET_BYTE(to_push, 3) & 0x30; + bool cancel = GET_BYTE(to_push, 1) & 0x1U; + bool set_or_resume = GET_BYTE(to_push, 3) & 0x30U; if (cancel) { controls_allowed = 0; } @@ -37,7 +37,7 @@ static int ford_rx_hook(CANPacket_t *to_push) { // exit controls on rising edge of brake press or on brake press when // speed > 0 if (addr == 0x165) { - brake_pressed = GET_BYTE(to_push, 0) & 0x20; + brake_pressed = GET_BYTE(to_push, 0) & 0x20U; if (brake_pressed && (!brake_pressed_prev || vehicle_moving)) { controls_allowed = 0; } @@ -46,7 +46,7 @@ static int ford_rx_hook(CANPacket_t *to_push) { // exit controls on rising edge of gas press if (addr == 0x204) { - gas_pressed = ((GET_BYTE(to_push, 0) & 0x03) | GET_BYTE(to_push, 1)) != 0; + gas_pressed = ((GET_BYTE(to_push, 0) & 0x03U) | GET_BYTE(to_push, 1)) != 0U; if (!unsafe_allow_gas && gas_pressed && !gas_pressed_prev) { controls_allowed = 0; } @@ -83,7 +83,7 @@ static int ford_tx_hook(CANPacket_t *to_send) { if (addr == 0x3CA) { if (!current_controls_allowed) { // bits 7-4 need to be 0xF to disallow lkas commands - if ((GET_BYTE(to_send, 0) & 0xF0) != 0xF0) { + if ((GET_BYTE(to_send, 0) & 0xF0U) != 0xF0U) { tx = 0; } } @@ -92,7 +92,7 @@ static int ford_tx_hook(CANPacket_t *to_send) { // FORCE CANCEL: safety check only relevant when spamming the cancel button // ensuring that set and resume aren't sent if (addr == 0x83) { - if ((GET_BYTE(to_send, 3) & 0x30) != 0) { + if ((GET_BYTE(to_send, 3) & 0x30U) != 0U) { tx = 0; } } diff --git a/panda/board/safety/safety_gm.h b/panda/board/safety/safety_gm.h index 6ef30f279..9a9fef611 100644 --- a/panda/board/safety/safety_gm.h +++ b/panda/board/safety/safety_gm.h @@ -38,11 +38,11 @@ static int gm_rx_hook(CANPacket_t *to_push) { bool valid = addr_safety_check(to_push, &gm_rx_checks, NULL, NULL, NULL); - if (valid && (GET_BUS(to_push) == 0)) { + if (valid && (GET_BUS(to_push) == 0U)) { int addr = GET_ADDR(to_push); if (addr == 388) { - int torque_driver_new = ((GET_BYTE(to_push, 6) & 0x7) << 8) | GET_BYTE(to_push, 7); + int torque_driver_new = ((GET_BYTE(to_push, 6) & 0x7U) << 8) | GET_BYTE(to_push, 7); torque_driver_new = to_signed(torque_driver_new, 11); // update array of samples update_sample(&torque_driver, torque_driver_new); @@ -56,7 +56,7 @@ static int gm_rx_hook(CANPacket_t *to_push) { // ACC steering wheel buttons if (addr == 481) { - int button = (GET_BYTE(to_push, 5) & 0x70) >> 4; + int button = (GET_BYTE(to_push, 5) & 0x70U) >> 4; switch (button) { case 2: // resume case 3: // set @@ -74,16 +74,16 @@ static int gm_rx_hook(CANPacket_t *to_push) { if (addr == 241) { // Brake pedal's potentiometer returns near-zero reading // even when pedal is not pressed - brake_pressed = GET_BYTE(to_push, 1) >= 10; + brake_pressed = GET_BYTE(to_push, 1) >= 10U; } if (addr == 417) { - gas_pressed = GET_BYTE(to_push, 6) != 0; + gas_pressed = GET_BYTE(to_push, 6) != 0U; } // exit controls on regen paddle if (addr == 189) { - bool regen = GET_BYTE(to_push, 0) & 0x20; + bool regen = GET_BYTE(to_push, 0) & 0x20U; if (regen) { controls_allowed = 0; } diff --git a/panda/board/safety/safety_honda.h b/panda/board/safety/safety_honda.h index 961a999b5..ed19fb8a6 100644 --- a/panda/board/safety/safety_honda.h +++ b/panda/board/safety/safety_honda.h @@ -7,8 +7,8 @@ // brake rising edge // brake > 0mph const CanMsg HONDA_N_TX_MSGS[] = {{0xE4, 0, 5}, {0x194, 0, 4}, {0x1FA, 0, 8}, {0x200, 0, 6}, {0x30C, 0, 8}, {0x33D, 0, 5}}; -const CanMsg HONDA_BH_TX_MSGS[] = {{0xE4, 0, 5}, {0xE5, 0, 8}, {0x296, 1, 4}, {0x33D, 0, 5}}; // Bosch Harness -const CanMsg HONDA_BH_LONG_TX_MSGS[] = {{0xE4, 1, 5}, {0x1DF, 1, 8}, {0x1EF, 1, 8}, {0x1FA, 1, 8}, {0x30C, 1, 8}, {0x33D, 1, 5}, {0x39F, 1, 8}, {0x18DAB0F1, 1, 8}}; // Bosch Harness w/ gas and brakes +const CanMsg HONDA_BOSCH_TX_MSGS[] = {{0xE4, 0, 5}, {0xE5, 0, 8}, {0x296, 1, 4}, {0x33D, 0, 5}, {0x33DA, 0, 5}, {0x33DB, 0, 8}}; // Bosch +const CanMsg HONDA_BOSCH_LONG_TX_MSGS[] = {{0xE4, 1, 5}, {0x1DF, 1, 8}, {0x1EF, 1, 8}, {0x1FA, 1, 8}, {0x30C, 1, 8}, {0x33D, 1, 5}, {0x33DA, 1, 5}, {0x33DB, 1, 8}, {0x39F, 1, 8}, {0x18DAB0F1, 1, 8}}; // Bosch w/ gas and brakes // Roughly calculated using the offsets in openpilot +5%: // In openpilot: ((gas1_norm + gas2_norm)/2) > 15 @@ -17,7 +17,7 @@ const CanMsg HONDA_BH_LONG_TX_MSGS[] = {{0xE4, 1, 5}, {0x1DF, 1, 8}, {0x1EF, 1, // assuming that 2*(gain_dbc1*gas1) == (gain_dbc2*gas2) // In this safety: ((gas1 + (gas2/2))/2) > THRESHOLD const int HONDA_GAS_INTERCEPTOR_THRESHOLD = 344; -#define HONDA_GET_INTERCEPTOR(msg) (((GET_BYTE((msg), 0) << 8) + GET_BYTE((msg), 1) + ((GET_BYTE((msg), 2) << 8) + GET_BYTE((msg), 3)) / 2 ) / 2) // avg between 2 tracks +#define HONDA_GET_INTERCEPTOR(msg) (((GET_BYTE((msg), 0) << 8) + GET_BYTE((msg), 1) + ((GET_BYTE((msg), 2) << 8) + GET_BYTE((msg), 3)) / 2U ) / 2U) // avg between 2 tracks const int HONDA_BOSCH_NO_GAS_VALUE = -30000; // value sent when not requesting gas const int HONDA_BOSCH_GAS_MAX = 2000; const int HONDA_BOSCH_ACCEL_MIN = -350; // max braking == -3.5m/s2 @@ -28,28 +28,28 @@ AddrCheckStruct honda_nidec_addr_checks[] = { {0x296, 0, 4, .check_checksum = true, .max_counter = 3U, .expected_timestep = 40000U}, { 0 }}}, {.msg = {{0x158, 0, 8, .check_checksum = true, .max_counter = 3U, .expected_timestep = 10000U}, { 0 }, { 0 }}}, {.msg = {{0x17C, 0, 8, .check_checksum = true, .max_counter = 3U, .expected_timestep = 10000U}, { 0 }, { 0 }}}, + {.msg = {{0x326, 0, 8, .check_checksum = true, .max_counter = 3U, .expected_timestep = 100000U}, { 0 }, { 0 }}}, }; #define HONDA_NIDEC_ADDR_CHECKS_LEN (sizeof(honda_nidec_addr_checks) / sizeof(honda_nidec_addr_checks[0])) -// For Nidecs with main on signal on 0x326 +// For Nidecs with main on signal on an alternate msg AddrCheckStruct honda_nidec_alt_addr_checks[] = { {.msg = {{0x1A6, 0, 8, .check_checksum = true, .max_counter = 3U, .expected_timestep = 40000U}, {0x296, 0, 4, .check_checksum = true, .max_counter = 3U, .expected_timestep = 40000U}, { 0 }}}, {.msg = {{0x158, 0, 8, .check_checksum = true, .max_counter = 3U, .expected_timestep = 10000U}, { 0 }, { 0 }}}, {.msg = {{0x17C, 0, 8, .check_checksum = true, .max_counter = 3U, .expected_timestep = 10000U}, { 0 }, { 0 }}}, - {.msg = {{0x326, 0, 8, .check_checksum = true, .max_counter = 3U, .expected_timestep = 100000U}, { 0 }, { 0 }}}, }; #define HONDA_NIDEC_ALT_ADDR_CHECKS_LEN (sizeof(honda_nidec_alt_addr_checks) / sizeof(honda_nidec_alt_addr_checks[0])) -// Bosch harness has pt on bus 1 -AddrCheckStruct honda_bh_addr_checks[] = { +// Bosch has pt on bus 1 +AddrCheckStruct honda_bosch_addr_checks[] = { {.msg = {{0x296, 1, 4, .check_checksum = true, .max_counter = 3U, .expected_timestep = 40000U}, { 0 }, { 0 }}}, {.msg = {{0x158, 1, 8, .check_checksum = true, .max_counter = 3U, .expected_timestep = 10000U}, { 0 }, { 0 }}}, {.msg = {{0x17C, 1, 8, .check_checksum = true, .max_counter = 3U, .expected_timestep = 10000U}, {0x1BE, 1, 3, .check_checksum = true, .max_counter = 3U, .expected_timestep = 20000U}, { 0 }}}, {.msg = {{0x326, 1, 8, .check_checksum = true, .max_counter = 3U, .expected_timestep = 100000U}, { 0 }, { 0 }}}, }; -#define HONDA_BH_ADDR_CHECKS_LEN (sizeof(honda_bh_addr_checks) / sizeof(honda_bh_addr_checks[0])) +#define HONDA_BOSCH_ADDR_CHECKS_LEN (sizeof(honda_bosch_addr_checks) / sizeof(honda_bosch_addr_checks[0])) const uint16_t HONDA_PARAM_ALT_BRAKE = 1; const uint16_t HONDA_PARAM_BOSCH_LONG = 2; @@ -59,12 +59,12 @@ int honda_brake = 0; bool honda_alt_brake_msg = false; bool honda_fwd_brake = false; bool honda_bosch_long = false; -enum {HONDA_N_HW, HONDA_BH_HW} honda_hw = HONDA_N_HW; +enum {HONDA_NIDEC, HONDA_BOSCH} honda_hw = HONDA_NIDEC; addr_checks honda_rx_checks = {honda_nidec_addr_checks, HONDA_NIDEC_ADDR_CHECKS_LEN}; static uint8_t honda_get_checksum(CANPacket_t *to_push) { - int checksum_byte = GET_LEN(to_push) - 1; + int checksum_byte = GET_LEN(to_push) - 1U; return (uint8_t)(GET_BYTE(to_push, checksum_byte)) & 0xFU; } @@ -86,7 +86,7 @@ static uint8_t honda_compute_checksum(CANPacket_t *to_push) { } static uint8_t honda_get_counter(CANPacket_t *to_push) { - int counter_byte = GET_LEN(to_push) - 1; + int counter_byte = GET_LEN(to_push) - 1U; return ((uint8_t)(GET_BYTE(to_push, counter_byte)) >> 4U) & 0x3U; } @@ -109,7 +109,7 @@ static int honda_rx_hook(CANPacket_t *to_push) { // check ACC main state // 0x326 for all Bosch and some Nidec, 0x1A6 for some Nidec if ((addr == 0x326) || (addr == 0x1A6)) { - acc_main_on = GET_BIT(to_push, (addr == 0x326) ? 28 : 47); + acc_main_on = GET_BIT(to_push, ((addr == 0x326) ? 28U : 47U)); if (!acc_main_on) { controls_allowed = 0; } @@ -119,7 +119,7 @@ static int honda_rx_hook(CANPacket_t *to_push) { // 0x1A6 for the ILX, 0x296 for the Civic Touring if ((addr == 0x1A6) || (addr == 0x296)) { // check for button presses - int button = (GET_BYTE(to_push, 0) & 0xE0) >> 5; + int button = (GET_BYTE(to_push, 0) & 0xE0U) >> 5; switch (button) { case 1: // main case 2: // cancel @@ -144,7 +144,7 @@ static int honda_rx_hook(CANPacket_t *to_push) { // accord, crv: 0x1BE bit 4 bool is_user_brake_msg = honda_alt_brake_msg ? ((addr) == 0x1BE) : ((addr) == 0x17C); if (is_user_brake_msg) { - brake_pressed = honda_alt_brake_msg ? (GET_BYTE((to_push), 0) & 0x10) : (GET_BYTE((to_push), 6) & 0x20); + brake_pressed = honda_alt_brake_msg ? (GET_BYTE((to_push), 0) & 0x10U) : (GET_BYTE((to_push), 6) & 0x20U); } // length check because bosch hardware also uses this id (0x201 w/ len = 8) @@ -157,15 +157,15 @@ static int honda_rx_hook(CANPacket_t *to_push) { if (!gas_interceptor_detected) { if (addr == 0x17C) { - gas_pressed = GET_BYTE(to_push, 0) != 0; + gas_pressed = GET_BYTE(to_push, 0) != 0U; } } // disable stock Honda AEB in unsafe mode if ( !(unsafe_mode & UNSAFE_DISABLE_STOCK_AEB) ) { if ((bus == 2) && (addr == 0x1FA)) { - bool honda_stock_aeb = GET_BYTE(to_push, 3) & 0x20; - int honda_stock_brake = (GET_BYTE(to_push, 0) << 2) + ((GET_BYTE(to_push, 1) >> 6) & 0x3); + bool honda_stock_aeb = GET_BYTE(to_push, 3) & 0x20U; + int honda_stock_brake = (GET_BYTE(to_push, 0) << 2) + ((GET_BYTE(to_push, 1) >> 6) & 0x3U); // Forward AEB when stock braking is higher than openpilot braking // only stop forwarding when AEB event is over @@ -180,14 +180,14 @@ static int honda_rx_hook(CANPacket_t *to_push) { } bool stock_ecu_detected = false; - int bus_rdr_car = (honda_hw == HONDA_BH_HW) ? 0 : 2; // radar bus, car side - int pt_bus = (honda_hw == HONDA_BH_HW) ? 1 : 0; + int bus_rdr_car = (honda_hw == HONDA_BOSCH) ? 0 : 2; // radar bus, car side + int pt_bus = (honda_hw == HONDA_BOSCH) ? 1 : 0; if (safety_mode_cnt > RELAY_TRNS_TIMEOUT) { // If steering controls messages are received on the destination bus, it's an indication // that the relay might be malfunctioning if ((addr == 0xE4) || (addr == 0x194)) { - if (((honda_hw != HONDA_N_HW) && (bus == bus_rdr_car)) || ((honda_hw == HONDA_N_HW) && (bus == 0))) { + if (((honda_hw != HONDA_NIDEC) && (bus == bus_rdr_car)) || ((honda_hw == HONDA_NIDEC) && (bus == 0))) { stock_ecu_detected = true; } } @@ -215,10 +215,10 @@ static int honda_tx_hook(CANPacket_t *to_send) { int addr = GET_ADDR(to_send); int bus = GET_BUS(to_send); - if ((honda_hw == HONDA_BH_HW) && !honda_bosch_long) { - tx = msg_allowed(to_send, HONDA_BH_TX_MSGS, sizeof(HONDA_BH_TX_MSGS)/sizeof(HONDA_BH_TX_MSGS[0])); - } else if ((honda_hw == HONDA_BH_HW) && honda_bosch_long) { - tx = msg_allowed(to_send, HONDA_BH_LONG_TX_MSGS, sizeof(HONDA_BH_LONG_TX_MSGS)/sizeof(HONDA_BH_LONG_TX_MSGS[0])); + if ((honda_hw == HONDA_BOSCH) && !honda_bosch_long) { + tx = msg_allowed(to_send, HONDA_BOSCH_TX_MSGS, sizeof(HONDA_BOSCH_TX_MSGS)/sizeof(HONDA_BOSCH_TX_MSGS[0])); + } else if ((honda_hw == HONDA_BOSCH) && honda_bosch_long) { + tx = msg_allowed(to_send, HONDA_BOSCH_LONG_TX_MSGS, sizeof(HONDA_BOSCH_LONG_TX_MSGS)/sizeof(HONDA_BOSCH_LONG_TX_MSGS[0])); } else { tx = msg_allowed(to_send, HONDA_N_TX_MSGS, sizeof(HONDA_N_TX_MSGS)/sizeof(HONDA_N_TX_MSGS[0])); } @@ -231,11 +231,11 @@ static int honda_tx_hook(CANPacket_t *to_send) { pedal_pressed = pedal_pressed || gas_pressed_prev || (gas_interceptor_prev > HONDA_GAS_INTERCEPTOR_THRESHOLD); } bool current_controls_allowed = controls_allowed && !(pedal_pressed); - int bus_pt = (honda_hw == HONDA_BH_HW)? 1 : 0; + int bus_pt = (honda_hw == HONDA_BOSCH) ? 1 : 0; // BRAKE: safety check (nidec) if ((addr == 0x1FA) && (bus == bus_pt)) { - honda_brake = (GET_BYTE(to_send, 0) << 2) + ((GET_BYTE(to_send, 1) >> 6) & 0x3); + honda_brake = (GET_BYTE(to_send, 0) << 2) + ((GET_BYTE(to_send, 1) >> 6) & 0x3U); if (!current_controls_allowed) { if (honda_brake != 0) { tx = 0; @@ -251,7 +251,7 @@ static int honda_tx_hook(CANPacket_t *to_send) { // BRAKE/GAS: safety check (bosch) if ((addr == 0x1DF) && (bus == bus_pt)) { - int accel = (GET_BYTE(to_send, 3) << 3) | ((GET_BYTE(to_send, 4) >> 5) & 0x7); + int accel = (GET_BYTE(to_send, 3) << 3) | ((GET_BYTE(to_send, 4) >> 5) & 0x7U); accel = to_signed(accel, 11); if (!current_controls_allowed) { if (accel != 0) { @@ -284,9 +284,9 @@ static int honda_tx_hook(CANPacket_t *to_send) { } } - // Bosch supplemental control check + // Bosch supplemental control check if (addr == 0xE5) { - if ((GET_BYTES_04(to_send) != 0x10800004) || ((GET_BYTES_48(to_send) & 0x00FFFFFF) != 0x0)) { + if ((GET_BYTES_04(to_send) != 0x10800004U) || ((GET_BYTES_48(to_send) & 0x00FFFFFFU) != 0x0U)) { tx = 0; } } @@ -304,14 +304,14 @@ static int honda_tx_hook(CANPacket_t *to_send) { // ensuring that only the cancel button press is sent (VAL 2) when controls are off. // This avoids unintended engagements while still allowing resume spam if ((addr == 0x296) && !current_controls_allowed && (bus == bus_pt)) { - if (((GET_BYTE(to_send, 0) >> 5) & 0x7) != 2) { + if (((GET_BYTE(to_send, 0) >> 5) & 0x7U) != 2U) { tx = 0; } } // Only tester present ("\x02\x3E\x80\x00\x00\x00\x00\x00") allowed on diagnostics address if (addr == 0x18DAB0F1) { - if ((GET_BYTES_04(to_send) != 0x00803E02) || (GET_BYTES_48(to_send) != 0x0)) { + if ((GET_BYTES_04(to_send) != 0x00803E02U) || (GET_BYTES_48(to_send) != 0x0U)) { tx = 0; } } @@ -325,26 +325,22 @@ static const addr_checks* honda_nidec_init(int16_t param) { controls_allowed = false; relay_malfunction_reset(); gas_interceptor_detected = 0; - honda_hw = HONDA_N_HW; + honda_hw = HONDA_NIDEC; honda_alt_brake_msg = false; honda_bosch_long = false; - honda_rx_checks = (addr_checks){honda_nidec_addr_checks, HONDA_NIDEC_ADDR_CHECKS_LEN}; - - /* if (GET_FLAG(param, HONDA_PARAM_NIDEC_ALT)) { honda_rx_checks = (addr_checks){honda_nidec_alt_addr_checks, HONDA_NIDEC_ALT_ADDR_CHECKS_LEN}; } else { honda_rx_checks = (addr_checks){honda_nidec_addr_checks, HONDA_NIDEC_ADDR_CHECKS_LEN}; } - */ return &honda_rx_checks; } -static const addr_checks* honda_bosch_harness_init(int16_t param) { +static const addr_checks* honda_bosch_init(int16_t param) { controls_allowed = false; relay_malfunction_reset(); - honda_hw = HONDA_BH_HW; + honda_hw = HONDA_BOSCH; // Checking for alternate brake override from safety parameter honda_alt_brake_msg = GET_FLAG(param, HONDA_PARAM_ALT_BRAKE); @@ -353,7 +349,7 @@ static const addr_checks* honda_bosch_harness_init(int16_t param) { honda_bosch_long = GET_FLAG(param, HONDA_PARAM_BOSCH_LONG); #endif - honda_rx_checks = (addr_checks){honda_bh_addr_checks, HONDA_BH_ADDR_CHECKS_LEN}; + honda_rx_checks = (addr_checks){honda_bosch_addr_checks, HONDA_BOSCH_ADDR_CHECKS_LEN}; return &honda_rx_checks; } @@ -384,17 +380,15 @@ static int honda_nidec_fwd_hook(int bus_num, CANPacket_t *to_fwd) { static int honda_bosch_fwd_hook(int bus_num, CANPacket_t *to_fwd) { int bus_fwd = -1; - int bus_rdr_cam = (honda_hw == HONDA_BH_HW) ? 2 : 1; // radar bus, camera side - int bus_rdr_car = (honda_hw == HONDA_BH_HW) ? 0 : 2; // radar bus, car side - if (bus_num == bus_rdr_car) { - bus_fwd = bus_rdr_cam; + if (bus_num == 0) { + bus_fwd = 2; } - if (bus_num == bus_rdr_cam) { + if (bus_num == 2) { int addr = GET_ADDR(to_fwd); - int is_lkas_msg = (addr == 0xE4) || (addr == 0xE5) || (addr == 0x33D); + int is_lkas_msg = (addr == 0xE4) || (addr == 0xE5) || (addr == 0x33D) || (addr == 0x33DA) || (addr == 0x33DB); if (!is_lkas_msg) { - bus_fwd = bus_rdr_car; + bus_fwd = 0; } } @@ -409,8 +403,8 @@ const safety_hooks honda_nidec_hooks = { .fwd = honda_nidec_fwd_hook, }; -const safety_hooks honda_bosch_harness_hooks = { - .init = honda_bosch_harness_init, +const safety_hooks honda_bosch_hooks = { + .init = honda_bosch_init, .rx = honda_rx_hook, .tx = honda_tx_hook, .tx_lin = nooutput_tx_lin_hook, diff --git a/panda/board/safety/safety_hyundai.h b/panda/board/safety/safety_hyundai.h index 4e4f0e65a..883b8c6b2 100644 --- a/panda/board/safety/safety_hyundai.h +++ b/panda/board/safety/safety_hyundai.h @@ -74,15 +74,15 @@ static uint8_t hyundai_get_counter(CANPacket_t *to_push) { uint8_t cnt; if (addr == 608) { - cnt = (GET_BYTE(to_push, 7) >> 4) & 0x3; + cnt = (GET_BYTE(to_push, 7) >> 4) & 0x3U; } else if (addr == 902) { cnt = ((GET_BYTE(to_push, 3) >> 6) << 2) | (GET_BYTE(to_push, 1) >> 6); } else if (addr == 916) { - cnt = (GET_BYTE(to_push, 1) >> 5) & 0x7; + cnt = (GET_BYTE(to_push, 1) >> 5) & 0x7U; } else if (addr == 1057) { - cnt = GET_BYTE(to_push, 7) & 0xF; + cnt = GET_BYTE(to_push, 7) & 0xFU; } else if (addr == 1265) { - cnt = (GET_BYTE(to_push, 3) >> 4) & 0xF; + cnt = (GET_BYTE(to_push, 3) >> 4) & 0xFU; } else { cnt = 0; } @@ -94,11 +94,11 @@ static uint8_t hyundai_get_checksum(CANPacket_t *to_push) { uint8_t chksum; if (addr == 608) { - chksum = GET_BYTE(to_push, 7) & 0xF; + chksum = GET_BYTE(to_push, 7) & 0xFU; } else if (addr == 902) { chksum = ((GET_BYTE(to_push, 7) >> 6) << 2) | (GET_BYTE(to_push, 5) >> 6); } else if (addr == 916) { - chksum = GET_BYTE(to_push, 6) & 0xF; + chksum = GET_BYTE(to_push, 6) & 0xFU; } else if (addr == 1057) { chksum = GET_BYTE(to_push, 7) >> 4; } else { @@ -149,11 +149,11 @@ static int hyundai_rx_hook(CANPacket_t *to_push) { hyundai_get_checksum, hyundai_compute_checksum, hyundai_get_counter); - if (valid && (GET_BUS(to_push) == 0)) { + if (valid && (GET_BUS(to_push) == 0U)) { int addr = GET_ADDR(to_push); if (addr == 593) { - int torque_driver_new = ((GET_BYTES_04(to_push) & 0x7ff) * 0.79) - 808; // scale down new driver torque signal to match previous one + int torque_driver_new = ((GET_BYTES_04(to_push) & 0x7ffU) * 0.79) - 808; // scale down new driver torque signal to match previous one // update array of samples update_sample(&torque_driver, torque_driver_new); } @@ -161,7 +161,7 @@ static int hyundai_rx_hook(CANPacket_t *to_push) { if (hyundai_longitudinal) { // ACC steering wheel buttons if (addr == 1265) { - int button = GET_BYTE(to_push, 0) & 0x7; + int button = GET_BYTE(to_push, 0) & 0x7U; switch (button) { case 1: // resume case 2: // set @@ -178,7 +178,7 @@ static int hyundai_rx_hook(CANPacket_t *to_push) { // enter controls on rising edge of ACC, exit controls on ACC off if (addr == 1057) { // 2 bits: 13-14 - int cruise_engaged = (GET_BYTES_04(to_push) >> 13) & 0x3; + int cruise_engaged = (GET_BYTES_04(to_push) >> 13) & 0x3U; if (cruise_engaged && !cruise_engaged_prev) { controls_allowed = 1; } @@ -191,24 +191,23 @@ static int hyundai_rx_hook(CANPacket_t *to_push) { // read gas pressed signal if ((addr == 881) && hyundai_ev_gas_signal) { - gas_pressed = (((GET_BYTE(to_push, 4) & 0x7F) << 1) | GET_BYTE(to_push, 3) >> 7) != 0; + gas_pressed = (((GET_BYTE(to_push, 4) & 0x7FU) << 1) | GET_BYTE(to_push, 3) >> 7) != 0U; } else if ((addr == 881) && hyundai_hybrid_gas_signal) { - gas_pressed = GET_BYTE(to_push, 7) != 0; + gas_pressed = GET_BYTE(to_push, 7) != 0U; } else if (addr == 608) { // ICE - gas_pressed = (GET_BYTE(to_push, 7) >> 6) != 0; + gas_pressed = (GET_BYTE(to_push, 7) >> 6) != 0U; } else { } // sample wheel speed, averaging opposite corners if (addr == 902) { - int hyundai_speed = GET_BYTES_04(to_push) & 0x3FFF; // FL - hyundai_speed += (GET_BYTES_48(to_push) >> 16) & 0x3FFF; // RL + int hyundai_speed = (GET_BYTES_04(to_push) & 0x3FFFU) + ((GET_BYTES_48(to_push) >> 16) & 0x3FFFU); // FL + RR hyundai_speed /= 2; vehicle_moving = hyundai_speed > HYUNDAI_STANDSTILL_THRSLD; } if (addr == 916) { - brake_pressed = (GET_BYTE(to_push, 6) >> 7) != 0; + brake_pressed = (GET_BYTE(to_push, 6) >> 7) != 0U; } bool stock_ecu_detected = (addr == 832); @@ -237,8 +236,8 @@ static int hyundai_tx_hook(CANPacket_t *to_send) { // FCA11: Block any potential actuation if (addr == 909) { int CR_VSM_DecCmd = GET_BYTE(to_send, 1); - int FCA_CmdAct = (GET_BYTE(to_send, 2) >> 5) & 1; - int CF_VSM_DecCmdAct = (GET_BYTE(to_send, 3) >> 7) & 1; + int FCA_CmdAct = (GET_BYTE(to_send, 2) >> 5) & 1U; + int CF_VSM_DecCmdAct = (GET_BYTE(to_send, 3) >> 7) & 1U; if ((CR_VSM_DecCmd != 0) || (FCA_CmdAct != 0) || (CF_VSM_DecCmdAct != 0)) { tx = 0; @@ -247,11 +246,11 @@ static int hyundai_tx_hook(CANPacket_t *to_send) { // ACCEL: safety check if (addr == 1057) { - int desired_accel_raw = (((GET_BYTE(to_send, 4) & 0x7) << 8) | GET_BYTE(to_send, 3)) - 1023; - int desired_accel_val = ((GET_BYTE(to_send, 5) << 3) | (GET_BYTE(to_send, 4) >> 5)) - 1023; + int desired_accel_raw = (((GET_BYTE(to_send, 4) & 0x7U) << 8) | GET_BYTE(to_send, 3)) - 1023U; + int desired_accel_val = ((GET_BYTE(to_send, 5) << 3) | (GET_BYTE(to_send, 4) >> 5)) - 1023U; int aeb_decel_cmd = GET_BYTE(to_send, 2); - int aeb_req = (GET_BYTE(to_send, 6) >> 6) & 1; + int aeb_req = (GET_BYTE(to_send, 6) >> 6) & 1U; bool violation = 0; @@ -273,7 +272,7 @@ static int hyundai_tx_hook(CANPacket_t *to_send) { // LKA STEER: safety check if (addr == 832) { - int desired_torque = ((GET_BYTES_04(to_send) >> 16) & 0x7ff) - 1024; + int desired_torque = ((GET_BYTES_04(to_send) >> 16) & 0x7ffU) - 1024U; uint32_t ts = microsecond_timer_get(); bool violation = 0; @@ -320,7 +319,7 @@ static int hyundai_tx_hook(CANPacket_t *to_send) { // UDS: Only tester present ("\x02\x3E\x80\x00\x00\x00\x00\x00") allowed on diagnostics address if (addr == 2000) { - if ((GET_BYTES_04(to_send) != 0x00803E02) || (GET_BYTES_48(to_send) != 0x0)) { + if ((GET_BYTES_04(to_send) != 0x00803E02U) || (GET_BYTES_48(to_send) != 0x0U)) { tx = 0; } } @@ -329,7 +328,7 @@ static int hyundai_tx_hook(CANPacket_t *to_send) { // ensuring that only the cancel button press is sent (VAL 4) when controls are off. // This avoids unintended engagements while still allowing resume spam if ((addr == 1265) && !controls_allowed) { - if ((GET_BYTES_04(to_send) & 0x7) != 4) { + if ((GET_BYTES_04(to_send) & 0x7U) != 4U) { tx = 0; } } diff --git a/panda/board/safety/safety_mazda.h b/panda/board/safety/safety_mazda.h index 01cc29f1b..48632f830 100644 --- a/panda/board/safety/safety_mazda.h +++ b/panda/board/safety/safety_mazda.h @@ -9,10 +9,10 @@ // CAN bus numbers #define MAZDA_MAIN 0 -#define MAZDA_AUX 1 -#define MAZDA_CAM 2 +#define MAZDA_AUX 1 +#define MAZDA_CAM 2 -#define MAZDA_MAX_STEER 2048 +#define MAZDA_MAX_STEER 2048U // max delta torque allowed for real time checks #define MAZDA_MAX_RT_DELTA 940 @@ -39,7 +39,7 @@ addr_checks mazda_rx_checks = {mazda_addr_checks, MAZDA_ADDR_CHECKS_LEN}; // track msgs coming from OP so that we know what CAM msgs to drop and what to forward static int mazda_rx_hook(CANPacket_t *to_push) { bool valid = addr_safety_check(to_push, &mazda_rx_checks, NULL, NULL, NULL); - if (valid && (GET_BUS(to_push) == MAZDA_MAIN)) { + if (valid && ((int)GET_BUS(to_push) == MAZDA_MAIN)) { int addr = GET_ADDR(to_push); if (addr == MAZDA_ENGINE_DATA) { @@ -49,14 +49,14 @@ static int mazda_rx_hook(CANPacket_t *to_push) { } if (addr == MAZDA_STEER_TORQUE) { - int torque_driver_new = GET_BYTE(to_push, 0) - 127; + int torque_driver_new = GET_BYTE(to_push, 0) - 127U; // update array of samples update_sample(&torque_driver, torque_driver_new); } // enter controls on rising edge of ACC, exit controls on ACC off if (addr == MAZDA_CRZ_CTRL) { - bool cruise_engaged = GET_BYTE(to_push, 0) & 8; + bool cruise_engaged = GET_BYTE(to_push, 0) & 0x8U; if (cruise_engaged) { if (!cruise_engaged_prev) { controls_allowed = 1; @@ -68,11 +68,11 @@ static int mazda_rx_hook(CANPacket_t *to_push) { } if (addr == MAZDA_ENGINE_DATA) { - gas_pressed = (GET_BYTE(to_push, 4) || (GET_BYTE(to_push, 5) & 0xF0)); + gas_pressed = (GET_BYTE(to_push, 4) || (GET_BYTE(to_push, 5) & 0xF0U)); } if (addr == MAZDA_PEDALS) { - brake_pressed = (GET_BYTE(to_push, 0) & 0x10); + brake_pressed = (GET_BYTE(to_push, 0) & 0x10U); } generic_rx_checks((addr == MAZDA_LKAS)); @@ -94,7 +94,7 @@ static int mazda_tx_hook(CANPacket_t *to_send) { // steer cmd checks if (addr == MAZDA_LKAS) { - int desired_torque = (((GET_BYTE(to_send, 0) & 0x0f) << 8) | GET_BYTE(to_send, 1)) - MAZDA_MAX_STEER; + int desired_torque = (((GET_BYTE(to_send, 0) & 0x0FU) << 8) | GET_BYTE(to_send, 1)) - MAZDA_MAX_STEER; bool violation = 0; uint32_t ts = microsecond_timer_get(); diff --git a/panda/board/safety/safety_nissan.h b/panda/board/safety/safety_nissan.h index 2c2c6f680..920a11a22 100644 --- a/panda/board/safety/safety_nissan.h +++ b/panda/board/safety/safety_nissan.h @@ -53,7 +53,7 @@ static int nissan_rx_hook(CANPacket_t *to_push) { if (addr == 0x2) { // Current steering angle // Factor -0.1, little endian - int angle_meas_new = (GET_BYTES_04(to_push) & 0xFFFF); + int angle_meas_new = (GET_BYTES_04(to_push) & 0xFFFFU); // Need to multiply by 10 here as LKAS and Steering wheel are different base unit angle_meas_new = to_signed(angle_meas_new, 16) * 10; @@ -71,9 +71,9 @@ static int nissan_rx_hook(CANPacket_t *to_push) { // X-Trail 0x15c, Leaf 0x239 if ((addr == 0x15c) || (addr == 0x239)) { if (addr == 0x15c){ - gas_pressed = ((GET_BYTE(to_push, 5) << 2) | ((GET_BYTE(to_push, 6) >> 6) & 0x3)) > 3; + gas_pressed = ((GET_BYTE(to_push, 5) << 2) | ((GET_BYTE(to_push, 6) >> 6) & 0x3U)) > 3U; } else { - gas_pressed = GET_BYTE(to_push, 0) > 3; + gas_pressed = GET_BYTE(to_push, 0) > 3U; } } } @@ -81,15 +81,15 @@ static int nissan_rx_hook(CANPacket_t *to_push) { // X-trail 0x454, Leaf 0x239 if ((addr == 0x454) || (addr == 0x239)) { if (addr == 0x454){ - brake_pressed = (GET_BYTE(to_push, 2) & 0x80) != 0; + brake_pressed = (GET_BYTE(to_push, 2) & 0x80U) != 0U; } else { - brake_pressed = ((GET_BYTE(to_push, 4) >> 5) & 1) != 0; + brake_pressed = ((GET_BYTE(to_push, 4) >> 5) & 1U) != 0U; } } // Handle cruise enabled if ((addr == 0x30f) && (((bus == 2) && (!nissan_alt_eps)) || ((bus == 1) && (nissan_alt_eps)))) { - bool cruise_engaged = (GET_BYTE(to_push, 0) >> 3) & 1; + bool cruise_engaged = (GET_BYTE(to_push, 0) >> 3) & 1U; if (cruise_engaged && !cruise_engaged_prev) { controls_allowed = 1; @@ -117,8 +117,8 @@ static int nissan_tx_hook(CANPacket_t *to_send) { // steer cmd checks if (addr == 0x169) { - int desired_angle = ((GET_BYTE(to_send, 0) << 10) | (GET_BYTE(to_send, 1) << 2) | ((GET_BYTE(to_send, 2) >> 6) & 0x3)); - bool lka_active = (GET_BYTE(to_send, 6) >> 4) & 1; + int desired_angle = ((GET_BYTE(to_send, 0) << 10) | (GET_BYTE(to_send, 1) << 2) | ((GET_BYTE(to_send, 2) >> 6) & 0x3U)); + bool lka_active = (GET_BYTE(to_send, 6) >> 4) & 1U; // offeset 1310 * NISSAN_DEG_TO_CAN desired_angle = desired_angle - 131000; @@ -154,7 +154,7 @@ static int nissan_tx_hook(CANPacket_t *to_send) { // acc button check, only allow cancel button to be sent if (addr == 0x20b) { // Violation of any button other than cancel is pressed - violation |= ((GET_BYTE(to_send, 1) & 0x3d) > 0); + violation |= ((GET_BYTE(to_send, 1) & 0x3dU) > 0U); } if (violation) { diff --git a/panda/board/safety/safety_subaru.h b/panda/board/safety/safety_subaru.h index 36eecb513..602803438 100644 --- a/panda/board/safety/safety_subaru.h +++ b/panda/board/safety/safety_subaru.h @@ -42,7 +42,7 @@ static uint8_t subaru_get_checksum(CANPacket_t *to_push) { } static uint8_t subaru_get_counter(CANPacket_t *to_push) { - return (uint8_t)(GET_BYTE(to_push, 1) & 0xF); + return (uint8_t)(GET_BYTE(to_push, 1) & 0xFU); } static uint8_t subaru_compute_checksum(CANPacket_t *to_push) { @@ -60,18 +60,18 @@ static int subaru_rx_hook(CANPacket_t *to_push) { bool valid = addr_safety_check(to_push, &subaru_rx_checks, subaru_get_checksum, subaru_compute_checksum, subaru_get_counter); - if (valid && (GET_BUS(to_push) == 0)) { + if (valid && (GET_BUS(to_push) == 0U)) { int addr = GET_ADDR(to_push); if (addr == 0x119) { int torque_driver_new; - torque_driver_new = ((GET_BYTES_04(to_push) >> 16) & 0x7FF); + torque_driver_new = ((GET_BYTES_04(to_push) >> 16) & 0x7FFU); torque_driver_new = -1 * to_signed(torque_driver_new, 11); update_sample(&torque_driver, torque_driver_new); } // enter controls on rising edge of ACC, exit controls on ACC off if (addr == 0x240) { - int cruise_engaged = ((GET_BYTES_48(to_push) >> 9) & 1); + int cruise_engaged = ((GET_BYTES_48(to_push) >> 9) & 1U); if (cruise_engaged && !cruise_engaged_prev) { controls_allowed = 1; } @@ -83,18 +83,17 @@ static int subaru_rx_hook(CANPacket_t *to_push) { // sample wheel speed, averaging opposite corners if (addr == 0x13a) { - int subaru_speed = (GET_BYTES_04(to_push) >> 12) & 0x1FFF; // FR - subaru_speed += (GET_BYTES_48(to_push) >> 6) & 0x1FFF; // RL + int subaru_speed = ((GET_BYTES_04(to_push) >> 12) & 0x1FFFU) + ((GET_BYTES_48(to_push) >> 6) & 0x1FFFU); // FR + RL subaru_speed /= 2; vehicle_moving = subaru_speed > SUBARU_STANDSTILL_THRSLD; } if (addr == 0x13c) { - brake_pressed = ((GET_BYTE(to_push, 7) >> 6) & 1); + brake_pressed = ((GET_BYTE(to_push, 7) >> 6) & 1U); } if (addr == 0x40) { - gas_pressed = GET_BYTE(to_push, 4) != 0; + gas_pressed = GET_BYTE(to_push, 4) != 0U; } generic_rx_checks((addr == 0x122)); @@ -106,7 +105,7 @@ static int subaru_legacy_rx_hook(CANPacket_t *to_push) { bool valid = addr_safety_check(to_push, &subaru_l_rx_checks, NULL, NULL, NULL); - if (valid && (GET_BUS(to_push) == 0)) { + if (valid && (GET_BUS(to_push) == 0U)) { int addr = GET_ADDR(to_push); if (addr == 0x371) { int torque_driver_new; @@ -117,7 +116,7 @@ static int subaru_legacy_rx_hook(CANPacket_t *to_push) { // enter controls on rising edge of ACC, exit controls on ACC off if (addr == 0x144) { - int cruise_engaged = ((GET_BYTES_48(to_push) >> 17) & 1); + int cruise_engaged = ((GET_BYTES_48(to_push) >> 17) & 1U); if (cruise_engaged && !cruise_engaged_prev) { controls_allowed = 1; } @@ -129,18 +128,17 @@ static int subaru_legacy_rx_hook(CANPacket_t *to_push) { // sample wheel speed, averaging opposite corners if (addr == 0xD4) { - int subaru_speed = (GET_BYTES_04(to_push) >> 16) & 0xFFFF; // FR - subaru_speed += GET_BYTES_48(to_push) & 0xFFFF; // RL + int subaru_speed = ((GET_BYTES_04(to_push) >> 16) & 0xFFFFU) + (GET_BYTES_48(to_push) & 0xFFFFU); // FR + RL subaru_speed /= 2; vehicle_moving = subaru_speed > SUBARU_STANDSTILL_THRSLD; } if (addr == 0xD1) { - brake_pressed = ((GET_BYTES_04(to_push) >> 16) & 0xFF) > 0; + brake_pressed = ((GET_BYTES_04(to_push) >> 16) & 0xFFU) > 0U; } if (addr == 0x140) { - gas_pressed = GET_BYTE(to_push, 0) != 0; + gas_pressed = GET_BYTE(to_push, 0) != 0U; } generic_rx_checks((addr == 0x164)); @@ -158,7 +156,7 @@ static int subaru_tx_hook(CANPacket_t *to_send) { // steer cmd checks if (addr == 0x122) { - int desired_torque = ((GET_BYTES_04(to_send) >> 16) & 0x1FFF); + int desired_torque = ((GET_BYTES_04(to_send) >> 16) & 0x1FFFU); bool violation = 0; uint32_t ts = microsecond_timer_get(); @@ -218,7 +216,7 @@ static int subaru_legacy_tx_hook(CANPacket_t *to_send) { // steer cmd checks if (addr == 0x164) { - int desired_torque = ((GET_BYTES_04(to_send) >> 8) & 0x1FFF); + int desired_torque = ((GET_BYTES_04(to_send) >> 8) & 0x1FFFU); bool violation = 0; uint32_t ts = microsecond_timer_get(); diff --git a/panda/board/safety/safety_tesla.h b/panda/board/safety/safety_tesla.h index 359c90078..ccbff7d60 100644 --- a/panda/board/safety/safety_tesla.h +++ b/panda/board/safety/safety_tesla.h @@ -62,25 +62,25 @@ static int tesla_rx_hook(CANPacket_t *to_push) { if(addr == 0x370) { // Steering angle: (0.1 * val) - 819.2 in deg. // Store it 1/10 deg to match steering request - int angle_meas_new = (((GET_BYTE(to_push, 4) & 0x3F) << 8) | GET_BYTE(to_push, 5)) - 8192; + int angle_meas_new = (((GET_BYTE(to_push, 4) & 0x3FU) << 8) | GET_BYTE(to_push, 5)) - 8192U; update_sample(&angle_meas, angle_meas_new); } } if(addr == (tesla_powertrain ? 0x116 : 0x118)) { // Vehicle speed: ((0.05 * val) - 25) * MPH_TO_MPS - vehicle_speed = (((((GET_BYTE(to_push, 3) & 0x0F) << 8) | (GET_BYTE(to_push, 2))) * 0.05) - 25) * 0.447; + vehicle_speed = (((((GET_BYTE(to_push, 3) & 0x0FU) << 8) | (GET_BYTE(to_push, 2))) * 0.05) - 25) * 0.447; vehicle_moving = ABS(vehicle_speed) > 0.1; } if(addr == (tesla_powertrain ? 0x106 : 0x108)) { // Gas pressed - gas_pressed = (GET_BYTE(to_push, 6) != 0); + gas_pressed = (GET_BYTE(to_push, 6) != 0U); } if(addr == (tesla_powertrain ? 0x1f8 : 0x20a)) { // Brake pressed - brake_pressed = (((GET_BYTE(to_push, 0) & 0x0C) >> 2) != 1); + brake_pressed = (((GET_BYTE(to_push, 0) & 0x0CU) >> 2) != 1U); } if(addr == (tesla_powertrain ? 0x256 : 0x368)) { @@ -129,7 +129,7 @@ static int tesla_tx_hook(CANPacket_t *to_send) { if(!tesla_powertrain && (addr == 0x488)) { // Steering control: (0.1 * val) - 1638.35 in deg. // We use 1/10 deg as a unit here - int raw_angle_can = (((GET_BYTE(to_send, 0) & 0x7F) << 8) | GET_BYTE(to_send, 1)); + int raw_angle_can = (((GET_BYTE(to_send, 0) & 0x7FU) << 8) | GET_BYTE(to_send, 1)); int desired_angle = raw_angle_can - 16384; int steer_control_type = GET_BYTE(to_send, 2) >> 6; bool steer_control_enabled = (steer_control_type != 0) && // NONE @@ -164,7 +164,7 @@ static int tesla_tx_hook(CANPacket_t *to_send) { if(!tesla_powertrain && (addr == 0x45)) { // No button other than cancel can be sent by us - int control_lever_status = (GET_BYTE(to_send, 0) & 0x3F); + int control_lever_status = (GET_BYTE(to_send, 0) & 0x3FU); if((control_lever_status != 0) && (control_lever_status != 1)) { violation = true; } @@ -174,14 +174,14 @@ static int tesla_tx_hook(CANPacket_t *to_send) { // DAS_control: longitudinal control message if (tesla_longitudinal) { // No AEB events may be sent by openpilot - int aeb_event = GET_BYTE(to_send, 2) & 0x03; + int aeb_event = GET_BYTE(to_send, 2) & 0x03U; if (aeb_event != 0) { violation = true; } // Don't allow any acceleration limits above the safety limits - int raw_accel_max = ((GET_BYTE(to_send, 6) & 0x1F) << 4) | (GET_BYTE(to_send, 5) >> 4); - int raw_accel_min = ((GET_BYTE(to_send, 5) & 0x0F) << 5) | (GET_BYTE(to_send, 4) >> 3); + int raw_accel_max = ((GET_BYTE(to_send, 6) & 0x1FU) << 4) | (GET_BYTE(to_send, 5) >> 4); + int raw_accel_min = ((GET_BYTE(to_send, 5) & 0x0FU) << 5) | (GET_BYTE(to_send, 4) >> 3); float accel_max = (0.04 * raw_accel_max) - 15; float accel_min = (0.04 * raw_accel_min) - 15; diff --git a/panda/board/safety/safety_toyota.h b/panda/board/safety/safety_toyota.h index 843acab0b..f3d5eff7e 100644 --- a/panda/board/safety/safety_toyota.h +++ b/panda/board/safety/safety_toyota.h @@ -24,7 +24,7 @@ const int TOYOTA_STANDSTILL_THRSLD = 100; // 1kph // gas_norm2 = ((gain_dbc*gas2) + offset2_dbc) // In this safety: ((gas1 + gas2)/2) > THRESHOLD const int TOYOTA_GAS_INTERCEPTOR_THRSLD = 845; -#define TOYOTA_GET_INTERCEPTOR(msg) (((GET_BYTE((msg), 0) << 8) + GET_BYTE((msg), 1) + (GET_BYTE((msg), 2) << 8) + GET_BYTE((msg), 3)) / 2) // avg between 2 tracks +#define TOYOTA_GET_INTERCEPTOR(msg) (((GET_BYTE((msg), 0) << 8) + GET_BYTE((msg), 1) + (GET_BYTE((msg), 2) << 8) + GET_BYTE((msg), 3)) / 2U) // avg between 2 tracks const CanMsg TOYOTA_TX_MSGS[] = {{0x283, 0, 7}, {0x2E6, 0, 8}, {0x2E7, 0, 8}, {0x33E, 0, 7}, {0x344, 0, 8}, {0x365, 0, 7}, {0x366, 0, 7}, {0x4CB, 0, 8}, // DSU bus 0 {0x128, 1, 6}, {0x141, 1, 4}, {0x160, 1, 8}, {0x161, 1, 7}, {0x470, 1, 4}, // DSU bus 1 @@ -55,7 +55,7 @@ static uint8_t toyota_compute_checksum(CANPacket_t *to_push) { } static uint8_t toyota_get_checksum(CANPacket_t *to_push) { - int checksum_byte = GET_LEN(to_push) - 1; + int checksum_byte = GET_LEN(to_push) - 1U; return (uint8_t)(GET_BYTE(to_push, checksum_byte)); } @@ -64,7 +64,7 @@ static int toyota_rx_hook(CANPacket_t *to_push) { bool valid = addr_safety_check(to_push, &toyota_rx_checks, toyota_get_checksum, toyota_compute_checksum, NULL); - if (valid && (GET_BUS(to_push) == 0)) { + if (valid && (GET_BUS(to_push) == 0U)) { int addr = GET_ADDR(to_push); // get eps motor torque (0.66 factor in dbc) @@ -87,7 +87,7 @@ static int toyota_rx_hook(CANPacket_t *to_push) { // exit controls on rising edge of gas press if (addr == 0x1D2) { // 5th bit is CRUISE_ACTIVE - int cruise_engaged = GET_BYTE(to_push, 0) & 0x20; + int cruise_engaged = GET_BYTE(to_push, 0) & 0x20U; if (!cruise_engaged) { controls_allowed = 0; } @@ -98,7 +98,7 @@ static int toyota_rx_hook(CANPacket_t *to_push) { // sample gas pedal if (!gas_interceptor_detected) { - gas_pressed = ((GET_BYTE(to_push, 0) >> 4) & 1) == 0; + gas_pressed = ((GET_BYTE(to_push, 0) >> 4) & 1U) == 0U; } } @@ -106,9 +106,9 @@ static int toyota_rx_hook(CANPacket_t *to_push) { if (addr == 0xaa) { int speed = 0; // sum 4 wheel speeds - for (int i=0; i<8; i+=2) { - int next_byte = i + 1; // hack to deal with misra 10.8 - speed += (GET_BYTE(to_push, i) << 8) + GET_BYTE(to_push, next_byte) - 0x1a6f; + for (uint8_t i=0U; i<8U; i+=2U) { + int wheel_speed = (GET_BYTE(to_push, i) << 8U) + GET_BYTE(to_push, (i+1U)); + speed += wheel_speed - 0x1a6f; } vehicle_moving = ABS(speed / 4) > TOYOTA_STANDSTILL_THRSLD; } @@ -116,7 +116,7 @@ static int toyota_rx_hook(CANPacket_t *to_push) { // most cars have brake_pressed on 0x226, corolla and rav4 on 0x224 if ((addr == 0x224) || (addr == 0x226)) { int byte = (addr == 0x224) ? 0 : 4; - brake_pressed = ((GET_BYTE(to_push, byte) >> 5) & 1) != 0; + brake_pressed = ((GET_BYTE(to_push, byte) >> 5) & 1U) != 0U; } // sample gas interceptor @@ -176,8 +176,8 @@ static int toyota_tx_hook(CANPacket_t *to_send) { // only sent to prevent dash errors, no actuation is accepted if (addr == 0x191) { // check the STEER_REQUEST, STEER_REQUEST_2, and STEER_ANGLE_CMD signals - bool lta_request = (GET_BYTE(to_send, 0) & 1) != 0; - bool lta_request2 = ((GET_BYTE(to_send, 3) >> 1) & 1) != 0; + bool lta_request = (GET_BYTE(to_send, 0) & 1U) != 0U; + bool lta_request2 = ((GET_BYTE(to_send, 3) >> 1) & 1U) != 0U; int lta_angle = (GET_BYTE(to_send, 1) << 8) | GET_BYTE(to_send, 2); lta_angle = to_signed(lta_angle, 16); diff --git a/panda/board/safety/safety_volkswagen.h b/panda/board/safety/safety_volkswagen.h index 42c6cac2f..8666c8807 100644 --- a/panda/board/safety/safety_volkswagen.h +++ b/panda/board/safety/safety_volkswagen.h @@ -145,7 +145,7 @@ static int volkswagen_mqb_rx_hook(CANPacket_t *to_push) { bool valid = addr_safety_check(to_push, &volkswagen_mqb_rx_checks, volkswagen_get_checksum, volkswagen_mqb_compute_crc, volkswagen_mqb_get_counter); - if (valid && (GET_BUS(to_push) == 0)) { + if (valid && (GET_BUS(to_push) == 0U)) { int addr = GET_ADDR(to_push); // Update in-motion state by sampling front wheel speeds @@ -163,8 +163,8 @@ static int volkswagen_mqb_rx_hook(CANPacket_t *to_push) { // Signal: LH_EPS_03.EPS_Lenkmoment (absolute torque) // Signal: LH_EPS_03.EPS_VZ_Lenkmoment (direction) if (addr == MSG_LH_EPS_03) { - int torque_driver_new = GET_BYTE(to_push, 5) | ((GET_BYTE(to_push, 6) & 0x1F) << 8); - int sign = (GET_BYTE(to_push, 6) & 0x80) >> 7; + int torque_driver_new = GET_BYTE(to_push, 5) | ((GET_BYTE(to_push, 6) & 0x1FU) << 8); + int sign = (GET_BYTE(to_push, 6) & 0x80U) >> 7; if (sign == 1) { torque_driver_new *= -1; } @@ -174,7 +174,7 @@ static int volkswagen_mqb_rx_hook(CANPacket_t *to_push) { // Enter controls on rising edge of stock ACC, exit controls if stock ACC disengages // Signal: TSK_06.TSK_Status if (addr == MSG_TSK_06) { - int acc_status = (GET_BYTE(to_push, 3) & 0x7); + int acc_status = (GET_BYTE(to_push, 3) & 0x7U); int cruise_engaged = ((acc_status == 3) || (acc_status == 4) || (acc_status == 5)) ? 1 : 0; if (cruise_engaged && !cruise_engaged_prev) { controls_allowed = 1; @@ -187,12 +187,12 @@ static int volkswagen_mqb_rx_hook(CANPacket_t *to_push) { // Signal: Motor_20.MO_Fahrpedalrohwert_01 if (addr == MSG_MOTOR_20) { - gas_pressed = ((GET_BYTES_04(to_push) >> 12) & 0xFF) != 0; + gas_pressed = ((GET_BYTES_04(to_push) >> 12) & 0xFFU) != 0U; } // Signal: ESP_05.ESP_Fahrer_bremst if (addr == MSG_ESP_05) { - brake_pressed = (GET_BYTE(to_push, 3) & 0x4) >> 2; + brake_pressed = (GET_BYTE(to_push, 3) & 0x4U) >> 2; } generic_rx_checks((addr == MSG_HCA_01)); @@ -205,13 +205,13 @@ static int volkswagen_pq_rx_hook(CANPacket_t *to_push) { bool valid = addr_safety_check(to_push, &volkswagen_pq_rx_checks, volkswagen_get_checksum, volkswagen_pq_compute_checksum, volkswagen_pq_get_counter); - if (valid && (GET_BUS(to_push) == 0)) { + if (valid && (GET_BUS(to_push) == 0U)) { int addr = GET_ADDR(to_push); // Update in-motion state from speed value. // Signal: Bremse_1.Geschwindigkeit_neu__Bremse_1_ if (addr == MSG_BREMSE_1) { - int speed = ((GET_BYTE(to_push, 2) & 0xFE) >> 1) | (GET_BYTE(to_push, 3) << 7); + int speed = ((GET_BYTE(to_push, 2) & 0xFEU) >> 1) | (GET_BYTE(to_push, 3) << 7); // DBC speed scale 0.01: 0.3m/s = 108. vehicle_moving = speed > 108; } @@ -220,8 +220,8 @@ static int volkswagen_pq_rx_hook(CANPacket_t *to_push) { // Signal: Lenkhilfe_3.LH3_LM (absolute torque) // Signal: Lenkhilfe_3.LH3_LMSign (direction) if (addr == MSG_LENKHILFE_3) { - int torque_driver_new = GET_BYTE(to_push, 2) | ((GET_BYTE(to_push, 3) & 0x3) << 8); - int sign = (GET_BYTE(to_push, 3) & 0x4) >> 2; + int torque_driver_new = GET_BYTE(to_push, 2) | ((GET_BYTE(to_push, 3) & 0x3U) << 8); + int sign = (GET_BYTE(to_push, 3) & 0x4U) >> 2; if (sign == 1) { torque_driver_new *= -1; } @@ -231,7 +231,7 @@ static int volkswagen_pq_rx_hook(CANPacket_t *to_push) { // Enter controls on rising edge of stock ACC, exit controls if stock ACC disengages // Signal: Motor_2.GRA_Status if (addr == MSG_MOTOR_2) { - int acc_status = (GET_BYTE(to_push, 2) & 0xC0) >> 6; + int acc_status = (GET_BYTE(to_push, 2) & 0xC0U) >> 6; int cruise_engaged = ((acc_status == 1) || (acc_status == 2)) ? 1 : 0; if (cruise_engaged && !cruise_engaged_prev) { controls_allowed = 1; @@ -249,7 +249,7 @@ static int volkswagen_pq_rx_hook(CANPacket_t *to_push) { // Signal: Motor_2.Bremslichtschalter if (addr == MSG_MOTOR_2) { - brake_pressed = (GET_BYTE(to_push, 2) & 0x1); + brake_pressed = (GET_BYTE(to_push, 2) & 0x1U); } generic_rx_checks((addr == MSG_HCA_1)); @@ -309,8 +309,8 @@ static int volkswagen_mqb_tx_hook(CANPacket_t *to_send) { // Signal: HCA_01.Assist_Torque (absolute torque) // Signal: HCA_01.Assist_VZ (direction) if (addr == MSG_HCA_01) { - int desired_torque = GET_BYTE(to_send, 2) | ((GET_BYTE(to_send, 3) & 0x3F) << 8); - int sign = (GET_BYTE(to_send, 3) & 0x80) >> 7; + int desired_torque = GET_BYTE(to_send, 2) | ((GET_BYTE(to_send, 3) & 0x3FU) << 8); + int sign = (GET_BYTE(to_send, 3) & 0x80U) >> 7; if (sign == 1) { desired_torque *= -1; } @@ -324,7 +324,7 @@ static int volkswagen_mqb_tx_hook(CANPacket_t *to_send) { // This avoids unintended engagements while still allowing resume spam if ((addr == MSG_GRA_ACC_01) && !controls_allowed) { // disallow resume and set: bits 16 and 19 - if ((GET_BYTE(to_send, 2) & 0x9) != 0) { + if ((GET_BYTE(to_send, 2) & 0x9U) != 0U) { tx = 0; } } @@ -345,9 +345,9 @@ static int volkswagen_pq_tx_hook(CANPacket_t *to_send) { // Signal: HCA_1.LM_Offset (absolute torque) // Signal: HCA_1.LM_Offsign (direction) if (addr == MSG_HCA_1) { - int desired_torque = GET_BYTE(to_send, 2) | ((GET_BYTE(to_send, 3) & 0x7F) << 8); + int desired_torque = GET_BYTE(to_send, 2) | ((GET_BYTE(to_send, 3) & 0x7FU) << 8); desired_torque = desired_torque / 32; // DBC scale from PQ network to centi-Nm - int sign = (GET_BYTE(to_send, 3) & 0x80) >> 7; + int sign = (GET_BYTE(to_send, 3) & 0x80U) >> 7; if (sign == 1) { desired_torque *= -1; } @@ -361,7 +361,7 @@ static int volkswagen_pq_tx_hook(CANPacket_t *to_send) { // This avoids unintended engagements while still allowing resume spam if ((addr == MSG_GRA_NEU) && !controls_allowed) { // disallow resume and set: bits 16 and 17 - if ((GET_BYTE(to_send, 2) & 0x3) != 0) { + if ((GET_BYTE(to_send, 2) & 0x3U) != 0U) { tx = 0; } } diff --git a/panda/board/safety_declarations.h b/panda/board/safety_declarations.h index d5705b482..11438a486 100644 --- a/panda/board/safety_declarations.h +++ b/panda/board/safety_declarations.h @@ -1,7 +1,7 @@ -#define GET_BIT(msg, b) (((msg)->data[(int)((b) / 8)] >> ((b) % 8)) & 0x1) +#define GET_BIT(msg, b) (((msg)->data[((b) / 8U)] >> ((b) % 8U)) & 0x1U) #define GET_BYTE(msg, b) ((msg)->data[(b)]) -#define GET_BYTES_04(msg) ((msg)->data[0] | ((msg)->data[1] << 8) | ((msg)->data[2] << 16) | ((msg)->data[3] << 24)) -#define GET_BYTES_48(msg) ((msg)->data[4] | ((msg)->data[5] << 8) | ((msg)->data[6] << 16) | ((msg)->data[7] << 24)) +#define GET_BYTES_04(msg) ((msg)->data[0] | ((msg)->data[1] << 8U) | ((msg)->data[2] << 16U) | ((msg)->data[3] << 24U)) +#define GET_BYTES_48(msg) ((msg)->data[4] | ((msg)->data[5] << 8U) | ((msg)->data[6] << 16U) | ((msg)->data[7] << 24U)) #define GET_FLAG(value, mask) (((__typeof__(mask))(value) & (mask)) == (mask)) const int MAX_WRONG_COUNTERS = 5; diff --git a/panda/board/stm32h7/clock.h b/panda/board/stm32h7/clock.h index 627fedee5..10febe15a 100644 --- a/panda/board/stm32h7/clock.h +++ b/panda/board/stm32h7/clock.h @@ -10,6 +10,9 @@ void clock_init(void) { // enable external oscillator HSE register_set_bits(&(RCC->CR), RCC_CR_HSEON); while ((RCC->CR & RCC_CR_HSERDY) == 0); + // enable internal HSI48 for USB FS kernel + register_set_bits(&(RCC->CR), RCC_CR_HSI48ON); + while ((RCC->CR & RCC_CR_HSI48RDY) == 0); // Specify the frequency source for PLL1, divider for DIVM1, DIVM2, DIVM3 : HSE, 5, 5, 5 register_set(&(RCC->PLLCKSELR), RCC_PLLCKSELR_PLLSRC_HSE | RCC_PLLCKSELR_DIVM1_0 | RCC_PLLCKSELR_DIVM1_2 | RCC_PLLCKSELR_DIVM2_0 | RCC_PLLCKSELR_DIVM2_2 | RCC_PLLCKSELR_DIVM3_0 | RCC_PLLCKSELR_DIVM3_2, 0x3F3F3F3U); @@ -23,16 +26,6 @@ void clock_init(void) { while((RCC->CR & RCC_CR_PLL1RDY) == 0); // *** PLL1 end *** - // *** PLL3 start *** - // Specify multiplier N and dividers P, Q, R for PLL1 : 48, 2, 5, 2 (PLL3Q 48Mhz for USB FS) - register_set(&(RCC->PLL3DIVR), 0x104022FU, 0x7F7FFFFFU); - // Specify the input and output frequency ranges, enable dividers for PLL1 - register_set(&(RCC->PLLCFGR), RCC_PLLCFGR_PLL3RGE_2 | RCC_PLLCFGR_DIVP3EN | RCC_PLLCFGR_DIVQ3EN | RCC_PLLCFGR_DIVR3EN, 0x1C00C00U); - // Enable PLL1 - register_set_bits(&(RCC->CR), RCC_CR_PLL3ON); - while((RCC->CR & RCC_CR_PLL3RDY) == 0); - // *** PLL1 end *** - //////////////OTHER CLOCKS//////////////////// // RCC HCLK Clock Source / RCC APB3 Clock Source / RCC SYS Clock Source register_set(&(RCC->D1CFGR), RCC_D1CFGR_HPRE_DIV2 | RCC_D1CFGR_D1PPRE_DIV2 | RCC_D1CFGR_D1CPRE_DIV1, 0xF7FU); @@ -49,8 +42,8 @@ void clock_init(void) { register_set_bits(&(RCC->AHB4ENR), RCC_APB4ENR_SYSCFGEN); //////////////END OTHER CLOCKS//////////////////// - // Configure clock source for USB (PLL3Q at 48Mhz) - register_set(&(RCC->D2CCIP2R), RCC_D2CCIP2R_USBSEL_1, RCC_D2CCIP2R_USBSEL); + // Configure clock source for USB (HSI at 48Mhz) + register_set(&(RCC->D2CCIP2R), RCC_D2CCIP2R_USBSEL_1 | RCC_D2CCIP2R_USBSEL_0, RCC_D2CCIP2R_USBSEL); // Configure clock source for FDCAN (PLL1Q at 80Mhz) register_set(&(RCC->D2CCIP1R), RCC_D2CCIP1R_FDCANSEL_0, RCC_D2CCIP1R_FDCANSEL); // Configure clock source for ADC1,2,3 (per_ck(currently HSE)) diff --git a/panda/board/usb_protocol.h b/panda/board/usb_protocol.h index 0e50aa7a0..a20b6c1b4 100644 --- a/panda/board/usb_protocol.h +++ b/panda/board/usb_protocol.h @@ -1,39 +1,39 @@ typedef struct { - uint8_t ptr; - uint8_t tail_size; + int ptr; + int tail_size; uint8_t data[72]; uint8_t counter; } usb_asm_buffer; usb_asm_buffer ep1_buffer = {.ptr = 0, .tail_size = 0, .counter = 0}; -uint32_t total_rx_size = 0; +int total_rx_size = 0; int usb_cb_ep1_in(void *usbdata, int len, bool hardwired) { UNUSED(hardwired); - uint8_t pos = 1; + int pos = 1; uint8_t *usbdata8 = (uint8_t *)usbdata; usbdata8[0] = ep1_buffer.counter; // Send tail of previous message if it is in buffer if (ep1_buffer.ptr > 0) { - if (ep1_buffer.ptr <= 63U) { + if (ep1_buffer.ptr <= 63) { (void)memcpy(&usbdata8[pos], ep1_buffer.data, ep1_buffer.ptr); pos += ep1_buffer.ptr; ep1_buffer.ptr = 0; } else { - (void)memcpy(&usbdata8[pos], ep1_buffer.data, 63U); - ep1_buffer.ptr = ep1_buffer.ptr - 63U; - (void)memcpy(ep1_buffer.data, &ep1_buffer.data[63U], ep1_buffer.ptr); - pos += 63U; + (void)memcpy(&usbdata8[pos], ep1_buffer.data, 63); + ep1_buffer.ptr = ep1_buffer.ptr - 63; + (void)memcpy(ep1_buffer.data, &ep1_buffer.data[63], ep1_buffer.ptr); + pos += 63; } } if (total_rx_size > MAX_EP1_CHUNK_PER_BULK_TRANSFER) { total_rx_size = 0; - ep1_buffer.counter = 0; + ep1_buffer.counter = 0U; } else { CANPacket_t can_packet; while ((pos < len) && can_pop(&can_rx_q, &can_packet)) { - uint8_t pckt_len = CANPACKET_HEAD_SIZE + dlc_to_len[can_packet.data_len_code]; + int pckt_len = CANPACKET_HEAD_SIZE + dlc_to_len[can_packet.data_len_code]; if ((pos + pckt_len) <= len) { (void)memcpy(&usbdata8[pos], &can_packet, pckt_len); pos += pckt_len; @@ -50,7 +50,7 @@ int usb_cb_ep1_in(void *usbdata, int len, bool hardwired) { total_rx_size += pos; } if (pos != len) { - ep1_buffer.counter = 0; + ep1_buffer.counter = 0U; total_rx_size = 0; } if (pos <= 1) { pos = 0; } @@ -64,17 +64,17 @@ void usb_cb_ep3_out(void *usbdata, int len, bool hardwired) { UNUSED(hardwired); uint8_t *usbdata8 = (uint8_t *)usbdata; // Got first packet from a stream, resetting buffer and counter - if (usbdata8[0] == 0) { - ep3_buffer.counter = 0; + if (usbdata8[0] == 0U) { + ep3_buffer.counter = 0U; ep3_buffer.ptr = 0; ep3_buffer.tail_size = 0; } // Assembling can message with data from buffer if (usbdata8[0] == ep3_buffer.counter) { - uint8_t pos = 1; + int pos = 1; ep3_buffer.counter++; if (ep3_buffer.ptr != 0) { - if (ep3_buffer.tail_size <= 63U) { + if (ep3_buffer.tail_size <= 63) { CANPacket_t to_push; (void)memcpy(&ep3_buffer.data[ep3_buffer.ptr], &usbdata8[pos], ep3_buffer.tail_size); (void)memcpy(&to_push, ep3_buffer.data, ep3_buffer.ptr + ep3_buffer.tail_size); @@ -84,15 +84,15 @@ void usb_cb_ep3_out(void *usbdata, int len, bool hardwired) { ep3_buffer.tail_size = 0; } else { (void)memcpy(&ep3_buffer.data[ep3_buffer.ptr], &usbdata8[pos], len - pos); - ep3_buffer.tail_size -= 63U; - ep3_buffer.ptr += 63U; - pos += 63U; + ep3_buffer.tail_size -= 63; + ep3_buffer.ptr += 63; + pos += 63; } } while (pos < len) { - uint8_t pckt_len = CANPACKET_HEAD_SIZE + dlc_to_len[(usbdata8[pos] >> 4U)]; - if ((pos + pckt_len) <= (uint8_t)len) { + int pckt_len = CANPACKET_HEAD_SIZE + dlc_to_len[(usbdata8[pos] >> 4U)]; + if ((pos + pckt_len) <= len) { CANPacket_t to_push; (void)memcpy(&to_push, &usbdata8[pos], pckt_len); can_send(&to_push, to_push.bus, false); diff --git a/panda/python/__init__.py b/panda/python/__init__.py index e491f3104..87c811fea 100644 --- a/panda/python/__init__.py +++ b/panda/python/__init__.py @@ -193,7 +193,7 @@ class Panda(object): SAFETY_ALLOUTPUT = 17 SAFETY_GM_ASCM = 18 SAFETY_NOOUTPUT = 19 - SAFETY_HONDA_BOSCH_HARNESS = 20 + SAFETY_HONDA_BOSCH = 20 SAFETY_VOLKSWAGEN_PQ = 21 SAFETY_SUBARU_LEGACY = 22 SAFETY_HYUNDAI_LEGACY = 23 diff --git a/release/files_common b/release/files_common index aeae351df..3fc354396 100644 --- a/release/files_common +++ b/release/files_common @@ -36,7 +36,6 @@ common/filter_simple.py common/stat_live.py common/spinner.py common/text_window.py -common/cython_hacks.py common/SConscript common/kalman/.gitignore @@ -306,6 +305,8 @@ selfdrive/loggerd/omx_encoder.h selfdrive/loggerd/logger.cc selfdrive/loggerd/logger.h selfdrive/loggerd/loggerd.cc +selfdrive/loggerd/loggerd.h +selfdrive/loggerd/main.cc selfdrive/loggerd/bootlog.cc selfdrive/loggerd/raw_logger.cc selfdrive/loggerd/raw_logger.h @@ -366,8 +367,6 @@ selfdrive/camerad/snapshot/* selfdrive/camerad/include/* selfdrive/camerad/cameras/camera_common.h selfdrive/camerad/cameras/camera_common.cc -selfdrive/camerad/cameras/camera_frame_stream.cc -selfdrive/camerad/cameras/camera_frame_stream.h selfdrive/camerad/cameras/camera_qcom.cc selfdrive/camerad/cameras/camera_qcom.h selfdrive/camerad/cameras/camera_replay.cc @@ -444,9 +443,6 @@ selfdrive/assets/training/* third_party/SConscript -third_party/nanovg/*.c -third_party/nanovg/*.h - third_party/libgralloc/** third_party/linux/** third_party/opencl/** diff --git a/release/files_tici b/release/files_tici index 29c8353b8..59cc41918 100644 --- a/release/files_tici +++ b/release/files_tici @@ -4,7 +4,6 @@ selfdrive/timezoned.py selfdrive/assets/navigation/* selfdrive/assets/training_wide/* -selfdrive/assets/sounds_tici/* selfdrive/camerad/cameras/camera_qcom2.cc selfdrive/camerad/cameras/camera_qcom2.h diff --git a/release/verify.sh b/release/verify.sh new file mode 100755 index 000000000..2ebd50a29 --- /dev/null +++ b/release/verify.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -e + +RED="\033[0;31m" +GREEN="\033[0;32m" +CLEAR="\033[0m" + +BRANCHES="devel dashcam dashcam3 release2 release3" +for b in $BRANCHES; do + if git diff --quiet origin/$b origin/$b-staging && [ "$(git rev-parse origin/$b)" = "$(git rev-parse origin/$b-staging)" ]; then + printf "%-10s $GREEN ok $CLEAR\n" "$b" + else + printf "%-10s $RED mismatch $CLEAR\n" "$b" + fi +done diff --git a/selfdrive/assets/offroad/fcc.html b/selfdrive/assets/offroad/fcc.html index 7f03a7204..793bea533 100644 --- a/selfdrive/assets/offroad/fcc.html +++ b/selfdrive/assets/offroad/fcc.html @@ -11,7 +11,7 @@

FCC ID: 2AOHHTURBOXSOMD845

Quectel/EG25-G
-

FCC ID: XMR201903EG25GM

+

FCC ID: XMR201903EG25G

This device complies with Part 15 of the FCC Rules. Operation is subject to the following two conditions: diff --git a/selfdrive/assets/sounds/disengage.wav b/selfdrive/assets/sounds/disengage.wav new file mode 100644 index 000000000..ba583c41f Binary files /dev/null and b/selfdrive/assets/sounds/disengage.wav differ diff --git a/selfdrive/assets/sounds/disengaged.wav b/selfdrive/assets/sounds/disengaged.wav deleted file mode 100644 index 3aa8e51e6..000000000 Binary files a/selfdrive/assets/sounds/disengaged.wav and /dev/null differ diff --git a/selfdrive/assets/sounds/engage.wav b/selfdrive/assets/sounds/engage.wav new file mode 100644 index 000000000..41e9b2d58 Binary files /dev/null and b/selfdrive/assets/sounds/engage.wav differ diff --git a/selfdrive/assets/sounds/engaged.wav b/selfdrive/assets/sounds/engaged.wav deleted file mode 100644 index 1451f937f..000000000 Binary files a/selfdrive/assets/sounds/engaged.wav and /dev/null differ diff --git a/selfdrive/assets/sounds/error.wav b/selfdrive/assets/sounds/error.wav deleted file mode 100644 index e805181ae..000000000 Binary files a/selfdrive/assets/sounds/error.wav and /dev/null differ diff --git a/selfdrive/assets/sounds/prompt.wav b/selfdrive/assets/sounds/prompt.wav new file mode 100644 index 000000000..420e9fabe Binary files /dev/null and b/selfdrive/assets/sounds/prompt.wav differ diff --git a/selfdrive/assets/sounds/prompt_distracted.wav b/selfdrive/assets/sounds/prompt_distracted.wav new file mode 100644 index 000000000..c3d4475ca Binary files /dev/null and b/selfdrive/assets/sounds/prompt_distracted.wav differ diff --git a/selfdrive/assets/sounds/refuse.wav b/selfdrive/assets/sounds/refuse.wav new file mode 100644 index 000000000..0e80f7d12 Binary files /dev/null and b/selfdrive/assets/sounds/refuse.wav differ diff --git a/selfdrive/assets/sounds/warning_1.wav b/selfdrive/assets/sounds/warning_1.wav deleted file mode 100644 index 43ca74cc5..000000000 Binary files a/selfdrive/assets/sounds/warning_1.wav and /dev/null differ diff --git a/selfdrive/assets/sounds/warning_2.wav b/selfdrive/assets/sounds/warning_2.wav deleted file mode 100644 index 4909f1198..000000000 Binary files a/selfdrive/assets/sounds/warning_2.wav and /dev/null differ diff --git a/selfdrive/assets/sounds/warning_repeat.wav b/selfdrive/assets/sounds/warning_immediate.wav similarity index 100% rename from selfdrive/assets/sounds/warning_repeat.wav rename to selfdrive/assets/sounds/warning_immediate.wav diff --git a/selfdrive/assets/sounds/warning_soft.wav b/selfdrive/assets/sounds/warning_soft.wav new file mode 100644 index 000000000..261c7e137 Binary files /dev/null and b/selfdrive/assets/sounds/warning_soft.wav differ diff --git a/selfdrive/assets/sounds_tici/disengaged.wav b/selfdrive/assets/sounds_tici/disengaged.wav deleted file mode 100644 index 182adffa2..000000000 Binary files a/selfdrive/assets/sounds_tici/disengaged.wav and /dev/null differ diff --git a/selfdrive/assets/sounds_tici/engaged.wav b/selfdrive/assets/sounds_tici/engaged.wav deleted file mode 100644 index 5c4c8b4d1..000000000 Binary files a/selfdrive/assets/sounds_tici/engaged.wav and /dev/null differ diff --git a/selfdrive/assets/sounds_tici/error.wav b/selfdrive/assets/sounds_tici/error.wav deleted file mode 100644 index c4e280e9a..000000000 Binary files a/selfdrive/assets/sounds_tici/error.wav and /dev/null differ diff --git a/selfdrive/assets/sounds_tici/warning_1.wav b/selfdrive/assets/sounds_tici/warning_1.wav deleted file mode 100644 index c937b3be0..000000000 Binary files a/selfdrive/assets/sounds_tici/warning_1.wav and /dev/null differ diff --git a/selfdrive/assets/sounds_tici/warning_2.wav b/selfdrive/assets/sounds_tici/warning_2.wav deleted file mode 100644 index 49188db88..000000000 Binary files a/selfdrive/assets/sounds_tici/warning_2.wav and /dev/null differ diff --git a/selfdrive/assets/sounds_tici/warning_repeat.wav b/selfdrive/assets/sounds_tici/warning_repeat.wav deleted file mode 100644 index bb3bfd0d5..000000000 Binary files a/selfdrive/assets/sounds_tici/warning_repeat.wav and /dev/null differ diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index 779045bf9..d19692661 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -30,7 +30,7 @@ from selfdrive.hardware import HARDWARE, PC from selfdrive.loggerd.config import ROOT from selfdrive.loggerd.xattr_cache import getxattr, setxattr from selfdrive.swaglog import cloudlog, SWAGLOG_DIR -from selfdrive.version import version, get_version, get_git_remote, get_git_branch, get_git_commit +from selfdrive.version import get_version, get_origin, get_short_branch, get_commit ATHENA_HOST = os.getenv('ATHENA_HOST', 'wss://athena.comma.ai') HANDLER_THREADS = int(os.getenv('HANDLER_THREADS', "4")) @@ -176,17 +176,19 @@ def getMessage(service=None, timeout=1000): def getVersion(): return { "version": get_version(), - "remote": get_git_remote(), - "branch": get_git_branch(), - "commit": get_git_commit(), + "remote": get_origin(), + "branch": get_short_branch(), + "commit": get_commit(), } @dispatcher.add_method -def setNavDestination(latitude=0, longitude=0): +def setNavDestination(latitude=0, longitude=0, place_name=None, place_details=None): destination = { "latitude": latitude, "longitude": longitude, + "place_name": place_name, + "place_details": place_details, } Params().put("NavDestination", json.dumps(destination)) @@ -551,7 +553,7 @@ def main(): except socket.timeout: try: r = requests.get("http://api.commadotai.com/v1/me", allow_redirects=False, - headers={"User-Agent": f"openpilot-{version}"}, timeout=15.0) + headers={"User-Agent": f"openpilot-{get_version()}"}, timeout=15.0) if r.status_code == 302 and r.headers['Location'].startswith("http://u.web2go.com"): params.put_bool("PrimeRedirected", True) except Exception: diff --git a/selfdrive/athena/manage_athenad.py b/selfdrive/athena/manage_athenad.py index 7bb717e07..fa95eacd8 100755 --- a/selfdrive/athena/manage_athenad.py +++ b/selfdrive/athena/manage_athenad.py @@ -6,7 +6,7 @@ from multiprocessing import Process from common.params import Params from selfdrive.manager.process import launcher from selfdrive.swaglog import cloudlog -from selfdrive.version import version, dirty +from selfdrive.version import get_version, get_dirty ATHENA_MGR_PID_PARAM = "AthenadPid" @@ -14,7 +14,7 @@ ATHENA_MGR_PID_PARAM = "AthenadPid" def main(): params = Params() dongle_id = params.get("DongleId").decode('utf-8') - cloudlog.bind_global(dongle_id=dongle_id, version=version, dirty=dirty) + cloudlog.bind_global(dongle_id=dongle_id, version=get_version(), dirty=get_dirty()) try: while 1: diff --git a/selfdrive/athena/registration.py b/selfdrive/athena/registration.py index 39dfb3c23..e06bae506 100755 --- a/selfdrive/athena/registration.py +++ b/selfdrive/athena/registration.py @@ -1,14 +1,13 @@ -#!/usr/bin/env python3 -import os +#/!/usr/bin/env python3 import time import json import jwt +from pathlib import Path from datetime import datetime, timedelta from common.api import api_get from common.params import Params from common.spinner import Spinner -from common.file_helpers import mkdirs_exists_ok from common.basedir import PERSIST from selfdrive.controls.lib.alertmanager import set_offroad_alert from selfdrive.hardware import HARDWARE @@ -27,19 +26,11 @@ def register(show_spinner=False) -> str: dongle_id = params.get("DongleId", encoding='utf8') needs_registration = None in (IMEI, HardwareSerial, dongle_id) - # create a key for auth - # your private key is kept on your device persist partition and never sent to our servers - # do not erase your persist partition - if not os.path.isfile(PERSIST+"/comma/id_rsa.pub"): - needs_registration = True - cloudlog.warning("generating your personal RSA key") - mkdirs_exists_ok(PERSIST+"/comma") - assert os.system("openssl genrsa -out "+PERSIST+"/comma/id_rsa.tmp 2048") == 0 - assert os.system("openssl rsa -in "+PERSIST+"/comma/id_rsa.tmp -pubout -out "+PERSIST+"/comma/id_rsa.tmp.pub") == 0 - os.rename(PERSIST+"/comma/id_rsa.tmp", PERSIST+"/comma/id_rsa") - os.rename(PERSIST+"/comma/id_rsa.tmp.pub", PERSIST+"/comma/id_rsa.pub") - - if needs_registration: + pubkey = Path(PERSIST+"/comma/id_rsa.pub") + if not pubkey.is_file(): + dongle_id = UNREGISTERED_DONGLE_ID + cloudlog.warning(f"missing public key: {pubkey}") + elif needs_registration: if show_spinner: spinner = Spinner() spinner.update("registering device") diff --git a/selfdrive/boardd/.gitignore b/selfdrive/boardd/.gitignore index 1f653bde8..e8daa2ef2 100644 --- a/selfdrive/boardd/.gitignore +++ b/selfdrive/boardd/.gitignore @@ -1,2 +1,3 @@ boardd boardd_api_impl.cpp +tests/test_boardd_usbprotocol diff --git a/selfdrive/boardd/SConscript b/selfdrive/boardd/SConscript index f2a1f3f7b..07ded56e1 100644 --- a/selfdrive/boardd/SConscript +++ b/selfdrive/boardd/SConscript @@ -1,6 +1,9 @@ Import('env', 'envCython', 'common', 'cereal', 'messaging') -env.Program('boardd', ['boardd.cc', 'panda.cc', 'pigeon.cc'], LIBS=['usb-1.0', common, cereal, messaging, 'pthread', 'zmq', 'capnp', 'kj']) +libs = ['usb-1.0', common, cereal, messaging, 'pthread', 'zmq', 'capnp', 'kj'] +env.Program('boardd', ['boardd.cc', 'panda.cc', 'pigeon.cc'], LIBS=libs) env.Library('libcan_list_to_can_capnp', ['can_list_to_can_capnp.cc']) envCython.Program('boardd_api_impl.so', 'boardd_api_impl.pyx', LIBS=["can_list_to_can_capnp", 'capnp', 'kj'] + envCython["LIBS"]) +if GetOption('test'): + env.Program('tests/test_boardd_usbprotocol', ['tests/test_boardd_usbprotocol.cc', 'panda.cc'], LIBS=libs) diff --git a/selfdrive/boardd/boardd.cc b/selfdrive/boardd/boardd.cc index ce73662e4..d09738a70 100644 --- a/selfdrive/boardd/boardd.cc +++ b/selfdrive/boardd/boardd.cc @@ -62,7 +62,7 @@ std::atomic pigeon_active(false); ExitHandler do_exit; -std::string get_time_str(const struct tm &time) { +static std::string get_time_str(const struct tm &time) { char s[30] = {'\0'}; std::strftime(s, std::size(s), "%Y-%m-%d %H:%M:%S", &time); return s; @@ -70,11 +70,43 @@ std::string get_time_str(const struct tm &time) { bool check_all_connected(const std::vector &pandas) { for (const auto& panda : pandas) { - if (!panda->connected) return false; + if (!panda->connected) { + do_exit = true; + return false; + } } return true; } +enum class SyncTimeDir { TO_PANDA, FROM_PANDA }; + +void sync_time(Panda *panda, SyncTimeDir dir) { + if (!panda->has_rtc) return; + + setenv("TZ", "UTC", 1); + struct tm sys_time = util::get_time(); + struct tm rtc_time = panda->get_rtc(); + + if (dir == SyncTimeDir::TO_PANDA) { + if (util::time_valid(sys_time)) { + // Write time to RTC if it looks reasonable + double seconds = difftime(mktime(&rtc_time), mktime(&sys_time)); + if (std::abs(seconds) > 1.1) { + panda->set_rtc(sys_time); + LOGW("Updating panda RTC. dt = %.2f System: %s RTC: %s", + seconds, get_time_str(sys_time).c_str(), get_time_str(rtc_time).c_str()); + } + } + } else if (dir == SyncTimeDir::FROM_PANDA) { + if (!util::time_valid(sys_time) && util::time_valid(rtc_time)) { + const struct timeval tv = {mktime(&rtc_time), 0}; + settimeofday(&tv, 0); + LOGE("System time wrong, setting from RTC. System: %s RTC: %s", + get_time_str(sys_time).c_str(), get_time_str(rtc_time).c_str()); + } + } +} + bool safety_setter_thread(std::vector pandas) { LOGD("Starting safety setter thread"); @@ -168,40 +200,22 @@ Panda *usb_connect(std::string serial="", uint32_t index=0) { std::call_once(connected_once, &Panda::set_usb_power_mode, panda, cereal::PeripheralState::UsbPowerMode::CDP); #endif - if (panda->has_rtc) { - setenv("TZ","UTC",1); - struct tm sys_time = util::get_time(); - struct tm rtc_time = panda->get_rtc(); - - if (!util::time_valid(sys_time) && util::time_valid(rtc_time)) { - LOGE("System time wrong, setting from RTC. System: %s RTC: %s", - get_time_str(sys_time).c_str(), get_time_str(rtc_time).c_str()); - const struct timeval tv = {mktime(&rtc_time), 0}; - settimeofday(&tv, 0); - } - } - + sync_time(panda.get(), SyncTimeDir::FROM_PANDA); return panda.release(); } void can_send_thread(std::vector pandas, bool fake_send) { - LOGD("start send thread"); + util::set_thread_name("boardd_can_send"); AlignedBuffer aligned_buf; - Context * context = Context::create(); - SubSocket * subscriber = SubSocket::create(context, "sendcan"); + std::unique_ptr context(Context::create()); + std::unique_ptr subscriber(SubSocket::create(context.get(), "sendcan")); assert(subscriber != NULL); subscriber->setTimeout(100); // run as fast as messages come in - while (!do_exit) { - if (!check_all_connected(pandas)) { - do_exit = true; - break; - } - - Message * msg = subscriber->receive(); - + while (!do_exit && check_all_connected(pandas)) { + std::unique_ptr msg(subscriber->receive()); if (!msg) { if (errno == EINTR) { do_exit = true; @@ -209,27 +223,20 @@ void can_send_thread(std::vector pandas, bool fake_send) { continue; } - capnp::FlatArrayMessageReader cmsg(aligned_buf.align(msg)); + capnp::FlatArrayMessageReader cmsg(aligned_buf.align(msg.get())); cereal::Event::Reader event = cmsg.getRoot(); //Dont send if older than 1 second - if (nanos_since_boot() - event.getLogMonoTime() < 1e9) { - if (!fake_send) { - for (const auto& panda : pandas) { - panda->can_send(event.getSendcan()); - } + if ((nanos_since_boot() - event.getLogMonoTime() < 1e9) && !fake_send) { + for (const auto& panda : pandas) { + panda->can_send(event.getSendcan()); } } - - delete msg; } - - delete subscriber; - delete context; } void can_recv_thread(std::vector pandas) { - LOGD("start recv thread"); + util::set_thread_name("boardd_can_recv"); // can = 8006 PubMaster pm({"can"}); @@ -239,12 +246,7 @@ void can_recv_thread(std::vector pandas) { uint64_t next_frame_time = nanos_since_boot() + dt; std::vector raw_can_data; - while (!do_exit) { - if (!check_all_connected(pandas)){ - do_exit = true; - break; - } - + while (!do_exit && check_all_connected(pandas)) { bool comms_healthy = true; raw_can_data.clear(); for (const auto& panda : pandas) { @@ -315,7 +317,7 @@ bool send_panda_states(PubMaster *pm, const std::vector &pandas, bool s for (uint32_t i=0; i &pandas, bool s } #endif - // TODO: do we still need this? if (!panda->comms_healthy) { evt.setValid(false); } @@ -407,6 +408,8 @@ void send_peripheral_state(PubMaster *pm, Panda *panda) { } void panda_state_thread(PubMaster *pm, std::vector pandas, bool spoofing_started) { + util::set_thread_name("boardd_panda_state"); + Params params; Panda *peripheral_panda = pandas[0]; bool ignition_last = false; @@ -415,15 +418,12 @@ void panda_state_thread(PubMaster *pm, std::vector pandas, bool spoofin LOGD("start panda state thread"); // run at 2hz - while (!do_exit) { - if(!check_all_connected(pandas)) { - do_exit = true; - break; - } - + while (!do_exit && check_all_connected(pandas)) { + // send out peripheralState send_peripheral_state(pm, peripheral_panda); ignition = send_panda_states(pm, pandas, spoofing_started); + // TODO: make this check fast, currently takes 16ms // check if we have new pandas and are offroad if (!ignition && (pandas.size() != Panda::list().size())) { LOGW("Reconnecting to changed amount of pandas!"); @@ -431,7 +431,7 @@ void panda_state_thread(PubMaster *pm, std::vector pandas, bool spoofin break; } - // clear VIN, CarParams, and set new safety on car start + // clear ignition-based params and set new safety on car start if (ignition && !ignition_last) { params.clearAll(CLEAR_ON_IGNITION_ON); if (!safety_future.valid() || safety_future.wait_for(0ms) == std::future_status::ready) { @@ -454,7 +454,8 @@ void panda_state_thread(PubMaster *pm, std::vector pandas, bool spoofin void peripheral_control_thread(Panda *panda) { - LOGD("start peripheral control thread"); + util::set_thread_name("boardd_peripheral_control"); + SubMaster sm({"deviceState", "driverCameraState"}); uint64_t last_front_frame_t = 0; @@ -525,21 +526,8 @@ void peripheral_control_thread(Panda *panda) { } // Write to rtc once per minute when no ignition present - if ((panda->has_rtc) && !ignition && (cnt % 120 == 1)) { - // Write time to RTC if it looks reasonable - setenv("TZ","UTC",1); - struct tm sys_time = util::get_time(); - - if (util::time_valid(sys_time)) { - struct tm rtc_time = panda->get_rtc(); - double seconds = difftime(mktime(&rtc_time), mktime(&sys_time)); - - if (std::abs(seconds) > 1.1) { - panda->set_rtc(sys_time); - LOGW("Updating panda RTC. dt = %.2f System: %s RTC: %s", - seconds, get_time_str(sys_time).c_str(), get_time_str(rtc_time).c_str()); - } - } + if (!ignition && (cnt % 120 == 1)) { + sync_time(panda, SyncTimeDir::TO_PANDA); } } } @@ -552,10 +540,12 @@ static void pigeon_publish_raw(PubMaster &pm, const std::string &dat) { } void pigeon_thread(Panda *panda) { + util::set_thread_name("boardd_pigeon"); + PubMaster pm({"ubloxRaw"}); bool ignition_last = false; - Pigeon *pigeon = Hardware::TICI() ? Pigeon::connect("/dev/ttyHS0") : Pigeon::connect(panda); + std::unique_ptr pigeon(Hardware::TICI() ? Pigeon::connect("/dev/ttyHS0") : Pigeon::connect(panda)); std::unordered_map last_recv_time; std::unordered_map cls_max_dt = { @@ -623,8 +613,6 @@ void pigeon_thread(Panda *panda) { // 10ms - 100 Hz util::sleep_for(10); } - - delete pigeon; } int main(int argc, char *argv[]) { @@ -632,9 +620,9 @@ int main(int argc, char *argv[]) { if (!Hardware::PC()) { int err; - err = set_realtime_priority(54); + err = util::set_realtime_priority(54); assert(err == 0); - err = set_core_affinity({Hardware::TICI() ? 4 : 3}); + err = util::set_core_affinity({Hardware::TICI() ? 4 : 3}); assert(err == 0); } diff --git a/selfdrive/boardd/panda.cc b/selfdrive/boardd/panda.cc index 78b123f69..751f735ca 100644 --- a/selfdrive/boardd/panda.cc +++ b/selfdrive/boardd/panda.cc @@ -352,7 +352,7 @@ void Panda::set_data_speed_kbps(uint16_t bus, uint16_t speed) { usb_write(0xf9, bus, (speed * 10)); } -uint8_t Panda::len_to_dlc(uint8_t len) { +static uint8_t len_to_dlc(uint8_t len) { if (len <= 8) { return len; } @@ -363,114 +363,98 @@ uint8_t Panda::len_to_dlc(uint8_t len) { } } +static void write_packet(uint8_t *dest, int *write_pos, const uint8_t *src, size_t size) { + for (int i = 0, &pos = *write_pos; i < size; ++i, ++pos) { + // Insert counter every 64 bytes (first byte of 64 bytes USB packet) + if (pos % USBPACKET_MAX_SIZE == 0) { + dest[pos] = pos / USBPACKET_MAX_SIZE; + pos++; + } + dest[pos] = src[i]; + } +} + +void Panda::pack_can_buffer(const capnp::List::Reader &can_data_list, + std::function write_func) { + int32_t pos = 0; + uint8_t send_buf[2 * USB_TX_SOFT_LIMIT]; + + for (auto cmsg : can_data_list) { + // check if the message is intended for this panda + uint8_t bus = cmsg.getSrc(); + if (bus < bus_offset || bus >= (bus_offset + PANDA_BUS_CNT)) { + continue; + } + auto can_data = cmsg.getDat(); + uint8_t data_len_code = len_to_dlc(can_data.size()); + assert(can_data.size() <= ((hw_type == cereal::PandaState::PandaType::RED_PANDA) ? 64 : 8)); + assert(can_data.size() == dlc_to_len[data_len_code]); + + can_header header; + header.addr = cmsg.getAddress(); + header.extended = (cmsg.getAddress() >= 0x800) ? 1 : 0; + header.data_len_code = data_len_code; + header.bus = bus - bus_offset; + + write_packet(send_buf, &pos, (uint8_t *)&header, sizeof(can_header)); + write_packet(send_buf, &pos, (uint8_t *)can_data.begin(), can_data.size()); + if (pos >= USB_TX_SOFT_LIMIT) { + write_func(send_buf, pos); + pos = 0; + } + } + + // send remaining packets + if (pos > 0) write_func(send_buf, pos); +} + void Panda::can_send(capnp::List::Reader can_data_list) { - if (send.size() < (can_data_list.size() * CANPACKET_MAX_SIZE)) { - send.resize(can_data_list.size() * CANPACKET_MAX_SIZE); - } - - int msg_count = 0; - while (msg_count < can_data_list.size()) { - uint32_t pos = 0; - while (pos < USB_TX_SOFT_LIMIT) { - if (msg_count == can_data_list.size()) { break; } - auto cmsg = can_data_list[msg_count]; - - // check if the message is intended for this panda - uint8_t bus = cmsg.getSrc(); - if (bus < bus_offset || bus >= (bus_offset + PANDA_BUS_CNT)) { - msg_count++; - continue; - } - auto can_data = cmsg.getDat(); - uint8_t data_len_code = len_to_dlc(can_data.size()); - assert(can_data.size() <= (hw_type == cereal::PandaState::PandaType::RED_PANDA) ? 64 : 8); - assert(can_data.size() == dlc_to_len[data_len_code]); - - can_header header; - header.addr = cmsg.getAddress(); - header.extended = (cmsg.getAddress() >= 0x800) ? 1 : 0; - header.data_len_code = data_len_code; - header.bus = bus - bus_offset; - memcpy(&send[pos], &header, CANPACKET_HEAD_SIZE); - memcpy(&send[pos+CANPACKET_HEAD_SIZE], can_data.begin(), can_data.size()); - - pos += CANPACKET_HEAD_SIZE + dlc_to_len[data_len_code]; - msg_count++; - } - - if (pos > 0) { // Helps not to spam with ZLP - // Counter needs to be inserted every 64 bytes (first byte of 64 bytes USB packet) - uint8_t counter = 0; - uint8_t to_write[USB_TX_SOFT_LIMIT+128]; - int ptr = 0; - for (int i = 0; i < pos; i += 63) { - to_write[ptr] = counter; - int copy_size = ((pos - i) < 63) ? (pos - i) : 63; - memcpy(&to_write[ptr+1], &(send.data()[i]) , copy_size); - ptr += copy_size + 1; - counter++; - } - usb_bulk_write(3, to_write, ptr, 5); - } - } + pack_can_buffer(can_data_list, [=](uint8_t* data, size_t size) { + usb_bulk_write(3, data, size, 5); + }); } bool Panda::can_receive(std::vector& out_vec) { uint8_t data[RECV_SIZE]; int recv = usb_bulk_read(0x81, (uint8_t*)data, RECV_SIZE); - - // Not sure if this can happen - if (recv < 0) recv = 0; - - if (recv == RECV_SIZE) { - LOGW("Receive buffer full"); - } - if (!comms_healthy) { return false; } + if (recv == RECV_SIZE) { + LOGW("Panda receive buffer full"); + } - static uint8_t tail[CANPACKET_MAX_SIZE]; - uint8_t tail_size = 0; - uint8_t counter = 0; - for (int i = 0; i < recv; i += USBPACKET_MAX_SIZE) { - // Check for counter every 64 bytes (length of USB packet) - if (counter != data[i]) { + return (recv <= 0) ? true : unpack_can_buffer(data, recv, out_vec); +} + +bool Panda::unpack_can_buffer(uint8_t *data, int size, std::vector &out_vec) { + recv_buf.clear(); + for (int i = 0; i < size; i += USBPACKET_MAX_SIZE) { + if (data[i] != i / USBPACKET_MAX_SIZE) { LOGE("CAN: MALFORMED USB RECV PACKET"); - break; + comms_healthy = false; + return false; } - counter++; - uint8_t chunk_len = ((recv - i) > USBPACKET_MAX_SIZE) ? 63 : (recv - i - 1); // as 1 is always reserved for counter - uint8_t chunk[USBPACKET_MAX_SIZE + CANPACKET_MAX_SIZE]; - memcpy(chunk, tail, tail_size); - memcpy(&chunk[tail_size], &data[i+1], chunk_len); - chunk_len += tail_size; - tail_size = 0; - uint8_t pos = 0; - while (pos < chunk_len) { - uint8_t data_len = dlc_to_len[(chunk[pos] >> 4)]; - uint8_t pckt_len = CANPACKET_HEAD_SIZE + data_len; - if (pckt_len <= (chunk_len - pos)) { - can_header header; - memcpy(&header, &chunk[pos], CANPACKET_HEAD_SIZE); + int chunk_len = std::min(USBPACKET_MAX_SIZE, (size - i)); + recv_buf.insert(recv_buf.end(), &data[i + 1], &data[i + chunk_len]); + } - can_frame &canData = out_vec.emplace_back(); - canData.busTime = 0; - canData.address = header.addr; - canData.src = header.bus + bus_offset; + int pos = 0; + while (pos < recv_buf.size()) { + can_header header; + memcpy(&header, &recv_buf[pos], CANPACKET_HEAD_SIZE); - if (header.rejected) { canData.src += CANPACKET_REJECTED; } - if (header.returned) { canData.src += CANPACKET_RETURNED; } - canData.dat.assign((char*)&chunk[pos+CANPACKET_HEAD_SIZE], data_len); + can_frame &canData = out_vec.emplace_back(); + canData.busTime = 0; + canData.address = header.addr; + canData.src = header.bus + bus_offset; + if (header.rejected) { canData.src += CANPACKET_REJECTED; } + if (header.returned) { canData.src += CANPACKET_RETURNED; } - pos += pckt_len; - } else { - // Keep partial CAN packet until next USB packet - tail_size = (chunk_len - pos); - memcpy(tail, &chunk[pos], tail_size); - break; - } - } + const uint8_t data_len = dlc_to_len[header.data_len_code]; + canData.dat.assign((char *)&recv_buf[pos + CANPACKET_HEAD_SIZE], data_len); + + pos += CANPACKET_HEAD_SIZE + data_len; } return true; } diff --git a/selfdrive/boardd/panda.h b/selfdrive/boardd/panda.h index 4be9454cf..fe6918948 100644 --- a/selfdrive/boardd/panda.h +++ b/selfdrive/boardd/panda.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -17,7 +18,7 @@ #define PANDA_BUS_CNT 4 #define RECV_SIZE (0x4000U) #define USB_TX_SOFT_LIMIT (0x100U) -#define USBPACKET_MAX_SIZE (0x40U) +#define USBPACKET_MAX_SIZE (0x40) #define CANPACKET_HEAD_SIZE 5U #define CANPACKET_MAX_SIZE 72U #define CANPACKET_REJECTED (0xC0U) @@ -68,7 +69,7 @@ class Panda { libusb_context *ctx = NULL; libusb_device_handle *dev_handle = NULL; std::mutex usb_lock; - std::vector send; + std::vector recv_buf; void handle_usb_issue(int err, const char func[]); void cleanup(); @@ -110,7 +111,13 @@ class Panda { void send_heartbeat(); void set_can_speed_kbps(uint16_t bus, uint16_t speed); void set_data_speed_kbps(uint16_t bus, uint16_t speed); - uint8_t len_to_dlc(uint8_t len); void can_send(capnp::List::Reader can_data_list); bool can_receive(std::vector& out_vec); + +protected: + // for unit tests + Panda(uint32_t bus_offset) : bus_offset(bus_offset) {} + void pack_can_buffer(const capnp::List::Reader &can_data_list, + std::function write_func); + bool unpack_can_buffer(uint8_t *data, int size, std::vector &out_vec); }; diff --git a/selfdrive/boardd/pigeon.cc b/selfdrive/boardd/pigeon.cc index b8735cc3b..912f4b03e 100644 --- a/selfdrive/boardd/pigeon.cc +++ b/selfdrive/boardd/pigeon.cc @@ -24,8 +24,8 @@ extern ExitHandler do_exit; const std::string ack = "\xb5\x62\x05\x01\x02\x00"; const std::string nack = "\xb5\x62\x05\x00\x02\x00"; -const std::string sos_ack = "\xb5\x62\x09\x14\x08\x00\x02\x00\x00\x00\x01\x00\x00\x00"; -const std::string sos_nack = "\xb5\x62\x09\x14\x08\x00\x02\x00\x00\x00\x00\x00\x00\x00"; +const std::string sos_save_ack = "\xb5\x62\x09\x14\x08\x00\x02\x00\x00\x00\x01\x00\x00\x00"; +const std::string sos_save_nack = "\xb5\x62\x09\x14\x08\x00\x02\x00\x00\x00\x00\x00\x00\x00"; Pigeon * Pigeon::connect(Panda * p) { PandaPigeon * pigeon = new PandaPigeon(); @@ -72,6 +72,25 @@ bool Pigeon::send_with_ack(const std::string &cmd) { return wait_for_ack(); } +sos_restore_response Pigeon::wait_for_backup_restore_status(int timeout_ms) { + std::string s; + const double start_t = millis_since_boot(); + while (!do_exit) { + s += receive(); + + size_t position = s.find("\xb5\x62\x09\x14\x08\x00\x03"); + if (position != std::string::npos && s.size() >= (position + 11)) { + return static_cast(s[position + 10]); + } else if (s.size() > 0x1000 || ((millis_since_boot() - start_t) > timeout_ms)) { + LOGE("No backup restore response from ublox"); + return error; + } + + util::sleep_for(1); // Allow other threads to be scheduled + } + return error; +} + void Pigeon::init() { for (int i = 0; i < 10; i++) { if (do_exit) return; @@ -118,6 +137,22 @@ void Pigeon::init() { if (!send_with_ack("\xB5\x62\x06\x01\x03\x00\x0A\x09\x01\x1E\x70"s)) continue; if (!send_with_ack("\xB5\x62\x06\x01\x03\x00\x0A\x0B\x01\x20\x74"s)) continue; + // check the backup restore status + send("\xB5\x62\x09\x14\x00\x00\x1D\x60"s); + sos_restore_response restore_status = wait_for_backup_restore_status(); + switch(restore_status) { + case restored: + LOGW("almanac backup restored"); + // clear the backup + send_with_ack("\xB5\x62\x06\x01\x03\x00\x0A\x0B\x01\x20\x74"s); + break; + case no_backup: + LOGW("no almanac backup found"); + break; + default: + LOGE("failed to restore almanac backup, status: %d", restore_status); + } + auto time = util::get_time(); if (util::time_valid(time)) { LOGW("Sending current time to ublox"); @@ -139,7 +174,7 @@ void Pigeon::stop() { // Store almanac in flash send("\xB5\x62\x09\x14\x04\x00\x00\x00\x00\x00\x21\xEC"s); - if (wait_for_ack(sos_ack, sos_nack)) { + if (wait_for_ack(sos_save_ack, sos_save_nack)) { LOGW("Done storing almanac"); } else { LOGE("Error storing almanac"); diff --git a/selfdrive/boardd/pigeon.h b/selfdrive/boardd/pigeon.h index 0a82bcdd9..c9ea4739d 100644 --- a/selfdrive/boardd/pigeon.h +++ b/selfdrive/boardd/pigeon.h @@ -7,6 +7,14 @@ #include "selfdrive/boardd/panda.h" +enum sos_restore_response : int { + unknown = 0, + failed = 1, + restored = 2, + no_backup = 3, + error = -1 +}; + class Pigeon { public: static Pigeon* connect(Panda * p); @@ -18,6 +26,7 @@ class Pigeon { bool wait_for_ack(); bool wait_for_ack(const std::string &ack, const std::string &nack, int timeout_ms = 1000); bool send_with_ack(const std::string &cmd); + sos_restore_response wait_for_backup_restore_status(int timeout_ms = 1000); virtual void set_baud(int baud) = 0; virtual void send(const std::string &s) = 0; virtual std::string receive() = 0; diff --git a/selfdrive/camerad/SConscript b/selfdrive/camerad/SConscript index d018704d6..85bc756bc 100644 --- a/selfdrive/camerad/SConscript +++ b/selfdrive/camerad/SConscript @@ -1,4 +1,4 @@ -Import('env', 'arch', 'cereal', 'messaging', 'common', 'gpucommon', 'visionipc', 'USE_WEBCAM', 'USE_FRAME_STREAM') +Import('env', 'arch', 'cereal', 'messaging', 'common', 'gpucommon', 'visionipc', 'USE_WEBCAM') libs = ['m', 'pthread', common, 'jpeg', 'OpenCL', 'yuv', cereal, messaging, 'zmq', 'capnp', 'kj', visionipc, gpucommon] @@ -18,15 +18,12 @@ else: env.Append(CFLAGS = '-DWEBCAM') env.Append(CPPPATH = ['/usr/include/opencv4', '/usr/local/include/opencv4']) else: - if USE_FRAME_STREAM: - cameras = ['cameras/camera_frame_stream.cc'] - else: - libs += ['avutil', 'avcodec', 'avformat', 'swscale', 'bz2', 'ssl', 'curl', 'crypto'] - # TODO: import replay_lib from root SConstruct - cameras = ['cameras/camera_replay.cc', - env.Object('camera-util', '#/selfdrive/ui/replay/util.cc'), - env.Object('camera-framereader', '#/selfdrive/ui/replay/framereader.cc'), - env.Object('camera-filereader', '#/selfdrive/ui/replay/filereader.cc')] + libs += ['avutil', 'avcodec', 'avformat', 'bz2', 'ssl', 'curl', 'crypto'] + # TODO: import replay_lib from root SConstruct + cameras = ['cameras/camera_replay.cc', + env.Object('camera-util', '#/selfdrive/ui/replay/util.cc'), + env.Object('camera-framereader', '#/selfdrive/ui/replay/framereader.cc'), + env.Object('camera-filereader', '#/selfdrive/ui/replay/filereader.cc')] if arch == "Darwin": del libs[libs.index('OpenCL')] diff --git a/selfdrive/camerad/cameras/camera_common.cc b/selfdrive/camerad/cameras/camera_common.cc index 4db6eaa12..6dcbf18c4 100644 --- a/selfdrive/camerad/cameras/camera_common.cc +++ b/selfdrive/camerad/cameras/camera_common.cc @@ -28,8 +28,6 @@ #include "selfdrive/camerad/cameras/camera_replay.h" #endif -const int YUV_COUNT = 100; - class Debayer { public: Debayer(cl_device_id device_id, cl_context context, const CameraBuf *b, const CameraState *s) { @@ -109,7 +107,7 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, CameraState *s, vipc_server->create_buffers(rgb_type, UI_BUF_COUNT, true, rgb_width, rgb_height); rgb_stride = vipc_server->get_buffer(rgb_type)->stride; - vipc_server->create_buffers(yuv_type, YUV_COUNT, false, rgb_width, rgb_height); + vipc_server->create_buffers(yuv_type, YUV_BUFFER_COUNT, false, rgb_width, rgb_height); if (ci->bayer) { debayer = new Debayer(device_id, context, this, s); @@ -353,7 +351,7 @@ void *processing_thread(MultiCameraState *cameras, CameraState *cs, process_thre } else { thread_name = "WideRoadCamera"; } - set_thread_name(thread_name); + util::set_thread_name(thread_name); uint32_t cnt = 0; while (!do_exit) { @@ -410,11 +408,11 @@ static void driver_cam_auto_exposure(CameraState *c, SubMaster &sm) { camera_autoexposure(c, set_exposure_target(b, rect.x1, rect.x2, rect.x_skip, rect.y1, rect.y2, rect.y_skip)); } -void common_process_driver_camera(SubMaster *sm, PubMaster *pm, CameraState *c, int cnt) { +void common_process_driver_camera(MultiCameraState *s, CameraState *c, int cnt) { int j = Hardware::TICI() ? 1 : 3; if (cnt % j == 0) { - sm->update(0); - driver_cam_auto_exposure(c, *sm); + s->sm->update(0); + driver_cam_auto_exposure(c, *(s->sm)); } MessageBuilder msg; auto framed = msg.initEvent().initDriverCameraState(); @@ -423,5 +421,5 @@ void common_process_driver_camera(SubMaster *sm, PubMaster *pm, CameraState *c, if (env_send_driver) { framed.setImage(get_frame_image(&c->buf)); } - pm->send("driverCameraState", msg); + s->pm->send("driverCameraState", msg); } diff --git a/selfdrive/camerad/cameras/camera_common.h b/selfdrive/camerad/cameras/camera_common.h index e994fc220..9394f9075 100644 --- a/selfdrive/camerad/cameras/camera_common.h +++ b/selfdrive/camerad/cameras/camera_common.h @@ -14,6 +14,7 @@ #include "selfdrive/common/queue.h" #include "selfdrive/common/swaglog.h" #include "selfdrive/common/visionimg.h" +#include "selfdrive/hardware/hw.h" #define CAMERA_ID_IMX298 0 #define CAMERA_ID_IMX179 1 @@ -26,7 +27,8 @@ #define CAMERA_ID_AR0231 8 #define CAMERA_ID_MAX 9 -#define UI_BUF_COUNT 4 +const int UI_BUF_COUNT = 4; +const int YUV_BUFFER_COUNT = Hardware::EON() ? 100 : 40; enum CameraType { RoadCam = 0, @@ -49,23 +51,6 @@ typedef struct CameraInfo { bool hdr; } CameraInfo; -typedef struct LogCameraInfo { - CameraType type; - const char* filename; - const char* frame_packet_name; - const char* encode_idx_name; - VisionStreamType stream_type; - int frame_width, frame_height; - int fps; - int bitrate; - bool is_h265; - bool downscale; - bool has_qcamera; - bool trigger_rotate; - bool enable; - bool record; -} LogCameraInfo; - typedef struct FrameMetadata { uint32_t frame_id; unsigned int frame_length; @@ -138,7 +123,7 @@ void fill_frame_data(cereal::FrameData::Builder &framed, const FrameMetadata &fr kj::Array get_frame_image(const CameraBuf *b); float set_exposure_target(const CameraBuf *b, int x_start, int x_end, int x_skip, int y_start, int y_end, int y_skip); std::thread start_process_thread(MultiCameraState *cameras, CameraState *cs, process_thread_cb callback); -void common_process_driver_camera(SubMaster *sm, PubMaster *pm, CameraState *c, int cnt); +void common_process_driver_camera(MultiCameraState *s, CameraState *c, int cnt); void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_id, cl_context ctx); void cameras_open(MultiCameraState *s); diff --git a/selfdrive/camerad/cameras/camera_frame_stream.cc b/selfdrive/camerad/cameras/camera_frame_stream.cc deleted file mode 100644 index 8e6c69e36..000000000 --- a/selfdrive/camerad/cameras/camera_frame_stream.cc +++ /dev/null @@ -1,92 +0,0 @@ -#include "selfdrive/camerad/cameras/camera_frame_stream.h" - -#include -#include - -#include - -#include "cereal/messaging/messaging.h" -#include "selfdrive/common/util.h" - -#define FRAME_WIDTH 1164 -#define FRAME_HEIGHT 874 - -extern ExitHandler do_exit; - -namespace { - -// TODO: make this more generic -CameraInfo cameras_supported[CAMERA_ID_MAX] = { - [CAMERA_ID_IMX298] = { - .frame_width = FRAME_WIDTH, - .frame_height = FRAME_HEIGHT, - .frame_stride = FRAME_WIDTH*3, - .bayer = false, - .bayer_flip = false, - }, - [CAMERA_ID_OV8865] = { - .frame_width = 1632, - .frame_height = 1224, - .frame_stride = 2040, // seems right - .bayer = false, - .bayer_flip = 3, - .hdr = false - }, -}; - -void camera_init(VisionIpcServer * v, CameraState *s, int camera_id, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType rgb_type, VisionStreamType yuv_type) { - assert(camera_id < std::size(cameras_supported)); - s->ci = cameras_supported[camera_id]; - assert(s->ci.frame_width != 0); - - s->camera_num = camera_id; - s->fps = fps; - s->buf.init(device_id, ctx, s, v, FRAME_BUF_COUNT, rgb_type, yuv_type); -} - -void run_frame_stream(CameraState &camera, const char* frame_pkt) { - SubMaster sm({frame_pkt}); - - size_t buf_idx = 0; - while (!do_exit) { - sm.update(1000); - if(sm.updated(frame_pkt)) { - auto msg = static_cast(sm[frame_pkt]); - auto frame = msg.get(frame_pkt).as(); - camera.buf.camera_bufs_metadata[buf_idx] = { - .frame_id = frame.get("frameId").as(), - .timestamp_eof = frame.get("timestampEof").as(), - .timestamp_sof = frame.get("timestampSof").as(), - }; - - cl_command_queue q = camera.buf.camera_bufs[buf_idx].copy_q; - cl_mem yuv_cl = camera.buf.camera_bufs[buf_idx].buf_cl; - - auto image = frame.get("image").as(); - clEnqueueWriteBuffer(q, yuv_cl, CL_TRUE, 0, image.size(), image.begin(), 0, NULL, NULL); - camera.buf.queue(buf_idx); - buf_idx = (buf_idx + 1) % FRAME_BUF_COUNT; - } - } -} - -} // namespace - -void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_id, cl_context ctx) { - camera_init(v, &s->road_cam, CAMERA_ID_IMX298, 20, device_id, ctx, - VISION_STREAM_RGB_BACK, VISION_STREAM_YUV_BACK); - camera_init(v, &s->driver_cam, CAMERA_ID_OV8865, 10, device_id, ctx, - VISION_STREAM_RGB_FRONT, VISION_STREAM_YUV_FRONT); -} - -void cameras_open(MultiCameraState *s) {} -void cameras_close(MultiCameraState *s) {} -void camera_autoexposure(CameraState *s, float grey_frac) {} -void process_road_camera(MultiCameraState *s, CameraState *c, int cnt) {} - -void cameras_run(MultiCameraState *s) { - std::thread t = start_process_thread(s, &s->road_cam, process_road_camera); - set_thread_name("frame_streaming"); - run_frame_stream(s->road_cam, "roadCameraState"); - t.join(); -} diff --git a/selfdrive/camerad/cameras/camera_frame_stream.h b/selfdrive/camerad/cameras/camera_frame_stream.h deleted file mode 100644 index 0d4d8cfb4..000000000 --- a/selfdrive/camerad/cameras/camera_frame_stream.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#define CL_USE_DEPRECATED_OPENCL_1_2_APIS -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include "camera_common.h" - -#define FRAME_BUF_COUNT 16 - -typedef struct CameraState { - int camera_num; - CameraInfo ci; - - int fps; - float digital_gain; - - CameraBuf buf; -} CameraState; - -typedef struct MultiCameraState { - CameraState road_cam; - CameraState driver_cam; - - SubMaster *sm; - PubMaster *pm; -} MultiCameraState; diff --git a/selfdrive/camerad/cameras/camera_qcom.cc b/selfdrive/camerad/cameras/camera_qcom.cc index 33b5d00c9..06d58f2b0 100644 --- a/selfdrive/camerad/cameras/camera_qcom.cc +++ b/selfdrive/camerad/cameras/camera_qcom.cc @@ -211,12 +211,12 @@ void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_i /*fps*/ 20, #endif device_id, ctx, - VISION_STREAM_RGB_BACK, VISION_STREAM_YUV_BACK); + VISION_STREAM_RGB_BACK, VISION_STREAM_ROAD); camera_init(v, &s->driver_cam, CAMERA_ID_OV8865, 1, /*pixel_clock=*/72000000, /*line_length_pclk=*/1602, /*max_gain=*/510, 10, device_id, ctx, - VISION_STREAM_RGB_FRONT, VISION_STREAM_YUV_FRONT); + VISION_STREAM_RGB_FRONT, VISION_STREAM_DRIVER); s->sm = new SubMaster({"driverState"}); s->pm = new PubMaster({"roadCameraState", "driverCameraState", "thumbnail"}); @@ -227,7 +227,7 @@ void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_i s->stats_bufs[i].allocate(0xb80); } std::fill_n(s->lapres, std::size(s->lapres), 16160); - s->lap_conv = new LapConv(device_id, ctx, s->road_cam.buf.rgb_width, s->road_cam.buf.rgb_height, 3); + s->lap_conv = new LapConv(device_id, ctx, s->road_cam.buf.rgb_width, s->road_cam.buf.rgb_height, s->road_cam.buf.rgb_stride, 3); } static void set_exposure(CameraState *s, float exposure_frac, float gain_frac) { @@ -1045,7 +1045,7 @@ static void ops_thread(MultiCameraState *s) { CameraExpInfo road_cam_op; CameraExpInfo driver_cam_op; - set_thread_name("camera_settings"); + util::set_thread_name("camera_settings"); SubMaster sm({"sensorEvents"}); while(!do_exit) { road_cam_op = road_cam_exp.load(); @@ -1086,10 +1086,6 @@ static void setup_self_recover(CameraState *c, const uint16_t *lapres, size_t la c->self_recover.store(self_recover); } -void process_driver_camera(MultiCameraState *s, CameraState *c, int cnt) { - common_process_driver_camera(s->sm, s->pm, c, cnt); -} - // called by processing_thread void process_road_camera(MultiCameraState *s, CameraState *c, int cnt) { const CameraBuf *b = &c->buf; @@ -1121,7 +1117,7 @@ void cameras_run(MultiCameraState *s) { std::vector threads; threads.push_back(std::thread(ops_thread, s)); threads.push_back(start_process_thread(s, &s->road_cam, process_road_camera)); - threads.push_back(start_process_thread(s, &s->driver_cam, process_driver_camera)); + threads.push_back(start_process_thread(s, &s->driver_cam, common_process_driver_camera)); CameraState* cameras[2] = {&s->road_cam, &s->driver_cam}; diff --git a/selfdrive/camerad/cameras/camera_qcom2.cc b/selfdrive/camerad/cameras/camera_qcom2.cc index 03138e353..030bbe47e 100644 --- a/selfdrive/camerad/cameras/camera_qcom2.cc +++ b/selfdrive/camerad/cameras/camera_qcom2.cc @@ -709,13 +709,13 @@ static void camera_open(CameraState *s) { void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_id, cl_context ctx) { camera_init(s, v, &s->road_cam, CAMERA_ID_AR0231, 1, 20, device_id, ctx, - VISION_STREAM_RGB_BACK, VISION_STREAM_YUV_BACK); // swap left/right + VISION_STREAM_RGB_BACK, VISION_STREAM_ROAD); // swap left/right printf("road camera initted \n"); camera_init(s, v, &s->wide_road_cam, CAMERA_ID_AR0231, 0, 20, device_id, ctx, - VISION_STREAM_RGB_WIDE, VISION_STREAM_YUV_WIDE); + VISION_STREAM_RGB_WIDE, VISION_STREAM_WIDE_ROAD); printf("wide road camera initted \n"); camera_init(s, v, &s->driver_cam, CAMERA_ID_AR0231, 2, 20, device_id, ctx, - VISION_STREAM_RGB_FRONT, VISION_STREAM_YUV_FRONT); + VISION_STREAM_RGB_FRONT, VISION_STREAM_DRIVER); printf("driver camera initted \n"); s->sm = new SubMaster({"driverState"}); @@ -987,11 +987,6 @@ void camera_autoexposure(CameraState *s, float grey_frac) { set_camera_exposure(s, grey_frac); } - -void process_driver_camera(MultiCameraState *s, CameraState *c, int cnt) { - common_process_driver_camera(s->sm, s->pm, c, cnt); -} - // called by processing_thread void process_road_camera(MultiCameraState *s, CameraState *c, int cnt) { const CameraBuf *b = &c->buf; @@ -1016,7 +1011,7 @@ void cameras_run(MultiCameraState *s) { LOG("-- Starting threads"); std::vector threads; threads.push_back(start_process_thread(s, &s->road_cam, process_road_camera)); - threads.push_back(start_process_thread(s, &s->driver_cam, process_driver_camera)); + threads.push_back(start_process_thread(s, &s->driver_cam, common_process_driver_camera)); threads.push_back(start_process_thread(s, &s->wide_road_cam, process_road_camera)); // start devices diff --git a/selfdrive/camerad/cameras/camera_replay.cc b/selfdrive/camerad/cameras/camera_replay.cc index 6bc53d8c9..b5b2e6ad2 100644 --- a/selfdrive/camerad/cameras/camera_replay.cc +++ b/selfdrive/camerad/cameras/camera_replay.cc @@ -23,7 +23,7 @@ std::string get_url(std::string route_name, const std::string &camera, int segme } void camera_init(VisionIpcServer *v, CameraState *s, int camera_id, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType rgb_type, VisionStreamType yuv_type, const std::string &url) { - s->frame = new FrameReader(true); + s->frame = new FrameReader(); if (!s->frame->load(url)) { printf("failed to load stream from %s", url.c_str()); assert(0); @@ -67,12 +67,12 @@ void run_camera(CameraState *s) { } void road_camera_thread(CameraState *s) { - set_thread_name("replay_road_camera_thread"); + util::set_thread_name("replay_road_camera_thread"); run_camera(s); } // void driver_camera_thread(CameraState *s) { -// set_thread_name("replay_driver_camera_thread"); +// util::set_thread_name("replay_driver_camera_thread"); // run_camera(s); // } @@ -98,9 +98,9 @@ void process_road_camera(MultiCameraState *s, CameraState *c, int cnt) { void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_id, cl_context ctx) { camera_init(v, &s->road_cam, CAMERA_ID_LGC920, 20, device_id, ctx, - VISION_STREAM_RGB_BACK, VISION_STREAM_YUV_BACK, get_url(road_camera_route, "fcamera", 0)); + VISION_STREAM_RGB_BACK, VISION_STREAM_ROAD, get_url(road_camera_route, "fcamera", 0)); // camera_init(v, &s->driver_cam, CAMERA_ID_LGC615, 10, device_id, ctx, - // VISION_STREAM_RGB_FRONT, VISION_STREAM_YUV_FRONT, get_url(driver_camera_route, "dcamera", 0)); + // VISION_STREAM_RGB_FRONT, VISION_STREAM_DRIVER, get_url(driver_camera_route, "dcamera", 0)); s->pm = new PubMaster({"roadCameraState", "driverCameraState", "thumbnail"}); } diff --git a/selfdrive/camerad/imgproc/utils.cc b/selfdrive/camerad/imgproc/utils.cc index ad6452c6a..a88b8f4bb 100644 --- a/selfdrive/camerad/imgproc/utils.cc +++ b/selfdrive/camerad/imgproc/utils.cc @@ -55,8 +55,8 @@ static cl_program build_conv_program(cl_device_id device_id, cl_context context, return cl_program_from_file(context, device_id, "imgproc/conv.cl", args); } -LapConv::LapConv(cl_device_id device_id, cl_context ctx, int rgb_width, int rgb_height, int filter_size) - : width(rgb_width / NUM_SEGMENTS_X), height(rgb_height / NUM_SEGMENTS_Y), +LapConv::LapConv(cl_device_id device_id, cl_context ctx, int rgb_width, int rgb_height, int rgb_stride, int filter_size) + : width(rgb_width / NUM_SEGMENTS_X), height(rgb_height / NUM_SEGMENTS_Y), rgb_stride(rgb_stride), roi_buf(width * height * 3), result_buf(width * height) { prg = build_conv_program(device_id, ctx, width, height, filter_size); @@ -81,9 +81,9 @@ uint16_t LapConv::Update(cl_command_queue q, const uint8_t *rgb_buf, const int r const int x_offset = ROI_X_MIN + roi_id % (ROI_X_MAX - ROI_X_MIN + 1); const int y_offset = ROI_Y_MIN + roi_id / (ROI_X_MAX - ROI_X_MIN + 1); - const uint8_t *rgb_offset = rgb_buf + y_offset * height * FULL_STRIDE_X * 3 + x_offset * width * 3; + const uint8_t *rgb_offset = rgb_buf + y_offset * height * rgb_stride + x_offset * width * 3; for (int i = 0; i < height; ++i) { - memcpy(&roi_buf[i * width * 3], &rgb_offset[i * FULL_STRIDE_X * 3], width * 3); + memcpy(&roi_buf[i * width * 3], &rgb_offset[i * rgb_stride], width * 3); } constexpr int local_mem_size = (CONV_LOCAL_WORKSIZE + 2 * (3 / 2)) * (CONV_LOCAL_WORKSIZE + 2 * (3 / 2)) * (3 * sizeof(uint8_t)); diff --git a/selfdrive/camerad/imgproc/utils.h b/selfdrive/camerad/imgproc/utils.h index 72baa5d53..b735975b3 100644 --- a/selfdrive/camerad/imgproc/utils.h +++ b/selfdrive/camerad/imgproc/utils.h @@ -16,16 +16,11 @@ #define LM_THRESH 120 #define LM_PREC_THRESH 0.9 // 90 perc is blur - -// only apply to QCOM -#define FULL_STRIDE_X 1280 -#define FULL_STRIDE_Y 896 - #define CONV_LOCAL_WORKSIZE 16 class LapConv { public: - LapConv(cl_device_id device_id, cl_context ctx, int rgb_width, int rgb_height, int filter_size); + LapConv(cl_device_id device_id, cl_context ctx, int rgb_width, int rgb_height, int rgb_stride, int filter_size); ~LapConv(); uint16_t Update(cl_command_queue q, const uint8_t *rgb_buf, const int roi_id); @@ -34,6 +29,7 @@ private: cl_program prg; cl_kernel krnl; const int width, height; + const int rgb_stride; std::vector roi_buf; std::vector result_buf; }; diff --git a/selfdrive/camerad/main.cc b/selfdrive/camerad/main.cc index 1db713044..f06bd341d 100644 --- a/selfdrive/camerad/main.cc +++ b/selfdrive/camerad/main.cc @@ -46,9 +46,9 @@ void party(cl_device_id device_id, cl_context context) { int main(int argc, char *argv[]) { if (!Hardware::PC()) { int ret; - ret = set_realtime_priority(53); + ret = util::set_realtime_priority(53); assert(ret == 0); - ret = set_core_affinity({Hardware::EON() ? 2 : 6}); + ret = util::set_core_affinity({Hardware::EON() ? 2 : 6}); assert(ret == 0 || Params().getBool("IsOffroad")); // failure ok while offroad due to offlining cores } diff --git a/selfdrive/camerad/snapshot/snapshot.py b/selfdrive/camerad/snapshot/snapshot.py index 889e1579b..506064de3 100755 --- a/selfdrive/camerad/snapshot/snapshot.py +++ b/selfdrive/camerad/snapshot/snapshot.py @@ -37,7 +37,7 @@ def extract_image(buf, w, h, stride): def rois_in_focus(lapres: List[float]) -> float: - return sum([1. / len(lapres) for sharpness in lapres if sharpness >= LM_THRESH]) + return sum(1. / len(lapres) for sharpness in lapres if sharpness >= LM_THRESH) def get_snapshots(frame="roadCameraState", front_frame="driverCameraState", focus_perc_threshold=0.): diff --git a/selfdrive/car/__init__.py b/selfdrive/car/__init__.py index ddb20fe54..408e0d075 100644 --- a/selfdrive/car/__init__.py +++ b/selfdrive/car/__init__.py @@ -99,7 +99,7 @@ def crc8_pedal(data): return crc -def create_gas_command(packer, gas_amount, idx): +def create_gas_interceptor_command(packer, gas_amount, idx): # Common gas pedal msg generator enable = gas_amount > 0.001 diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index 980b207fa..786b00fa2 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -1,7 +1,7 @@ import os from common.params import Params from common.basedir import BASEDIR -from selfdrive.version import comma_remote, tested_branch +from selfdrive.version import get_comma_remote, get_tested_branch from selfdrive.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars from selfdrive.car.vin import get_vin, VIN_UNKNOWN from selfdrive.car.fw_versions import get_fw_versions, match_fw_to_car @@ -14,7 +14,7 @@ EventName = car.CarEvent.EventName def get_startup_event(car_recognized, controller_available, fw_seen): - if comma_remote and tested_branch: + if get_comma_remote() and get_tested_branch(): event = EventName.startup else: event = EventName.startupMaster diff --git a/selfdrive/car/chrysler/carstate.py b/selfdrive/car/chrysler/carstate.py index ba85a89c3..ab3125b01 100644 --- a/selfdrive/car/chrysler/carstate.py +++ b/selfdrive/car/chrysler/carstate.py @@ -31,10 +31,13 @@ class CarState(CarStateBase): ret.espDisabled = (cp.vl["TRACTION_BUTTON"]["TRACTION_OFF"] == 1) - ret.wheelSpeeds.fl = cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FL"] - ret.wheelSpeeds.rr = cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RR"] - ret.wheelSpeeds.rl = cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RL"] - ret.wheelSpeeds.fr = cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FR"] + ret.wheelSpeeds = self.get_wheel_speeds( + cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FL"], + cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FR"], + cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RL"], + cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RR"], + unit=1, + ) ret.vEgoRaw = (cp.vl["SPEED_1"]["SPEED_LEFT"] + cp.vl["SPEED_1"]["SPEED_RIGHT"]) / 2. ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) ret.standstill = not ret.vEgoRaw > 0.001 diff --git a/selfdrive/car/ford/carstate.py b/selfdrive/car/ford/carstate.py index 100d47240..a621ab16a 100644 --- a/selfdrive/car/ford/carstate.py +++ b/selfdrive/car/ford/carstate.py @@ -10,10 +10,14 @@ WHEEL_RADIUS = 0.33 class CarState(CarStateBase): def update(self, cp): ret = car.CarState.new_message() - ret.wheelSpeeds.rr = cp.vl["WheelSpeed_CG1"]["WhlRr_W_Meas"] * WHEEL_RADIUS - ret.wheelSpeeds.rl = cp.vl["WheelSpeed_CG1"]["WhlRl_W_Meas"] * WHEEL_RADIUS - ret.wheelSpeeds.fr = cp.vl["WheelSpeed_CG1"]["WhlFr_W_Meas"] * WHEEL_RADIUS - ret.wheelSpeeds.fl = cp.vl["WheelSpeed_CG1"]["WhlFl_W_Meas"] * WHEEL_RADIUS + + ret.wheelSpeeds = self.get_wheel_speeds( + cp.vl["WheelSpeed_CG1"]["WhlFl_W_Meas"], + cp.vl["WheelSpeed_CG1"]["WhlFr_W_Meas"], + cp.vl["WheelSpeed_CG1"]["WhlRl_W_Meas"], + cp.vl["WheelSpeed_CG1"]["WhlRr_W_Meas"], + unit=WHEEL_RADIUS, + ) ret.vEgoRaw = mean([ret.wheelSpeeds.rr, ret.wheelSpeeds.rl, ret.wheelSpeeds.fr, ret.wheelSpeeds.fl]) ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) ret.standstill = not ret.vEgoRaw > 0.001 diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index 315c398ab..7c6cc6d2d 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -9,12 +9,6 @@ MAX_ANGLE = 87. # make sure we never command the extremes (0xfff) which cause l class CAR: FUSION = "FORD FUSION 2018" -FINGERPRINTS = { - CAR.FUSION: [{ - 71: 8, 74: 8, 75: 8, 76: 8, 90: 8, 92: 8, 93: 8, 118: 8, 119: 8, 120: 8, 125: 8, 129: 8, 130: 8, 131: 8, 132: 8, 133: 8, 145: 8, 146: 8, 357: 8, 359: 8, 360: 8, 361: 8, 376: 8, 390: 8, 391: 8, 392: 8, 394: 8, 512: 8, 514: 8, 516: 8, 531: 8, 532: 8, 534: 8, 535: 8, 560: 8, 578: 8, 604: 8, 613: 8, 673: 8, 827: 8, 848: 8, 934: 8, 935: 8, 936: 8, 947: 8, 963: 8, 970: 8, 972: 8, 973: 8, 984: 8, 992: 8, 994: 8, 997: 8, 998: 8, 1003: 8, 1034: 8, 1045: 8, 1046: 8, 1053: 8, 1054: 8, 1058: 8, 1059: 8, 1068: 8, 1072: 8, 1073: 8, 1082: 8, 1107: 8, 1108: 8, 1109: 8, 1110: 8, 1200: 8, 1427: 8, 1430: 8, 1438: 8, 1459: 8 - }], -} - DBC = { CAR.FUSION: dbc_dict('ford_fusion_2018_pt', 'ford_fusion_2018_adas'), } diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index 2c451b7f4..5d982d6a8 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -47,7 +47,7 @@ HYUNDAI_VERSION_REQUEST_LONG = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) HYUNDAI_VERSION_REQUEST_MULTI = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_SPARE_PART_NUMBER) + \ p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION) + \ - p16(0xf100) + p16(0xf100) HYUNDAI_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) @@ -240,7 +240,7 @@ def match_fw_to_car_exact(fw_versions_dict): addr = ecu[1:] found_version = fw_versions_dict.get(addr, None) ESSENTIAL_ECUS = [Ecu.engine, Ecu.eps, Ecu.esp, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.vsa] - if ecu_type == Ecu.esp and candidate in [TOYOTA.RAV4, TOYOTA.COROLLA, TOYOTA.HIGHLANDER] and found_version is None: + if ecu_type == Ecu.esp and candidate in [TOYOTA.RAV4, TOYOTA.COROLLA, TOYOTA.HIGHLANDER, TOYOTA.SIENNA, TOYOTA.LEXUS_IS] and found_version is None: continue # On some Toyota models, the engine can show on two different addresses diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py index 00f25c5fd..e4d644834 100644 --- a/selfdrive/car/gm/carstate.py +++ b/selfdrive/car/gm/carstate.py @@ -1,6 +1,5 @@ from cereal import car from common.numpy_fast import mean -from selfdrive.config import Conversions as CV from opendbc.can.can_define import CANDefine from opendbc.can.parser import CANParser from selfdrive.car.interfaces import CarStateBase @@ -21,10 +20,12 @@ class CarState(CarStateBase): self.prev_cruise_buttons = self.cruise_buttons self.cruise_buttons = pt_cp.vl["ASCMSteeringButton"]["ACCButtons"] - ret.wheelSpeeds.fl = pt_cp.vl["EBCMWheelSpdFront"]["FLWheelSpd"] * CV.KPH_TO_MS - ret.wheelSpeeds.fr = pt_cp.vl["EBCMWheelSpdFront"]["FRWheelSpd"] * CV.KPH_TO_MS - ret.wheelSpeeds.rl = pt_cp.vl["EBCMWheelSpdRear"]["RLWheelSpd"] * CV.KPH_TO_MS - ret.wheelSpeeds.rr = pt_cp.vl["EBCMWheelSpdRear"]["RRWheelSpd"] * CV.KPH_TO_MS + ret.wheelSpeeds = self.get_wheel_speeds( + pt_cp.vl["EBCMWheelSpdFront"]["FLWheelSpd"], + pt_cp.vl["EBCMWheelSpdFront"]["FRWheelSpd"], + pt_cp.vl["EBCMWheelSpdRear"]["RLWheelSpd"], + pt_cp.vl["EBCMWheelSpdRear"]["RRWheelSpd"], + ) ret.vEgoRaw = mean([ret.wheelSpeeds.fl, ret.wheelSpeeds.fr, ret.wheelSpeeds.rl, ret.wheelSpeeds.rr]) ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) ret.standstill = ret.vEgoRaw < 0.01 diff --git a/selfdrive/car/honda/carcontroller.py b/selfdrive/car/honda/carcontroller.py index 1e0a35fdf..e1488eda2 100644 --- a/selfdrive/car/honda/carcontroller.py +++ b/selfdrive/car/honda/carcontroller.py @@ -3,7 +3,7 @@ from cereal import car from common.realtime import DT_CTRL from selfdrive.controls.lib.drive_helpers import rate_limit from common.numpy_fast import clip, interp -from selfdrive.car import create_gas_command +from selfdrive.car import create_gas_interceptor_command from selfdrive.car.honda import hondacan from selfdrive.car.honda.values import CruiseButtons, VISUAL_HUD, HONDA_BOSCH, HONDA_NIDEC_ALT_PCM_ACCEL, CarControllerParams from opendbc.can.packer import CANPacker @@ -236,7 +236,7 @@ class CarController(): apply_gas = clip(gas_mult * (gas - brake + wind_brake*3/4), 0., 1.) else: apply_gas = 0.0 - can_sends.append(create_gas_command(self.packer, apply_gas, idx)) + can_sends.append(create_gas_interceptor_command(self.packer, apply_gas, idx)) hud = HUDData(int(pcm_accel), int(round(hud_v_cruise)), hud_car, hud_lanes, fcw_display, acc_alert, steer_required) @@ -244,6 +244,6 @@ class CarController(): # Send dashboard UI commands. if (frame % 10) == 0: idx = (frame//10) % 4 - can_sends.extend(hondacan.create_ui_commands(self.packer, pcm_speed, hud, CS.CP.carFingerprint, CS.is_metric, idx, CS.CP.openpilotLongitudinalControl, CS.stock_hud)) + can_sends.extend(hondacan.create_ui_commands(self.packer, CS.CP, pcm_speed, hud, CS.is_metric, idx, CS.stock_hud)) return can_sends diff --git a/selfdrive/car/honda/carstate.py b/selfdrive/car/honda/carstate.py index 8bf3ac58f..12a4ae4c2 100644 --- a/selfdrive/car/honda/carstate.py +++ b/selfdrive/car/honda/carstate.py @@ -5,7 +5,7 @@ from opendbc.can.can_define import CANDefine from opendbc.can.parser import CANParser from selfdrive.config import Conversions as CV from selfdrive.car.interfaces import CarStateBase -from selfdrive.car.honda.values import CAR, DBC, STEER_THRESHOLD, SPEED_FACTOR, HONDA_BOSCH, HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_ALT_BRAKE_SIGNAL +from selfdrive.car.honda.values import CAR, DBC, STEER_THRESHOLD, HONDA_BOSCH, HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_ALT_BRAKE_SIGNAL TransmissionType = car.CarParams.TransmissionType @@ -213,16 +213,17 @@ class CarState(CarStateBase): self.brake_error = cp.vl["STANDSTILL"]["BRAKE_ERROR_1"] or cp.vl["STANDSTILL"]["BRAKE_ERROR_2"] ret.espDisabled = cp.vl["VSA_STATUS"]["ESP_DISABLED"] != 0 - speed_factor = SPEED_FACTOR.get(self.CP.carFingerprint, 1.) - ret.wheelSpeeds.fl = cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FL"] * CV.KPH_TO_MS * speed_factor - ret.wheelSpeeds.fr = cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FR"] * CV.KPH_TO_MS * speed_factor - ret.wheelSpeeds.rl = cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RL"] * CV.KPH_TO_MS * speed_factor - ret.wheelSpeeds.rr = cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RR"] * CV.KPH_TO_MS * speed_factor - v_wheel = (ret.wheelSpeeds.fl + ret.wheelSpeeds.fr + ret.wheelSpeeds.rl + ret.wheelSpeeds.rr)/4. + ret.wheelSpeeds = self.get_wheel_speeds( + cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FL"], + cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FR"], + cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RL"], + cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RR"], + ) + v_wheel = (ret.wheelSpeeds.fl + ret.wheelSpeeds.fr + ret.wheelSpeeds.rl + ret.wheelSpeeds.rr) / 4.0 # blend in transmission speed at low speed, since it has more low speed accuracy v_weight = interp(v_wheel, v_weight_bp, v_weight_v) - ret.vEgoRaw = (1. - v_weight) * cp.vl["ENGINE_DATA"]["XMISSION_SPEED"] * CV.KPH_TO_MS * speed_factor + v_weight * v_wheel + ret.vEgoRaw = (1. - v_weight) * cp.vl["ENGINE_DATA"]["XMISSION_SPEED"] * CV.KPH_TO_MS * self.CP.wheelSpeedFactor + v_weight * v_wheel ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) ret.steeringAngleDeg = cp.vl["STEERING_SENSORS"]["STEER_ANGLE"] diff --git a/selfdrive/car/honda/hondacan.py b/selfdrive/car/honda/hondacan.py index 5bcf59c25..7fcafe67f 100644 --- a/selfdrive/car/honda/hondacan.py +++ b/selfdrive/car/honda/hondacan.py @@ -1,4 +1,4 @@ -from selfdrive.car.honda.values import HONDA_BOSCH, CAR, CarControllerParams +from selfdrive.car.honda.values import HondaFlags, HONDA_BOSCH, CAR, CarControllerParams from selfdrive.config import Conversions as CV # CAN bus layout with relay @@ -98,14 +98,14 @@ def create_bosch_supplemental_1(packer, car_fingerprint, idx): return packer.make_can_msg("BOSCH_SUPPLEMENTAL_1", bus, values, idx) -def create_ui_commands(packer, pcm_speed, hud, car_fingerprint, is_metric, idx, openpilot_longitudinal_control, stock_hud): +def create_ui_commands(packer, CP, pcm_speed, hud, is_metric, idx, stock_hud): commands = [] - bus_pt = get_pt_bus(car_fingerprint) - radar_disabled = car_fingerprint in HONDA_BOSCH and openpilot_longitudinal_control - bus_lkas = get_lkas_cmd_bus(car_fingerprint, radar_disabled) + bus_pt = get_pt_bus(CP.carFingerprint) + radar_disabled = CP.carFingerprint in HONDA_BOSCH and CP.openpilotLongitudinalControl + bus_lkas = get_lkas_cmd_bus(CP.carFingerprint, radar_disabled) - if openpilot_longitudinal_control: - if car_fingerprint in HONDA_BOSCH: + if CP.openpilotLongitudinalControl: + if CP.carFingerprint in HONDA_BOSCH: acc_hud_values = { 'CRUISE_SPEED': hud.v_cruise, 'ENABLE_MINI_CAR': 1, @@ -142,16 +142,24 @@ def create_ui_commands(packer, pcm_speed, hud, car_fingerprint, is_metric, idx, 'SOLID_LANES': hud.lanes, 'BEEP': 0, } - commands.append(packer.make_can_msg('LKAS_HUD', bus_lkas, lkas_hud_values, idx)) - if radar_disabled and car_fingerprint in HONDA_BOSCH: + if not (CP.flags & HondaFlags.BOSCH_EXT_HUD): + lkas_hud_values['SET_ME_X48'] = 0x48 + + if CP.flags & HondaFlags.BOSCH_EXT_HUD and not CP.openpilotLongitudinalControl: + commands.append(packer.make_can_msg('LKAS_HUD_A', bus_lkas, lkas_hud_values, idx)) + commands.append(packer.make_can_msg('LKAS_HUD_B', bus_lkas, lkas_hud_values, idx)) + else: + commands.append(packer.make_can_msg('LKAS_HUD', bus_lkas, lkas_hud_values, idx)) + + if radar_disabled and CP.carFingerprint in HONDA_BOSCH: radar_hud_values = { 'CMBS_OFF': 0x01, 'SET_TO_1': 0x01, } commands.append(packer.make_can_msg('RADAR_HUD', bus_pt, radar_hud_values, idx)) - if car_fingerprint == CAR.CIVIC_BOSCH: + if CP.carFingerprint == CAR.CIVIC_BOSCH: commands.append(packer.make_can_msg("LEGACY_BRAKE_COMMAND", bus_pt, {}, idx)) return commands diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index 4aa6ec381..03074b875 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -3,7 +3,7 @@ from cereal import car from panda import Panda from common.numpy_fast import interp from common.params import Params -from selfdrive.car.honda.values import CarControllerParams, CruiseButtons, CAR, HONDA_BOSCH, HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_ALT_BRAKE_SIGNAL +from selfdrive.car.honda.values import CarControllerParams, CruiseButtons, HondaFlags, CAR, HONDA_BOSCH, HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_ALT_BRAKE_SIGNAL from selfdrive.car import STD_CARGO_KG, CivicParams, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.disable_ecu import disable_ecu @@ -33,7 +33,7 @@ class CarInterface(CarInterfaceBase): ret.carName = "honda" if candidate in HONDA_BOSCH: - ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hondaBoschHarness)] + ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hondaBosch)] ret.radarOffCan = True # Disable the radar and let openpilot control longitudinal @@ -52,6 +52,10 @@ class CarInterface(CarInterfaceBase): if candidate == CAR.CRV_5G: ret.enableBsm = 0x12f8bfa7 in fingerprint[0] + # Detect Bosch cars with new HUD msgs + if any(0x33DA in f for f in fingerprint.values()): + ret.flags |= HondaFlags.BOSCH_EXT_HUD.value + # Accord 1.5T CVT has different gearbox message if candidate == CAR.ACCORD and 0x191 in fingerprint[1]: ret.transmissionType = TransmissionType.cvt @@ -143,6 +147,7 @@ class CarInterface(CarInterfaceBase): ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 1000], [0, 1000]] # TODO: determine if there is a dead zone at the top end tire_stiffness_factor = 0.444 ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]] + ret.wheelSpeedFactor = 1.025 elif candidate == CAR.CRV_5G: stop_and_go = True @@ -160,6 +165,7 @@ class CarInterface(CarInterfaceBase): ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 3840], [0, 3840]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.64], [0.192]] tire_stiffness_factor = 0.677 + ret.wheelSpeedFactor = 1.025 elif candidate == CAR.CRV_HYBRID: stop_and_go = True @@ -170,6 +176,7 @@ class CarInterface(CarInterfaceBase): ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end tire_stiffness_factor = 0.677 ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]] + ret.wheelSpeedFactor = 1.025 elif candidate == CAR.FIT: stop_and_go = False @@ -201,6 +208,7 @@ class CarInterface(CarInterfaceBase): ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] tire_stiffness_factor = 0.5 ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.16], [0.025]] + ret.wheelSpeedFactor = 1.025 elif candidate == CAR.ACURA_RDX: stop_and_go = False diff --git a/selfdrive/car/honda/values.py b/selfdrive/car/honda/values.py index 97e0ded66..083a52bb8 100644 --- a/selfdrive/car/honda/values.py +++ b/selfdrive/car/honda/values.py @@ -1,3 +1,5 @@ +from enum import IntFlag + from cereal import car from selfdrive.car import dbc_dict @@ -37,6 +39,11 @@ class CarControllerParams(): self.STEER_LOOKUP_V = [v * -1 for v in CP.lateralParams.torqueV][1:][::-1] + list(CP.lateralParams.torqueV) +class HondaFlags(IntFlag): + # Bosch models with alternate set of LKAS_HUD messages + BOSCH_EXT_HUD = 1 + + # Car button codes class CruiseButtons: RES_ACCEL = 4 @@ -84,6 +91,7 @@ class CAR: FW_VERSIONS = { CAR.ACCORD: { (Ecu.programmedFuelInjection, 0x18da10f1, None): [ + b'37805-6A0-8720\x00\x00', b'37805-6A0-9520\x00\x00', b'37805-6A0-9620\x00\x00', b'37805-6A0-9720\x00\x00', @@ -95,7 +103,9 @@ FW_VERSIONS = { b'37805-6A0-A750\x00\x00', b'37805-6A0-A840\x00\x00', b'37805-6A0-A850\x00\x00', + b'37805-6A0-AF30\x00\x00', b'37805-6A0-AG30\x00\x00', + b'37805-6B2-C520\x00\x00', b'37805-6A0-C540\x00\x00', b'37805-6A1-H650\x00\x00', b'37805-6B2-A550\x00\x00', @@ -121,6 +131,7 @@ FW_VERSIONS = { b'28101-6A7-A410\x00\x00', b'28101-6A7-A510\x00\x00', b'28101-6A7-A610\x00\x00', + b'28101-6A7-A710\x00\x00', b'28101-6A9-H140\x00\x00', b'28101-6A9-H420\x00\x00', b'28102-6B8-A560\x00\x00', @@ -150,6 +161,7 @@ FW_VERSIONS = { b'57114-TVA-C050\x00\x00', b'57114-TVA-C060\x00\x00', b'57114-TVA-C530\x00\x00', + b'57114-TVA-E520\x00\x00', b'57114-TVE-H250\x00\x00', ], (Ecu.eps, 0x18da30f1, None): [ @@ -165,6 +177,7 @@ FW_VERSIONS = { ], (Ecu.unknown, 0x18da3af1, None): [ b'39390-TVA-A020\x00\x00', + b'39390-TVA-A120\x00\x00', ], (Ecu.srs, 0x18da53f1, None): [ b'77959-TBX-H230\x00\x00', @@ -183,10 +196,12 @@ FW_VERSIONS = { b'78109-TVA-A120\x00\x00', b'78109-TVA-A210\x00\x00', b'78109-TVA-A220\x00\x00', + b'78109-TVA-A230\x00\x00', b'78109-TVA-A310\x00\x00', b'78109-TVA-C010\x00\x00', b'78109-TVA-L010\x00\x00', b'78109-TVA-L210\x00\x00', + b'78109-TVA-R310\x00\x00', b'78109-TVC-A010\x00\x00', b'78109-TVC-A020\x00\x00', b'78109-TVC-A030\x00\x00', @@ -194,6 +209,7 @@ FW_VERSIONS = { b'78109-TVC-A130\x00\x00', b'78109-TVC-A210\x00\x00', b'78109-TVC-A220\x00\x00', + b'78109-TVC-A230\x00\x00', b'78109-TVC-C010\x00\x00', b'78109-TVC-C110\x00\x00', b'78109-TVC-L010\x00\x00', @@ -205,6 +221,7 @@ FW_VERSIONS = { ], (Ecu.hud, 0x18da61f1, None): [ b'78209-TVA-A010\x00\x00', + b'78209-TVA-A110\x00\x00', ], (Ecu.fwdRadar, 0x18dab0f1, None): [ b'36802-TBX-H140\x00\x00', @@ -253,6 +270,7 @@ FW_VERSIONS = { b'78109-TWA-A030\x00\x00', b'78109-TWA-A110\x00\x00', b'78109-TWA-A120\x00\x00', + b'78109-TWA-A130\x00\x00', b'78109-TWA-A210\x00\x00', b'78109-TWA-A220\x00\x00', b'78109-TWA-A230\x00\x00', @@ -271,8 +289,8 @@ FW_VERSIONS = { b'36161-TWA-A330\x00\x00', ], (Ecu.fwdRadar, 0x18dab0f1, None): [ - b'36802-TWA-A080\x00\x00', b'36802-TWA-A070\x00\x00', + b'36802-TWA-A080\x00\x00', b'36802-TWA-A330\x00\x00', ], (Ecu.eps, 0x18da30f1, None): [ @@ -382,6 +400,7 @@ FW_VERSIONS = { (Ecu.programmedFuelInjection, 0x18da10f1, None): [ b'37805-5AA-A940\x00\x00', b'37805-5AA-A950\x00\x00', + b'37805-5AA-C950\x00\x00', b'37805-5AA-L940\x00\x00', b'37805-5AA-L950\x00\x00', b'37805-5AG-Z910\x00\x00', @@ -397,7 +416,9 @@ FW_VERSIONS = { b'37805-5AN-AG20\x00\x00', b'37805-5AN-AH20\x00\x00', b'37805-5AN-AJ30\x00\x00', + b'37805-5AN-AK10\x00\x00', b'37805-5AN-AK20\x00\x00', + b'37805-5AN-AR10\x00\x00', b'37805-5AN-AR20\x00\x00', b'37805-5AN-CH20\x00\x00', b'37805-5AN-E630\x00\x00', @@ -492,7 +513,9 @@ FW_VERSIONS = { b'78109-TBA-C340\x00\x00', b'78109-TBA-C910\x00\x00', b'78109-TBC-A740\x00\x00', + b'78109-TBC-C540\x00\x00', b'78109-TBG-A110\x00\x00', + b'78109-TBH-A710\x00\x00', b'78109-TEG-A720\x00\x00', b'78109-TFJ-G020\x00\x00', b'78109-TGG-9020\x00\x00', @@ -686,6 +709,7 @@ FW_VERSIONS = { b'78109-TLA-A120\x00\x00', b'78109-TLA-A210\x00\x00', b'78109-TLA-A220\x00\x00', + b'78109-TLA-C020\x00\x00', b'78109-TLA-C110\x00\x00', b'78109-TLA-C210\x00\x00', b'78109-TLA-C310\x00\x00', @@ -720,6 +744,7 @@ FW_VERSIONS = { b'36161-TMC-Q040\x00\x00', b'36161-TNY-A020\x00\x00', b'36161-TNY-A030\x00\x00', + b'36161-TNY-A040\x00\x00', ], (Ecu.srs, 0x18da53f1, None): [ b'77959-TLA-A240\x00\x00', @@ -1025,6 +1050,7 @@ FW_VERSIONS = { b'36161-TG8-A830\x00\x00', b'36161-TGS-A130\x00\x00', b'36161-TGT-A030\x00\x00', + b'36161-TGT-A130\x00\x00', ], (Ecu.srs, 0x18da53f1, None): [ b'77959-TG7-A210\x00\x00', @@ -1040,7 +1066,9 @@ FW_VERSIONS = { b'78109-TG7-AP10\x00\x00', b'78109-TG7-AP20\x00\x00', b'78109-TG7-AS20\x00\x00', + b'78109-TG7-AT20\x00\x00', b'78109-TG7-AU20\x00\x00', + b'78109-TG7-AX20\x00\x00', b'78109-TG7-DJ10\x00\x00', b'78109-TG7-YK20\x00\x00', b'78109-TG8-AJ10\x00\x00', @@ -1049,7 +1077,7 @@ FW_VERSIONS = { b'78109-TGS-AK20\x00\x00', b'78109-TGS-AP20\x00\x00', b'78109-TGT-AJ20\x00\x00', - b'78109-TG7-AT20\x00\x00', + b'78109-TGT-AK30\x00\x00', ], (Ecu.vsa, 0x18da28f1, None): [ b'57114-TG7-A630\x00\x00', @@ -1147,6 +1175,7 @@ FW_VERSIONS = { b'28102-5YK-A711\x00\x00', b'28102-5YL-A620\x00\x00', b'28102-5YL-A700\x00\x00', + b'28102-5YL-A711\x00\x00', ], (Ecu.combinationMeter, 0x18da60f1, None): [ b'78109-TJB-A140\x00\x00', @@ -1155,6 +1184,7 @@ FW_VERSIONS = { b'78109-TJB-AB10\x00\x00', b'78109-TJB-AD10\x00\x00', b'78109-TJB-AF10\x00\x00', + b'78109-TJB-AR10\x00\x00', b'78109-TJB-AS10\000\000', b'78109-TJB-AU10\x00\x00', b'78109-TJB-AW10\x00\x00', @@ -1271,6 +1301,7 @@ FW_VERSIONS = { ], (Ecu.combinationMeter, 0x18da60f1, None): [ b'78109-THX-A110\x00\x00', + b'78109-THX-A120\x00\x00', b'78109-THX-A210\x00\x00', b'78109-THX-A220\x00\x00', b'78109-THX-C220\x00\x00', @@ -1354,18 +1385,9 @@ STEER_THRESHOLD = { CAR.CRV_EU: 400, } -# TODO: is this real? -SPEED_FACTOR = { - # default is 1, overrides go here - CAR.CRV: 1.025, - CAR.CRV_5G: 1.025, - CAR.CRV_EU: 1.025, - CAR.CRV_HYBRID: 1.025, - CAR.HRV: 1.025, -} - HONDA_NIDEC_ALT_PCM_ACCEL = set([CAR.ODYSSEY]) HONDA_NIDEC_ALT_SCM_MESSAGES = set([CAR.ACURA_ILX, CAR.ACURA_RDX, CAR.CRV, CAR.CRV_EU, CAR.FIT, CAR.FREED, CAR.HRV, CAR.ODYSSEY_CHN, CAR.PILOT, CAR.PILOT_2019, CAR.PASSPORT, CAR.RIDGELINE]) -HONDA_BOSCH = set([CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_5G, CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E]) +HONDA_BOSCH = set([CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_5G, + CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E]) HONDA_BOSCH_ALT_BRAKE_SIGNAL = set([CAR.ACCORD, CAR.CRV_5G, CAR.ACURA_RDX_3G]) diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index 4db775bea..e889f24fc 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -28,10 +28,12 @@ class CarState(CarStateBase): ret.seatbeltUnlatched = cp.vl["CGW1"]["CF_Gway_DrvSeatBeltSw"] == 0 - ret.wheelSpeeds.fl = cp.vl["WHL_SPD11"]["WHL_SPD_FL"] * CV.KPH_TO_MS - ret.wheelSpeeds.fr = cp.vl["WHL_SPD11"]["WHL_SPD_FR"] * CV.KPH_TO_MS - ret.wheelSpeeds.rl = cp.vl["WHL_SPD11"]["WHL_SPD_RL"] * CV.KPH_TO_MS - ret.wheelSpeeds.rr = cp.vl["WHL_SPD11"]["WHL_SPD_RR"] * CV.KPH_TO_MS + ret.wheelSpeeds = self.get_wheel_speeds( + cp.vl["WHL_SPD11"]["WHL_SPD_FL"], + cp.vl["WHL_SPD11"]["WHL_SPD_FR"], + cp.vl["WHL_SPD11"]["WHL_SPD_RL"], + cp.vl["WHL_SPD11"]["WHL_SPD_RR"], + ) ret.vEgoRaw = (ret.wheelSpeeds.fl + ret.wheelSpeeds.fr + ret.wheelSpeeds.rl + ret.wheelSpeeds.rr) / 4. ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) diff --git a/selfdrive/car/hyundai/hyundaican.py b/selfdrive/car/hyundai/hyundaican.py index c8130fd72..50031231b 100644 --- a/selfdrive/car/hyundai/hyundaican.py +++ b/selfdrive/car/hyundai/hyundaican.py @@ -102,7 +102,7 @@ def create_acc_commands(packer, enabled, accel, jerk, idx, lead_visible, set_spe "CR_VSM_Alive": idx % 0xF, } scc12_dat = packer.make_can_msg("SCC12", 0, scc12_values)[2] - scc12_values["CR_VSM_ChkSum"] = 0x10 - sum([sum(divmod(i, 16)) for i in scc12_dat]) % 0x10 + scc12_values["CR_VSM_ChkSum"] = 0x10 - sum(sum(divmod(i, 16)) for i in scc12_dat) % 0x10 commands.append(packer.make_can_msg("SCC12", 0, scc12_values)) @@ -127,7 +127,7 @@ def create_acc_commands(packer, enabled, accel, jerk, idx, lead_visible, set_spe "FCA_Status": 1, # AEB disabled } fca11_dat = packer.make_can_msg("FCA11", 0, fca11_values)[2] - fca11_values["CR_FCA_ChkSum"] = 0x10 - sum([sum(divmod(i, 16)) for i in fca11_dat]) % 0x10 + fca11_values["CR_FCA_ChkSum"] = 0x10 - sum(sum(divmod(i, 16)) for i in fca11_dat) % 0x10 commands.append(packer.make_can_msg("FCA11", 0, fca11_values)) return commands diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index ddcd00ba0..df2e10dbe 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -283,6 +283,7 @@ FW_VERSIONS = { b'\xf1\x82DNBWN5TMDCXXXG2E', b'\xf1\x82DNCVN5GMCCXXXF0A', b'\xf1\x82DNCVN5GMCCXXXG2B', + b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82DNDWN5TMDCXXXJ1A', b'\xf1\x87391162M003', b'\xf1\x87391162M013', b'\xf1\x87391162M023', @@ -327,6 +328,8 @@ FW_VERSIONS = { b'\xf1\x87SAKFBA2926554GJ2VefVww\x87xwwwww\x88\x87xww\x87wTo\xfb\xffvUo\xff\x8d\x16\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', b'\xf1\x87SAKFBA3030524GJ2UVugww\x97yx\x88\x87\x88vw\x87gww\x87wto\xf9\xfffUo\xff\xa2\x0c\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', b'\xf1\x87SAKFBA3356084GJ2\x86fvgUUuWgw\x86www\x87wffvf\xb6\xcf\xfc\xffeUO\xff\x12\x19\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x87SAKFBA3474944GJ2ffvgwwwwg\x88\x86x\x88\x88\x98\x88ffvfeo\xfa\xff\x86fo\xff\t\xae\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x87SAKFBA3475714GJ2Vfvgvg\x96yx\x88\x97\x88ww\x87ww\x88\x87xs_\xfb\xffvUO\xff\x0f\xff\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', b'\xf1\x87SALDBA3510954GJ3ww\x87xUUuWx\x88\x87\x88\x87w\x88wvfwfc_\xf9\xff\x98wO\xffl\xe0\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', b'\xf1\x87SALDBA3573534GJ3\x89\x98\x89\x88EUuWgwvwwwwww\x88\x87xTo\xfa\xff\x86f\x7f\xffo\x0e\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', b'\xf1\x87SALDBA3601464GJ3\x88\x88\x88\x88ffvggwvwvw\x87gww\x87wvo\xfb\xff\x98\x88\x7f\xffjJ\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', @@ -336,21 +339,27 @@ FW_VERSIONS = { b'\xf1\x87SALDBA4525334GJ3\x89\x99\x99\x99fevWh\x88\x86\x88fwvgw\x88\x87xfo\xfa\xffuDo\xff\xd1>\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', b'\xf1\x87SALDBA4626804GJ3wwww\x88\x87\x88xx\x88\x87\x88wwgw\x88\x88\x98\x88\x95_\xf9\xffuDo\xff|\xe7\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', b'\xf1\x87SALDBA4803224GJ3wwwwwvwg\x88\x88\x98\x88wwww\x87\x88\x88xu\x9f\xfc\xff\x87f\x8f\xff\xea\xea\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', + b'\xf1\x87SALDBA6212564GJ3\x87wwwUTuGg\x88\x86xx\x88\x87\x88\x87\x88\x98xu?\xf9\xff\x97f\x7f\xff\xb8\n\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', b'\xf1\x87SALDBA6347404GJ3wwwwff\x86hx\x88\x97\x88\x88\x88\x88\x88vfgf\x88?\xfc\xff\x86Uo\xff\xec/\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', b'\xf1\x87SALDBA6901634GJ3UUuWVeVUww\x87wwwwwvUge\x86/\xfb\xff\xbb\x99\x7f\xff]2\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', b'\xf1\x87SALDBA7077724GJ3\x98\x88\x88\x88ww\x97ygwvwww\x87ww\x88\x87x\x87_\xfd\xff\xba\x99o\xff\x99\x01\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', b'\xf1\x87SALFBA3525114GJ2wvwgvfvggw\x86wffvffw\x86g\x85_\xf9\xff\xa8wo\xffv\xcd\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', b'\xf1\x87SALFBA3624024GJ2\x88\x88\x88\x88wv\x87hx\x88\x97\x88x\x88\x97\x88ww\x87w\x86o\xfa\xffvU\x7f\xff\xd1\xec\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', b'\xf1\x87SALFBA3960824GJ2wwwwff\x86hffvfffffvfwfg_\xf9\xff\xa9\x88\x8f\xffb\x99\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x87SALFBA4011074GJ2fgvwwv\x87hw\x88\x87xww\x87wwfgvu_\xfa\xffefo\xff\x87\xc0\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', b'\xf1\x87SALFBA4121304GJ2x\x87xwff\x86hwwwwww\x87wwwww\x84_\xfc\xff\x98\x88\x9f\xffi\xa6\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', b'\xf1\x87SALFBA4195874GJ2EVugvf\x86hgwvwww\x87wgw\x86wc_\xfb\xff\x98\x88\x8f\xff\xe23\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x87SALFBA4625294GJ2eVefeUeVx\x88\x97\x88wwwwwwww\xa7o\xfb\xffvw\x9f\xff\xee.\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x87SALFBA4728774GJ2vfvg\x87vwgww\x87ww\x88\x97xww\x87w\x86_\xfb\xffeD?\xffk0\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', b'\xf1\x87SALFBA5129064GJ2vfvgwv\x87hx\x88\x87\x88ww\x87www\x87wd_\xfa\xffvfo\xff\x1d\x00\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', b'\xf1\x87SALFBA5454914GJ2\x98\x88\x88\x88\x87vwgx\x88\x87\x88xww\x87ffvf\xa7\x7f\xf9\xff\xa8w\x7f\xff\x1b\x90\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x87SALFBA5987784GJ2UVugDDtGx\x88\x87\x88w\x88\x87xwwwwd/\xfb\xff\x97fO\xff\xb0h\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', b'\xf1\x87SALFBA5987864GJ2fgvwUUuWgwvw\x87wxwwwww\x84/\xfc\xff\x97w\x7f\xff\xdf\x1d\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', b'\xf1\x87SALFBA6337644GJ2vgvwwv\x87hgffvwwwwwwww\x85O\xfa\xff\xa7w\x7f\xff\xc5\xfc\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', b'\xf1\x87SALFBA6802004GJ2UUuWUUuWgw\x86www\x87www\x87w\x96?\xf9\xff\xa9\x88\x7f\xff\x9fK\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', b'\xf1\x87SALFBA6892284GJ233S5\x87w\x87xx\x88\x87\x88vwwgww\x87w\x84?\xfb\xff\x98\x88\x8f\xff*\x9e\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', b'\xf1\x87SALFBA7005534GJ2eUuWfg\x86xxww\x87x\x88\x87\x88\x88w\x88\x87\x87O\xfc\xffuUO\xff\xa3k\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB1\xe3\xc10\xa1', + b'\xf1\x87SALFBA7152454GJ2gvwgFf\x86hx\x88\x87\x88vfWfffffd?\xfa\xff\xba\x88o\xff,\xcf\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB1\xe3\xc10\xa1', b'\xf1\x87SALFBA7485034GJ2ww\x87xww\x87xfwvgwwwwvfgf\xa5/\xfc\xff\xa9w_\xff40\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', b'\xf1\x87SAMDBA7743924GJ3wwwwww\x87xgwvw\x88\x88\x88\x88wwww\x85_\xfa\xff\x86f\x7f\xff0\x9d\xf1\x89HT6WAD10A1\xf1\x82SDN8G25NB2\x00\x00\x00\x00\x00\x00', b'\xf1\x87SAMDBA7817334GJ3Vgvwvfvgww\x87wwwwwwfgv\x97O\xfd\xff\x88\x88o\xff\x8e\xeb\xf1\x89HT6WAD10A1\xf1\x82SDN8G25NB2\x00\x00\x00\x00\x00\x00', @@ -529,6 +538,7 @@ FW_VERSIONS = { b'\xf1\x00LX2_ SCC FHCUP 1.00 1.00 99110-S8110 ', b'\xf1\x00LX2_ SCC FHCUP 1.00 1.04 99110-S8100 ', b'\xf1\x00LX2_ SCC FHCUP 1.00 1.05 99110-S8100 ', + b'\xf1\x00ON__ FCA FHCUP 1.00 1.02 99110-S9100 ', ], (Ecu.esp, 0x7d1, None): [ b'\xf1\x00LX ESC \x01 103\x19\t\x10 58910-S8360', @@ -595,11 +605,18 @@ FW_VERSIONS = { b'\xf1\x87LDLVBN757883KF37\x98\x87xw\x98\x87\x88xy\xaa\xb7\x9ag\x88\x96x\x89\x99\xa8\x99e\x7f\xf6\xff\xa9\x88o\xff5\x15\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB4\xd6\xe8\xd7\xa6', b'\xf1\x87LDMVBN778156KF37\x87vWe\xa9\x99\x99\x99y\x99\xb7\x99\x99\x99\x99\x99x\x99\x97\x89\xa8\x7f\xf8\xffwf\x7f\xff\x82_\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB4\xd6\xe8\xd7\xa6', b'\xf1\x87LDMVBN780576KF37\x98\x87hv\x97x\x97\x89x\x99\xa7\x89\x88\x99\x98\x89w\x88\x97x\x98\x7f\xf7\xff\xba\x88\x8f\xff\x1e0\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB4\xd6\xe8\xd7\xa6', + b'\xf1\x87LDMVBN783485KF37\x87www\x87vwgy\x99\xa7\x99\x99\x99\xa9\x99Vw\x95g\x89_\xf6\xff\xa9w_\xff\xc5\xd6\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB4\xd6\xe8\xd7\xa6', b'\xf1\x87LDMVBN811844KF37\x87vwgvfffx\x99\xa7\x89Vw\x95gg\x88\xa6xe\x8f\xf6\xff\x97wO\xff\t\x80\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB4\xd6\xe8\xd7\xa6', b'\xf1\x87LDMVBN830601KF37\xa7www\xa8\x87xwx\x99\xa7\x89Uw\x85Ww\x88\x97x\x88o\xf6\xff\x8a\xaa\x7f\xff\xe2:\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB4\xd6\xe8\xd7\xa6', b'\xf1\x87LDMVBN848789KF37\x87w\x87x\x87w\x87xy\x99\xb7\x99\x87\x88\x98x\x88\x99\xa8\x89\x87\x7f\xf6\xfffUo\xff\xe3!\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', b'\xf1\x87LDMVBN851595KF37\x97wgvvfffx\x99\xb7\x89\x88\x99\x98\x89\x87\x88\x98x\x99\x7f\xf7\xff\x97w\x7f\xff@\xf3\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', + b'\xf1\x87LDMVBN873175KF26\xa8\x88\x88\x88vfVex\x99\xb7\x89\x88\x99\x98\x89x\x88\x97\x88f\x7f\xf7\xff\xbb\xaa\x8f\xff,\x04\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', b'\xf1\x87LDMVBN879401KF26veVU\xa8\x88\x88\x88g\x88\xa6xVw\x95gx\x88\xa7\x88v\x8f\xf9\xff\xdd\xbb\xbf\xff\xb3\x99\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', + b'\xf1\x87LDMVBN881314KF37\xa8\x88h\x86\x97www\x89\x99\xa8\x99w\x88\x97xx\x99\xa7\x89\xca\x7f\xf8\xff\xba\x99\x8f\xff\xd8v\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', + b'\xf1\x87LDMVBN888651KF37\xa9\x99\x89\x98vfff\x88\x99\x98\x89w\x99\xa7y\x88\x88\x98\x88D\x8f\xf9\xff\xcb\x99\x8f\xff\xa5\x1e\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', + b'\xf1\x87LDMVBN889419KF37\xa9\x99y\x97\x87w\x87xx\x88\x97\x88w\x88\x97x\x88\x99\x98\x89e\x9f\xf9\xffeUo\xff\x901\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', + b'\xf1\x87LDMVBN895969KF37vefV\x87vgfx\x99\xa7\x89\x99\x99\xb9\x99f\x88\x96he_\xf7\xffxwo\xff\x14\xf9\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', + b'\xf1\x87LDMVBN899222KF37\xa8\x88x\x87\x97www\x98\x99\x99\x89\x88\x99\x98\x89f\x88\x96hdo\xf7\xff\xbb\xaa\x9f\xff\xe2U\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', b"\xf1\x87LBLUFN622950KF36\xa8\x88\x88\x88\x87w\x87xh\x99\x96\x89\x88\x99\x98\x89\x88\x99\x98\x89\x87o\xf6\xff\x98\x88o\xffx'\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8", ], }, @@ -672,6 +689,7 @@ FW_VERSIONS = { CAR.KIA_FORTE: { (Ecu.eps, 0x7D4, None): [ b'\xf1\x00BD MDPS C 1.00 1.02 56310-XX000 4BD2C102', + b'\xf1\x00BD MDPS C 1.00 1.08 56310/M6300 4BDDC108', b'\xf1\x00BD MDPS C 1.00 1.08 56310M6300\x00 4BDDC108', ], (Ecu.fwdCamera, 0x7C4, None): [ @@ -688,6 +706,7 @@ FW_VERSIONS = { b'\xf1\x816VGRAH00018.ELF\xf1\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x816U2VC051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VC051\x00\x00DBD0T16SS0\x00\x00\x00\x00', b"\xf1\x816U2VC051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VC051\x00\x00DBD0T16SS0\xcf\x1e'\xc3", ], }, @@ -696,28 +715,34 @@ FW_VERSIONS = { b'\xf1\000DL3_ SCC FHCUP 1.00 1.03 99110-L2000 ', b'\xf1\x8799110L2000\xf1\000DL3_ SCC FHCUP 1.00 1.03 99110-L2000 ', b'\xf1\x8799110L2100\xf1\x00DL3_ SCC F-CUP 1.00 1.03 99110-L2100 ', + b'\xf1\x8799110L2100\xf1\x00DL3_ SCC FHCUP 1.00 1.03 99110-L2100 ', ], (Ecu.eps, 0x7D4, None): [ b'\xf1\x8756310-L3110\xf1\000DL3 MDPS C 1.00 1.01 56310-L3110 4DLAC101', b'\xf1\x8756310-L3220\xf1\x00DL3 MDPS C 1.00 1.01 56310-L3220 4DLAC101', + b'\xf1\x8757700-L3000\xf1\x00DL3 MDPS R 1.00 1.02 57700-L3000 4DLAP102', ], (Ecu.fwdCamera, 0x7C4, None): [ b'\xf1\x00DL3 MFC AT USA LHD 1.00 1.03 99210-L3000 200915', + b'\xf1\x00DL3 MFC AT USA LHD 1.00 1.04 99210-L3000 210208', ], (Ecu.esp, 0x7D1, None): [ b'\xf1\000DL ESC \006 101 \004\002 58910-L3200', b'\xf1\x8758910-L3200\xf1\000DL ESC \006 101 \004\002 58910-L3200', b'\xf1\x8758910-L3800\xf1\x00DL ESC \t 101 \x07\x02 58910-L3800', + b'\xf1\x8758910-L3600\xf1\x00DL ESC \x03 100 \x08\x02 58910-L3600', ], (Ecu.engine, 0x7E0, None): [ b'\xf1\x87391212MKT0', b'\xf1\x87391212MKV0', + b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82DLDWN5TMDCXXXJ1B', ], (Ecu.transmission, 0x7E1, None): [ b'\xf1\000bcsh8p54 U913\000\000\000\000\000\000TDL2T16NB1ia\v\xb8', b'\xf1\x87SALFEA5652514GK2UUeV\x88\x87\x88xxwg\x87ww\x87wwfwvd/\xfb\xffvU_\xff\x93\xd3\xf1\x81U913\000\000\000\000\000\000\xf1\000bcsh8p54 U913\000\000\000\000\000\000TDL2T16NB1ia\v\xb8', b'\xf1\x87SALFEA6046104GK2wvwgeTeFg\x88\x96xwwwwffvfe?\xfd\xff\x86fo\xff\x97A\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL2T16NB1ia\x0b\xb8', b'\xf1\x87SCMSAA8572454GK1\x87x\x87\x88Vf\x86hgwvwvwwgvwwgT?\xfb\xff\x97fo\xffH\xb8\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL4T16NB05\x94t\x18', + b'\xf1\x87954A02N300\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 WDL3T25XXX730NS2b\x1f\xb8%', ], }, CAR.KONA_EV: { @@ -750,6 +775,7 @@ FW_VERSIONS = { CAR.KIA_NIRO_EV: { (Ecu.fwdRadar, 0x7D0, None): [ b'\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4000 ', + b'\xf1\x00DEev SCC F-CUP 1.00 1.02 96400-Q4100 ', b'\xf1\x00DEev SCC F-CUP 1.00 1.03 96400-Q4100 ', b'\xf1\x00OSev SCC F-CUP 1.00 1.01 99110-K4000 ', b'\xf1\x8799110Q4000\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4000 ', @@ -842,13 +868,17 @@ FW_VERSIONS = { b'\xf1\x89F1JF600AISEIU702\xf1\x82F1JF600AISEIU702', ], (Ecu.eps, 0x7d4, None): [b'\xf1\x00TM MDPS C 1.00 1.00 56340-S2000 8409'], - (Ecu.fwdCamera, 0x7c4, None): [b'\xf1\x00JFA LKAS AT USA LHD 1.00 1.02 95895-D5000 h31'], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00JFA LKAS AT USA LHD 1.00 1.00 95895-D5001 h32', + b'\xf1\x00JFA LKAS AT USA LHD 1.00 1.02 95895-D5000 h31', + ], (Ecu.transmission, 0x7e1, None): [b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJF0T16NL0\t\xd2GW'], }, CAR.ELANTRA_2021: { (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00CN7_ SCC FHCUP 1.00 1.01 99110-AA000 ', b'\xf1\x00CN7_ SCC F-CUP 1.00 1.01 99110-AA000 ', + b'\xf1\x00CN7_ SCC FHCUP 1.00 1.01 99110-AA000 ', + b'\xf1\x8799110AA000\xf1\x00CN7_ SCC FHCUP 1.00 1.01 99110-AA000 ', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x87\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x00CN7 MDPS C 1.00 1.06 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 4CNDC106', diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 336c1ee6c..bef3456a7 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -14,9 +14,7 @@ from selfdrive.controls.lib.vehicle_model import VehicleModel GearShifter = car.CarState.GearShifter EventName = car.CarEvent.EventName -# WARNING: this value was determined based on the model's training distribution, -# model predictions above this speed can be unpredictable -MAX_CTRL_SPEED = (V_CRUISE_MAX + 4) * CV.KPH_TO_MS # 135 + 4 = 86 mph +MAX_CTRL_SPEED = (V_CRUISE_MAX + 4) * CV.KPH_TO_MS ACCEL_MAX = 2.0 ACCEL_MIN = -3.5 @@ -78,6 +76,7 @@ class CarInterfaceBase(): ret.steerMaxBP = [0.] ret.steerMaxV = [1.] ret.minSteerSpeed = 0. + ret.wheelSpeedFactor = 1.0 ret.pcmCruise = True # openpilot's state is tied to the PCM's cruise state on most cars ret.minEnableSpeed = -1. # enable is done by stock ACC, so ignore this @@ -210,6 +209,16 @@ class CarStateBase: v_ego_x = self.v_ego_kf.update(v_ego_raw) return float(v_ego_x[0]), float(v_ego_x[1]) + def get_wheel_speeds(self, fl, fr, rl, rr, unit=CV.KPH_TO_MS): + factor = unit * self.CP.wheelSpeedFactor + + wheelSpeeds = car.CarState.WheelSpeeds.new_message() + wheelSpeeds.fl = fl * factor + wheelSpeeds.fr = fr * factor + wheelSpeeds.rl = rl * factor + wheelSpeeds.rr = rr * factor + return wheelSpeeds + def update_blinker_from_lamp(self, blinker_time: int, left_blinker_lamp: bool, right_blinker_lamp: bool): """Update blinkers from lights. Enable output when light was seen within the last `blinker_time` iterations""" diff --git a/selfdrive/car/mazda/carstate.py b/selfdrive/car/mazda/carstate.py index 4140a4e07..7ebf60c96 100644 --- a/selfdrive/car/mazda/carstate.py +++ b/selfdrive/car/mazda/carstate.py @@ -20,10 +20,12 @@ class CarState(CarStateBase): def update(self, cp, cp_cam): ret = car.CarState.new_message() - ret.wheelSpeeds.fl = cp.vl["WHEEL_SPEEDS"]["FL"] * CV.KPH_TO_MS - ret.wheelSpeeds.fr = cp.vl["WHEEL_SPEEDS"]["FR"] * CV.KPH_TO_MS - ret.wheelSpeeds.rl = cp.vl["WHEEL_SPEEDS"]["RL"] * CV.KPH_TO_MS - ret.wheelSpeeds.rr = cp.vl["WHEEL_SPEEDS"]["RR"] * CV.KPH_TO_MS + ret.wheelSpeeds = self.get_wheel_speeds( + cp.vl["WHEEL_SPEEDS"]["FL"], + cp.vl["WHEEL_SPEEDS"]["FR"], + cp.vl["WHEEL_SPEEDS"]["RL"], + cp.vl["WHEEL_SPEEDS"]["RR"], + ) ret.vEgoRaw = (ret.wheelSpeeds.fl + ret.wheelSpeeds.fr + ret.wheelSpeeds.rl + ret.wheelSpeeds.rr) / 4. ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) diff --git a/selfdrive/car/mazda/values.py b/selfdrive/car/mazda/values.py index ada3ea1dd..58e1dd703 100644 --- a/selfdrive/car/mazda/values.py +++ b/selfdrive/car/mazda/values.py @@ -21,7 +21,7 @@ class CAR: CX9 = "MAZDA CX-9" MAZDA3 = "MAZDA 3" MAZDA6 = "MAZDA 6" - CX9_2021 = "Mazda CX-9 2021" # No Steer Lockout + CX9_2021 = "MAZDA CX-9 2021" # No Steer Lockout class LKAS_LIMITS: STEER_THRESHOLD = 15 diff --git a/selfdrive/car/nissan/carstate.py b/selfdrive/car/nissan/carstate.py index a4019b538..05191feed 100644 --- a/selfdrive/car/nissan/carstate.py +++ b/selfdrive/car/nissan/carstate.py @@ -35,11 +35,12 @@ class CarState(CarStateBase): elif self.CP.carFingerprint in [CAR.LEAF, CAR.LEAF_IC]: ret.brakePressed = bool(cp.vl["CRUISE_THROTTLE"]["USER_BRAKE_PRESSED"]) - ret.wheelSpeeds.fl = cp.vl["WHEEL_SPEEDS_FRONT"]["WHEEL_SPEED_FL"] * CV.KPH_TO_MS - ret.wheelSpeeds.fr = cp.vl["WHEEL_SPEEDS_FRONT"]["WHEEL_SPEED_FR"] * CV.KPH_TO_MS - ret.wheelSpeeds.rl = cp.vl["WHEEL_SPEEDS_REAR"]["WHEEL_SPEED_RL"] * CV.KPH_TO_MS - ret.wheelSpeeds.rr = cp.vl["WHEEL_SPEEDS_REAR"]["WHEEL_SPEED_RR"] * CV.KPH_TO_MS - + ret.wheelSpeeds = self.get_wheel_speeds( + cp.vl["WHEEL_SPEEDS_FRONT"]["WHEEL_SPEED_FL"], + cp.vl["WHEEL_SPEEDS_FRONT"]["WHEEL_SPEED_FR"], + cp.vl["WHEEL_SPEEDS_REAR"]["WHEEL_SPEED_RL"], + cp.vl["WHEEL_SPEEDS_REAR"]["WHEEL_SPEED_RR"], + ) ret.vEgoRaw = (ret.wheelSpeeds.fl + ret.wheelSpeeds.fr + ret.wheelSpeeds.rl + ret.wheelSpeeds.rr) / 4. ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) diff --git a/selfdrive/car/subaru/carstate.py b/selfdrive/car/subaru/carstate.py index e32403776..1f56f09ff 100644 --- a/selfdrive/car/subaru/carstate.py +++ b/selfdrive/car/subaru/carstate.py @@ -23,10 +23,12 @@ class CarState(CarStateBase): else: ret.brakePressed = cp.vl["Brake_Status"]["Brake"] == 1 - ret.wheelSpeeds.fl = cp.vl["Wheel_Speeds"]["FL"] * CV.KPH_TO_MS - ret.wheelSpeeds.fr = cp.vl["Wheel_Speeds"]["FR"] * CV.KPH_TO_MS - ret.wheelSpeeds.rl = cp.vl["Wheel_Speeds"]["RL"] * CV.KPH_TO_MS - ret.wheelSpeeds.rr = cp.vl["Wheel_Speeds"]["RR"] * CV.KPH_TO_MS + ret.wheelSpeeds = self.get_wheel_speeds( + cp.vl["Wheel_Speeds"]["FL"], + cp.vl["Wheel_Speeds"]["FR"], + cp.vl["Wheel_Speeds"]["RL"], + cp.vl["Wheel_Speeds"]["RR"], + ) ret.vEgoRaw = (ret.wheelSpeeds.fl + ret.wheelSpeeds.fr + ret.wheelSpeeds.rl + ret.wheelSpeeds.rr) / 4. # Kalman filter, even though Subaru raw wheel speed is heaviliy filtered by default ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) diff --git a/selfdrive/car/toyota/carcontroller.py b/selfdrive/car/toyota/carcontroller.py index daf91a49d..1997e6c11 100644 --- a/selfdrive/car/toyota/carcontroller.py +++ b/selfdrive/car/toyota/carcontroller.py @@ -1,39 +1,22 @@ from cereal import car from common.numpy_fast import clip, interp -from selfdrive.car import apply_toyota_steer_torque_limits, create_gas_command, make_can_msg +from selfdrive.car import apply_toyota_steer_torque_limits, create_gas_interceptor_command, make_can_msg from selfdrive.car.toyota.toyotacan import create_steer_command, create_ui_command, \ create_accel_command, create_acc_cancel_command, \ create_fcw_command, create_lta_steer_command from selfdrive.car.toyota.values import CAR, STATIC_DSU_MSGS, NO_STOP_TIMER_CAR, TSS2_CAR, \ - MIN_ACC_SPEED, PEDAL_HYST_GAP, PEDAL_SCALE, CarControllerParams + MIN_ACC_SPEED, PEDAL_TRANSITION, CarControllerParams from opendbc.can.packer import CANPacker VisualAlert = car.CarControl.HUDControl.VisualAlert -def accel_hysteresis(accel, accel_steady, enabled): - - # for small accel oscillations within ACCEL_HYST_GAP, don't change the accel command - if not enabled: - # send 0 when disabled, otherwise acc faults - accel_steady = 0. - elif accel > accel_steady + CarControllerParams.ACCEL_HYST_GAP: - accel_steady = accel - CarControllerParams.ACCEL_HYST_GAP - elif accel < accel_steady - CarControllerParams.ACCEL_HYST_GAP: - accel_steady = accel + CarControllerParams.ACCEL_HYST_GAP - accel = accel_steady - - return accel, accel_steady - - class CarController(): def __init__(self, dbc_name, CP, VM): self.last_steer = 0 - self.accel_steady = 0. self.alert_active = False self.last_standstill = False self.standstill_req = False self.steer_rate_limited = False - self.use_interceptor = False self.packer = CANPacker(dbc_name) @@ -43,25 +26,22 @@ class CarController(): # *** compute control surfaces *** # gas and brake - interceptor_gas_cmd = 0. - pcm_accel_cmd = actuators.accel - - if CS.CP.enableGasInterceptor: - # handle hysteresis when around the minimum acc speed - if CS.out.vEgo < MIN_ACC_SPEED: - self.use_interceptor = True - elif CS.out.vEgo > MIN_ACC_SPEED + PEDAL_HYST_GAP: - self.use_interceptor = False - - if self.use_interceptor and active: - # only send negative accel when using interceptor. gas handles acceleration - # +0.18 m/s^2 offset to reduce ABS pump usage when OP is engaged - MAX_INTERCEPTOR_GAS = interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED], [0.2, 0.5]) - interceptor_gas_cmd = clip(actuators.accel / PEDAL_SCALE, 0., MAX_INTERCEPTOR_GAS) - pcm_accel_cmd = 0.18 - max(0, -actuators.accel) - - pcm_accel_cmd, self.accel_steady = accel_hysteresis(pcm_accel_cmd, self.accel_steady, enabled) - pcm_accel_cmd = clip(pcm_accel_cmd, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX) + if CS.CP.enableGasInterceptor and enabled: + MAX_INTERCEPTOR_GAS = 0.5 + # RAV4 has very sensitive gas pedal + if CS.CP.carFingerprint in [CAR.RAV4, CAR.RAV4H, CAR.HIGHLANDER, CAR.HIGHLANDERH]: + PEDAL_SCALE = interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.15, 0.3, 0.0]) + elif CS.CP.carFingerprint in [CAR.COROLLA]: + PEDAL_SCALE = interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.3, 0.4, 0.0]) + else: + PEDAL_SCALE = interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.4, 0.5, 0.0]) + # offset for creep and windbrake + pedal_offset = interp(CS.out.vEgo, [0.0, 2.3, MIN_ACC_SPEED + PEDAL_TRANSITION], [-.4, 0.0, 0.2]) + pedal_command = PEDAL_SCALE * (actuators.accel + pedal_offset) + interceptor_gas_cmd = clip(pedal_command, 0., MAX_INTERCEPTOR_GAS) + else: + interceptor_gas_cmd = 0. + pcm_accel_cmd = clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX) # steer torque new_steer = int(round(actuators.steer * CarControllerParams.STEER_MAX)) @@ -88,7 +68,6 @@ class CarController(): self.standstill_req = False self.last_steer = apply_steer - self.last_accel = pcm_accel_cmd self.last_standstill = CS.out.standstill can_sends = [] @@ -113,7 +92,7 @@ class CarController(): lead = lead or CS.out.vEgo < 12. # at low speed we always assume the lead is present do ACC can be engaged # Lexus IS uses a different cancellation message - if pcm_cancel_cmd and CS.CP.carFingerprint == CAR.LEXUS_IS: + if pcm_cancel_cmd and CS.CP.carFingerprint in [CAR.LEXUS_IS, CAR.LEXUS_RC]: can_sends.append(create_acc_cancel_command(self.packer)) elif CS.CP.openpilotLongitudinalControl: can_sends.append(create_accel_command(self.packer, pcm_accel_cmd, pcm_cancel_cmd, self.standstill_req, lead, CS.acc_type)) @@ -123,7 +102,7 @@ class CarController(): if frame % 2 == 0 and CS.CP.enableGasInterceptor: # send exactly zero if gas cmd is zero. Interceptor will send the max between read value and gas cmd. # This prevents unexpected pedal range rescaling - can_sends.append(create_gas_command(self.packer, interceptor_gas_cmd, frame // 2)) + can_sends.append(create_gas_interceptor_command(self.packer, interceptor_gas_cmd, frame // 2)) # ui mesg is at 100Hz but we send asap if: # - there is something to display diff --git a/selfdrive/car/toyota/carstate.py b/selfdrive/car/toyota/carstate.py index cacd5b7be..7ce5907b9 100644 --- a/selfdrive/car/toyota/carstate.py +++ b/selfdrive/car/toyota/carstate.py @@ -41,10 +41,12 @@ class CarState(CarStateBase): ret.gas = cp.vl["GAS_PEDAL"]["GAS_PEDAL"] ret.gasPressed = cp.vl["PCM_CRUISE"]["GAS_RELEASED"] == 0 - ret.wheelSpeeds.fl = cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FL"] * CV.KPH_TO_MS - ret.wheelSpeeds.fr = cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FR"] * CV.KPH_TO_MS - ret.wheelSpeeds.rl = cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RL"] * CV.KPH_TO_MS - ret.wheelSpeeds.rr = cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RR"] * CV.KPH_TO_MS + ret.wheelSpeeds = self.get_wheel_speeds( + cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FL"], + cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FR"], + cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RL"], + cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RR"], + ) ret.vEgoRaw = mean([ret.wheelSpeeds.fl, ret.wheelSpeeds.fr, ret.wheelSpeeds.rl, ret.wheelSpeeds.rr]) ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) @@ -79,7 +81,7 @@ class CarState(CarStateBase): ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD ret.steerWarning = cp.vl["EPS_STATUS"]["LKA_STATE"] not in [1, 5] - if self.CP.carFingerprint == CAR.LEXUS_IS: + if self.CP.carFingerprint in [CAR.LEXUS_IS, CAR.LEXUS_RC]: ret.cruiseState.available = cp.vl["DSU_CRUISE"]["MAIN_ON"] != 0 ret.cruiseState.speed = cp.vl["DSU_CRUISE"]["SET_SPEED"] * CV.KPH_TO_MS else: @@ -93,7 +95,7 @@ class CarState(CarStateBase): # these cars are identified by an ACC_TYPE value of 2. # TODO: it is possible to avoid the lockout and gain stop and go if you # send your own ACC_CONTROL msg on startup with ACC_TYPE set to 1 - if (self.CP.carFingerprint not in TSS2_CAR and self.CP.carFingerprint != CAR.LEXUS_IS) or \ + if (self.CP.carFingerprint not in TSS2_CAR and self.CP.carFingerprint not in [CAR.LEXUS_IS, CAR.LEXUS_RC]) or \ (self.CP.carFingerprint in TSS2_CAR and self.acc_type == 1): self.low_speed_lockout = cp.vl["PCM_CRUISE_2"]["LOW_SPEED_LOCKOUT"] == 2 @@ -168,7 +170,7 @@ class CarState(CarStateBase): ("STEER_TORQUE_SENSOR", 50), ] - if CP.carFingerprint == CAR.LEXUS_IS: + if CP.carFingerprint in [CAR.LEXUS_IS, CAR.LEXUS_RC]: signals.append(("MAIN_ON", "DSU_CRUISE", 0)) signals.append(("SET_SPEED", "DSU_CRUISE", 0)) checks.append(("DSU_CRUISE", 5)) diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index a1221ae8a..f9d6b586f 100755 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -65,7 +65,6 @@ class CarInterface(CarInterfaceBase): ret.mass = 4387. * CV.LB_TO_KG + STD_CARGO_KG set_lat_tune(ret.lateralTuning, LatTunes.PID_B) - elif candidate == CAR.LEXUS_RXH: stop_and_go = True ret.wheelbase = 2.79 @@ -81,6 +80,7 @@ class CarInterface(CarInterfaceBase): tire_stiffness_factor = 0.5533 # not optimized yet ret.mass = 4387. * CV.LB_TO_KG + STD_CARGO_KG set_lat_tune(ret.lateralTuning, LatTunes.PID_D) + ret.wheelSpeedFactor = 1.035 elif candidate == CAR.LEXUS_RXH_TSS2: stop_and_go = True @@ -89,6 +89,7 @@ class CarInterface(CarInterfaceBase): tire_stiffness_factor = 0.444 # not optimized yet ret.mass = 4481.0 * CV.LB_TO_KG + STD_CARGO_KG # mean between min and max set_lat_tune(ret.lateralTuning, LatTunes.PID_E) + ret.wheelSpeedFactor = 1.035 elif candidate in [CAR.CHR, CAR.CHRH]: stop_and_go = True @@ -186,6 +187,15 @@ class CarInterface(CarInterfaceBase): ret.mass = 3736.8 * CV.LB_TO_KG + STD_CARGO_KG set_lat_tune(ret.lateralTuning, LatTunes.PID_L) + elif candidate == CAR.LEXUS_RC: + ret.safetyConfigs[0].safetyParam = 77 + stop_and_go = False + ret.wheelbase = 2.73050 + ret.steerRatio = 13.3 + tire_stiffness_factor = 0.444 + ret.mass = 3736.8 * CV.LB_TO_KG + STD_CARGO_KG + set_lat_tune(ret.lateralTuning, LatTunes.PID_L) + elif candidate == CAR.LEXUS_CTH: ret.safetyConfigs[0].safetyParam = 100 stop_and_go = True @@ -206,7 +216,7 @@ class CarInterface(CarInterfaceBase): elif candidate == CAR.PRIUS_TSS2: stop_and_go = True ret.wheelbase = 2.70002 # from toyota online sepc. - ret.steerRatio = 13.4 # True steerRation from older prius + ret.steerRatio = 13.4 # True steerRatio from older prius tire_stiffness_factor = 0.6371 # hand-tune ret.mass = 3115. * CV.LB_TO_KG + STD_CARGO_KG set_lat_tune(ret.lateralTuning, LatTunes.PID_N) @@ -259,7 +269,8 @@ class CarInterface(CarInterfaceBase): if ret.enableGasInterceptor: set_long_tune(ret.longitudinalTuning, LongTunes.PEDAL) - elif candidate in [CAR.COROLLA_TSS2, CAR.COROLLAH_TSS2, CAR.RAV4_TSS2, CAR.RAV4H_TSS2, CAR.LEXUS_NX_TSS2]: + elif candidate in [CAR.COROLLA_TSS2, CAR.COROLLAH_TSS2, CAR.RAV4_TSS2, CAR.RAV4H_TSS2, CAR.LEXUS_NX_TSS2, + CAR.HIGHLANDER_TSS2, CAR.HIGHLANDERH_TSS2, CAR.PRIUS_TSS2]: set_long_tune(ret.longitudinalTuning, LongTunes.TSS2) ret.stoppingDecelRate = 0.3 # reach stopping target smoothly ret.startingAccelRate = 6.0 # release brakes fast diff --git a/selfdrive/car/toyota/tunes.py b/selfdrive/car/toyota/tunes.py index 3f210d48a..15c8bbfcc 100644 --- a/selfdrive/car/toyota/tunes.py +++ b/selfdrive/car/toyota/tunes.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 from enum import Enum -from selfdrive.car.toyota.values import MIN_ACC_SPEED, PEDAL_HYST_GAP class LongTunes(Enum): @@ -29,15 +28,8 @@ class LatTunes(Enum): ###### LONG ###### def set_long_tune(tune, name): - if name == LongTunes.PEDAL: - tune.deadzoneBP = [0.] - tune.deadzoneV = [0.] - tune.kpBP = [0., 5., MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_HYST_GAP, 35.] - tune.kpV = [1.2, 0.8, 0.765, 2.255, 1.5] - tune.kiBP = [0., MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_HYST_GAP, 35.] - tune.kiV = [0.18, 0.165, 0.489, 0.36] # Improved longitudinal tune - elif name == LongTunes.TSS2: + if name == LongTunes.TSS2 or name == LongTunes.PEDAL: tune.deadzoneBP = [0., 8.05] tune.deadzoneV = [.0, .14] tune.kpBP = [0., 5., 20.] diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 73198d925..692de379b 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -6,12 +6,10 @@ from selfdrive.config import Conversions as CV Ecu = car.CarParams.Ecu MIN_ACC_SPEED = 19. * CV.MPH_TO_MS +PEDAL_TRANSITION = 10. * CV.MPH_TO_MS -PEDAL_HYST_GAP = 3. * CV.MPH_TO_MS -PEDAL_SCALE = 3.0 class CarControllerParams: - ACCEL_HYST_GAP = 0.06 # don't change accel command for small oscilalitons within this value ACCEL_MAX = 1.5 # m/s2, lower than allowed 2.0 m/s2 for tuning reasons ACCEL_MIN = -3.5 # m/s2 @@ -58,6 +56,7 @@ class CAR: LEXUS_NX = "LEXUS NX 2018" LEXUS_NXH = "LEXUS NX HYBRID 2018" LEXUS_NX_TSS2 = "LEXUS NX 2020" + LEXUS_RC = "LEXUS RC 2020" LEXUS_RX = "LEXUS RX 2016" LEXUS_RXH = "LEXUS RX HYBRID 2017" LEXUS_RX_TSS2 = "LEXUS RX 2020" @@ -347,10 +346,11 @@ FW_VERSIONS = { b'\x018966306T3200\x00\x00\x00\x00', b'\x018966306T4100\x00\x00\x00\x00', ], - (Ecu.fwdRadar, 0x750, 15): [ + (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F6201200\x00\x00\x00\x00', ], - (Ecu.fwdCamera, 0x750, 109): [ + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'\x028646F0602200\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F3305200\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F3305300\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', ], @@ -434,6 +434,7 @@ FW_VERSIONS = { }, CAR.CHRH: { (Ecu.engine, 0x700, None): [ + b'\x0289663F405100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896631013200\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x0289663F405000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x0289663F418000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', @@ -442,6 +443,7 @@ FW_VERSIONS = { b'\x0189663F438000\x00\x00\x00\x00', ], (Ecu.esp, 0x7b0, None): [ + b'F152610012\x00\x00\x00\x00\x00\x00', b'F152610013\x00\x00\x00\x00\x00\x00', b'F152610014\x00\x00\x00\x00\x00\x00', b'F152610040\x00\x00\x00\x00\x00\x00', @@ -455,6 +457,7 @@ FW_VERSIONS = { b'8821FF402400 ', b'8821FF404000 ', b'8821FF404100 ', + b'8821FF405000 ', b'8821FF406000 ', b'8821FF407100 ', ], @@ -470,10 +473,12 @@ FW_VERSIONS = { b'8821FF402400 ', b'8821FF404000 ', b'8821FF404100 ', + b'8821FF405000 ', b'8821FF406000 ', b'8821FF407100 ', ], (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646FF401700 ', b'8646FF402100 ', b'8646FF404000 ', b'8646FF406000 ', @@ -542,6 +547,7 @@ FW_VERSIONS = { b'\x018966312W9000\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ + b'\x0230A10000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x0230A11000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x0230ZN4000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x03312K7000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00', @@ -568,6 +574,7 @@ FW_VERSIONS = { b'\x01F152602560\x00\x00\x00\x00\x00\x00', b'\x01F152602590\x00\x00\x00\x00\x00\x00', b'\x01F152602650\x00\x00\x00\x00\x00\x00', + b"\x01F15260A010\x00\x00\x00\x00\x00\x00", b'\x01F15260A050\x00\x00\x00\x00\x00\x00', b'\x01F152612641\x00\x00\x00\x00\x00\x00', b'\x01F152612651\x00\x00\x00\x00\x00\x00', @@ -666,6 +673,7 @@ FW_VERSIONS = { b'\x028646F1202000\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', b'\x028646F1202100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', b'\x028646F1202200\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', + b"\x028646F1601300\x00\x00\x00\x008646G2601400\x00\x00\x00\x00", b'\x028646F4203400\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', b'\x028646F76020C0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', b'\x028646F7603100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', @@ -801,13 +809,14 @@ FW_VERSIONS = { }, CAR.LEXUS_IS: { (Ecu.engine, 0x700, None): [ + b'\x018966353M7000\x00\x00\x00\x00', b'\x018966353M7100\x00\x00\x00\x00', b'\x018966353Q2000\x00\x00\x00\x00', b'\x018966353Q2300\x00\x00\x00\x00', + b'\x018966353Q4000\x00\x00\x00\x00', b'\x018966353R1100\x00\x00\x00\x00', b'\x018966353R7100\x00\x00\x00\x00', b'\x018966353R8100\x00\x00\x00\x00', - b'\x018966353Q4000\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ b'\x0232480000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', @@ -815,6 +824,7 @@ FW_VERSIONS = { b'\x02353P9000\x00\x00\x00\x00\x00\x00\x00\x00553C1000\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.esp, 0x7b0, None): [ + b'F152653300\x00\x00\x00\x00\x00\x00', b'F152653301\x00\x00\x00\x00\x00\x00', b'F152653310\x00\x00\x00\x00\x00\x00', b'F152653330\x00\x00\x00\x00\x00\x00', @@ -837,9 +847,10 @@ FW_VERSIONS = { b'8821F4702100\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F5301101\x00\x00\x00\x00', + b'8646F5301200\x00\x00\x00\x00', b'8646F5301300\x00\x00\x00\x00', b'8646F5301400\x00\x00\x00\x00', - b'8646F5301200\x00\x00\x00\x00', ], }, CAR.PRIUS: { @@ -1157,6 +1168,7 @@ FW_VERSIONS = { b'\x01896630851000\x00\x00\x00\x00', b'\x01896630851100\x00\x00\x00\x00', b'\x01896630851200\x00\x00\x00\x00', + b'\x01896630852000\x00\x00\x00\x00', b'\x01896630852100\x00\x00\x00\x00', b'\x01896630859000\x00\x00\x00\x00', b'\x01896630860000\x00\x00\x00\x00', @@ -1204,15 +1216,18 @@ FW_VERSIONS = { b'\x018966333T5000\x00\x00\x00\x00', b'\x018966333T5100\x00\x00\x00\x00', b'\x018966333X6000\x00\x00\x00\x00', + b'\x01896633T07000\x00\x00\x00\x00', ], (Ecu.esp, 0x7b0, None): [ b'\x01F152606281\x00\x00\x00\x00\x00\x00', b'\x01F152606340\x00\x00\x00\x00\x00\x00', + b'\x01F152606461\x00\x00\x00\x00\x00\x00', b'\x01F15260E031\x00\x00\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'8965B33252\x00\x00\x00\x00\x00\x00', b'8965B33590\x00\x00\x00\x00\x00\x00', + b'8965B33690\x00\x00\x00\x00\x00\x00', b'8965B48271\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ @@ -1224,6 +1239,7 @@ FW_VERSIONS = { b'\x028646F33030D0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', b'\x028646F3303200\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', b'\x028646F3304100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', + b'\x028646F3304300\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', b'\x028646F4810200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', ], }, @@ -1288,6 +1304,7 @@ FW_VERSIONS = { b'\x01896637851000\x00\x00\x00\x00', b'\x01896637852000\x00\x00\x00\x00', b'\x01896637854000\x00\x00\x00\x00', + b'\x01896637878000\x00\x00\x00\x00', ], (Ecu.esp, 0x7b0, None): [ b'F152678130\x00\x00\x00\x00\x00\x00', @@ -1295,6 +1312,7 @@ FW_VERSIONS = { ], (Ecu.dsu, 0x791, None): [ b'881517803100\x00\x00\x00\x00', + b'881517803300\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'8965B78060\x00\x00\x00\x00\x00\x00', @@ -1306,6 +1324,7 @@ FW_VERSIONS = { ], (Ecu.fwdCamera, 0x750, 0x6d): [ b'8646F7801100\x00\x00\x00\x00', + b'8646F7801300\x00\x00\x00\x00', ], }, CAR.LEXUS_NX_TSS2: { @@ -1358,6 +1377,26 @@ FW_VERSIONS = { b'8646F7801100\x00\x00\x00\x00', ], }, + CAR.LEXUS_RC: { + (Ecu.engine, 0x7e0, None): [ + b'\x0232484000\x00\x00\x00\x00\x00\x00\x00\x0052422000\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.esp, 0x7b0, None): [ + b'F152624221\x00\x00\x00\x00\x00\x00', + ], + (Ecu.dsu, 0x791, None): [ + b'881512409100\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B24081\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'8821F4702300\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F2402200\x00\x00\x00\x00', + ], + }, CAR.LEXUS_RX: { (Ecu.engine, 0x700, None): [ b'\x01896630E36200\x00\x00\x00\x00', @@ -1369,6 +1408,7 @@ FW_VERSIONS = { b'\x01896630E41200\x00\x00\x00\x00', b'\x01896630E41500\x00\x00\x00\x00', b'\x01896630EA3100\x00\x00\x00\x00', + b'\x01896630EA3400\x00\x00\x00\x00', b'\x01896630EA4100\x00\x00\x00\x00', b'\x01896630EA4300\x00\x00\x00\x00', b'\x01896630EA4400\x00\x00\x00\x00', @@ -1558,6 +1598,7 @@ DBC = { CAR.RAV4: dbc_dict('toyota_rav4_2017_pt_generated', 'toyota_adas'), CAR.PRIUS: dbc_dict('toyota_prius_2017_pt_generated', 'toyota_adas'), CAR.COROLLA: dbc_dict('toyota_corolla_2017_pt_generated', 'toyota_adas'), + CAR.LEXUS_RC: dbc_dict('lexus_is_2018_pt_generated', 'toyota_adas'), CAR.LEXUS_RX: dbc_dict('lexus_rx_350_2016_pt_generated', 'toyota_adas'), CAR.LEXUS_RXH: dbc_dict('lexus_rx_hybrid_2017_pt_generated', 'toyota_adas'), CAR.LEXUS_RX_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), diff --git a/selfdrive/car/volkswagen/carcontroller.py b/selfdrive/car/volkswagen/carcontroller.py index 97f00008b..977818dbd 100644 --- a/selfdrive/car/volkswagen/carcontroller.py +++ b/selfdrive/car/volkswagen/carcontroller.py @@ -86,27 +86,28 @@ class CarController(): # FIXME: this entire section is in desperate need of refactoring - if frame > self.graMsgStartFramePrev + P.GRA_VBP_STEP: - if not enabled and CS.out.cruiseState.enabled: - # Cancel ACC if it's engaged with OP disengaged. - self.graButtonStatesToSend = BUTTON_STATES.copy() - self.graButtonStatesToSend["cancel"] = True - elif enabled and CS.out.standstill: - # Blip the Resume button if we're engaged at standstill. - # FIXME: This is a naive implementation, improve with visiond or radar input. - self.graButtonStatesToSend = BUTTON_STATES.copy() - self.graButtonStatesToSend["resumeCruise"] = True + if CS.CP.pcmCruise: + if frame > self.graMsgStartFramePrev + P.GRA_VBP_STEP: + if not enabled and CS.out.cruiseState.enabled: + # Cancel ACC if it's engaged with OP disengaged. + self.graButtonStatesToSend = BUTTON_STATES.copy() + self.graButtonStatesToSend["cancel"] = True + elif enabled and CS.esp_hold_confirmation: + # Blip the Resume button if we're engaged at standstill. + # FIXME: This is a naive implementation, improve with visiond or radar input. + self.graButtonStatesToSend = BUTTON_STATES.copy() + self.graButtonStatesToSend["resumeCruise"] = True - if CS.graMsgBusCounter != self.graMsgBusCounterPrev: - self.graMsgBusCounterPrev = CS.graMsgBusCounter - if self.graButtonStatesToSend is not None: - if self.graMsgSentCount == 0: - self.graMsgStartFramePrev = frame - idx = (CS.graMsgBusCounter + 1) % 16 - can_sends.append(volkswagencan.create_mqb_acc_buttons_control(self.packer_pt, ext_bus, self.graButtonStatesToSend, CS, idx)) - self.graMsgSentCount += 1 - if self.graMsgSentCount >= P.GRA_VBP_COUNT: - self.graButtonStatesToSend = None - self.graMsgSentCount = 0 + if CS.graMsgBusCounter != self.graMsgBusCounterPrev: + self.graMsgBusCounterPrev = CS.graMsgBusCounter + if self.graButtonStatesToSend is not None: + if self.graMsgSentCount == 0: + self.graMsgStartFramePrev = frame + idx = (CS.graMsgBusCounter + 1) % 16 + can_sends.append(volkswagencan.create_mqb_acc_buttons_control(self.packer_pt, ext_bus, self.graButtonStatesToSend, CS, idx)) + self.graMsgSentCount += 1 + if self.graMsgSentCount >= P.GRA_VBP_COUNT: + self.graButtonStatesToSend = None + self.graMsgSentCount = 0 return can_sends diff --git a/selfdrive/car/volkswagen/carstate.py b/selfdrive/car/volkswagen/carstate.py index f02129a61..97e3094fa 100644 --- a/selfdrive/car/volkswagen/carstate.py +++ b/selfdrive/car/volkswagen/carstate.py @@ -20,14 +20,16 @@ class CarState(CarStateBase): def update(self, pt_cp, cam_cp, ext_cp, trans_type): ret = car.CarState.new_message() # Update vehicle speed and acceleration from ABS wheel speeds. - ret.wheelSpeeds.fl = pt_cp.vl["ESP_19"]["ESP_VL_Radgeschw_02"] * CV.KPH_TO_MS - ret.wheelSpeeds.fr = pt_cp.vl["ESP_19"]["ESP_VR_Radgeschw_02"] * CV.KPH_TO_MS - ret.wheelSpeeds.rl = pt_cp.vl["ESP_19"]["ESP_HL_Radgeschw_02"] * CV.KPH_TO_MS - ret.wheelSpeeds.rr = pt_cp.vl["ESP_19"]["ESP_HR_Radgeschw_02"] * CV.KPH_TO_MS + ret.wheelSpeeds = self.get_wheel_speeds( + pt_cp.vl["ESP_19"]["ESP_VL_Radgeschw_02"], + pt_cp.vl["ESP_19"]["ESP_VR_Radgeschw_02"], + pt_cp.vl["ESP_19"]["ESP_HL_Radgeschw_02"], + pt_cp.vl["ESP_19"]["ESP_HR_Radgeschw_02"], + ) ret.vEgoRaw = float(np.mean([ret.wheelSpeeds.fl, ret.wheelSpeeds.fr, ret.wheelSpeeds.rl, ret.wheelSpeeds.rr])) ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) - ret.standstill = bool(pt_cp.vl["ESP_21"]["ESP_Haltebestaetigung"]) + ret.standstill = ret.vEgo < 0.1 # Update steering angle, rate, yaw rate, and driver input torque. VW send # the sign/direction in a separate signal so they must be recombined. @@ -47,6 +49,7 @@ class CarState(CarStateBase): ret.gasPressed = ret.gas > 0 ret.brake = pt_cp.vl["ESP_05"]["ESP_Bremsdruck"] / 250.0 # FIXME: this is pressure in Bar, not sure what OP expects ret.brakePressed = bool(pt_cp.vl["ESP_05"]["ESP_Fahrer_bremst"]) + self.esp_hold_confirmation = pt_cp.vl["ESP_21"]["ESP_Haltebestaetigung"] # Update gear and/or clutch position data. if trans_type == TransmissionType.automatic: @@ -94,13 +97,13 @@ class CarState(CarStateBase): ret.stockAeb = bool(ext_cp.vl["ACC_10"]["ANB_Teilbremsung_Freigabe"]) or bool(ext_cp.vl["ACC_10"]["ANB_Zielbremsung_Freigabe"]) # Update ACC radar status. - accStatus = pt_cp.vl["TSK_06"]["TSK_Status"] - if accStatus == 2: + self.tsk_status = pt_cp.vl["TSK_06"]["TSK_Status"] + if self.tsk_status == 2: # ACC okay and enabled, but not currently engaged ret.cruiseState.available = True ret.cruiseState.enabled = False - elif accStatus in [3, 4, 5]: - # ACC okay and enabled, currently engaged and regulating speed (3) or engaged with driver accelerating (4) or overrun (5) + elif self.tsk_status in [3, 4, 5]: + # ACC okay and enabled, currently regulating speed (3) or driver accel override (4) or overrun coast-down (5) ret.cruiseState.available = True ret.cruiseState.enabled = True else: @@ -110,9 +113,10 @@ class CarState(CarStateBase): # Update ACC setpoint. When the setpoint is zero or there's an error, the # radar sends a set-speed of ~90.69 m/s / 203mph. - ret.cruiseState.speed = ext_cp.vl["ACC_02"]["ACC_Wunschgeschw"] * CV.KPH_TO_MS - if ret.cruiseState.speed > 90: - ret.cruiseState.speed = 0 + if self.CP.pcmCruise: + ret.cruiseState.speed = ext_cp.vl["ACC_02"]["ACC_Wunschgeschw"] * CV.KPH_TO_MS + if ret.cruiseState.speed > 90: + ret.cruiseState.speed = 0 # Update control button states for turn signals and ACC controls. self.buttonStates["accelCruise"] = bool(pt_cp.vl["GRA_ACC_01"]["GRA_Tip_Hoch"]) diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index b8c9c5d67..560e64ce2 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -43,7 +43,7 @@ class CarInterface(CarInterfaceBase): else: ret.networkLocation = NetworkLocation.fwdCamera - # Global tuning defaults, can be overridden per-vehicle + # Global lateral tuning defaults, can be overridden per-vehicle ret.steerActuatorDelay = 0.05 ret.steerRateCost = 1.0 @@ -115,6 +115,10 @@ class CarInterface(CarInterfaceBase): ret.mass = 1205 + STD_CARGO_KG ret.wheelbase = 2.61 + elif candidate == CAR.AUDI_Q3_MK2: + ret.mass = 1623 + STD_CARGO_KG + ret.wheelbase = 2.68 + elif candidate == CAR.SEAT_ATECA_MK1: ret.mass = 1900 + STD_CARGO_KG ret.wheelbase = 2.64 @@ -190,6 +194,8 @@ class CarInterface(CarInterfaceBase): # Vehicle health and operation safety checks if self.CS.parkingBrakeSet: events.add(EventName.parkBrake) + if self.CS.tsk_status in [6, 7]: + events.add(EventName.accFaulted) # Low speed steer alert hysteresis logic if self.CP.minSteerSpeed > 0. and ret.vEgo < (self.CP.minSteerSpeed + 1.): diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index 7b3160e62..822326acd 100755 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -79,6 +79,7 @@ class CAR: TROC_MK1 = "VOLKSWAGEN T-ROC 1ST GEN" # Chassis A1, Mk1 VW VW T-Roc and variants AUDI_A3_MK3 = "AUDI A3 3RD GEN" # Chassis 8V/FF, Mk3 Audi A3 and variants AUDI_Q2_MK1 = "AUDI Q2 1ST GEN" # Chassis GA, Mk1 Audi Q2 (RoW) and Q2L (China only) + AUDI_Q3_MK2 = "AUDI Q3 2ND GEN" # Chassis 8U/F3/FS, Mk2 Audi Q3 and variants SEAT_ATECA_MK1 = "SEAT ATECA 1ST GEN" # Chassis 5F, Mk1 SEAT Ateca and CUPRA Ateca SEAT_LEON_MK3 = "SEAT LEON 3RD GEN" # Chassis 5F, Mk3 SEAT Leon and variants SKODA_KAMIQ_MK1 = "SKODA KAMIQ 1ST GEN" # Chassis NW, Mk1 Skoda Kamiq @@ -121,6 +122,7 @@ FW_VERSIONS = { b'\xf1\x8703H906026F \xf1\x896696', b'\xf1\x8703H906026F \xf1\x899970', b'\xf1\x8703H906026J \xf1\x896026', + b'\xf1\x8703H906026J \xf1\x899971', b'\xf1\x8703H906026S \xf1\x896693', b'\xf1\x8703H906026S \xf1\x899970', ], @@ -147,6 +149,7 @@ FW_VERSIONS = { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704E906016A \xf1\x897697', b'\xf1\x8704E906016AD\xf1\x895758', + b'\xf1\x8704E906016CE\xf1\x899096', b'\xf1\x8704E906023AG\xf1\x891726', b'\xf1\x8704E906023BN\xf1\x894518', b'\xf1\x8704E906024K \xf1\x896811', @@ -187,6 +190,7 @@ FW_VERSIONS = { b'\xf1\x870CW300042F \xf1\x891604', b'\xf1\x870CW300043B \xf1\x891601', b'\xf1\x870CW300044S \xf1\x894530', + b'\xf1\x870CW300044T \xf1\x895245', b'\xf1\x870CW300045 \xf1\x894531', b'\xf1\x870CW300047D \xf1\x895261', b'\xf1\x870CW300048J \xf1\x890611', @@ -269,6 +273,7 @@ FW_VERSIONS = { ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x875Q0907567G \xf1\x890390\xf1\x82\00101', + b'\xf1\x875Q0907567J \xf1\x890396\xf1\x82\x0101', b'\xf1\x875Q0907572A \xf1\x890141\xf1\x82\00101', b'\xf1\x875Q0907572B \xf1\x890200\xf1\x82\00101', b'\xf1\x875Q0907572C \xf1\x890210\xf1\x82\00101', @@ -552,6 +557,29 @@ FW_VERSIONS = { b'\xf1\x872Q0907572M \xf1\x890233', ], }, + CAR.AUDI_Q3_MK2: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8705E906018N \xf1\x899970', + b'\xf1\x8783A906259 \xf1\x890001', + b'\xf1\x8783A906259 \xf1\x890005', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x8709G927158CN\xf1\x893608', + b'\xf1\x870GC300046F \xf1\x892701', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x875Q0959655BF\xf1\x890403\xf1\x82\x1321211111211200311121232152219321422111', + b'\xf1\x875Q0959655CC\xf1\x890421\xf1\x82\x131111111111120031111237116A119321532111', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567G6000300', + b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567G6000800', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572R \xf1\x890372', + b'\xf1\x872Q0907572T \xf1\x890383', + ], + }, CAR.SEAT_ATECA_MK1: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704E906027KA\xf1\x893749', diff --git a/selfdrive/common/modeldata.h b/selfdrive/common/modeldata.h index 7aec15e7b..9a9414cfa 100644 --- a/selfdrive/common/modeldata.h +++ b/selfdrive/common/modeldata.h @@ -1,6 +1,8 @@ #pragma once #include +#include "selfdrive/common/mat.h" +#include "selfdrive/hardware/hw.h" const int TRAJECTORY_SIZE = 33; const int LAT_MPC_N = 16; @@ -36,10 +38,14 @@ const std::array X_IDXS = { 168.75 , 180.1875, 192.}; const auto X_IDXS_FLOAT = convert_array_to_type(X_IDXS); -#ifdef __cplusplus +const int TICI_CAM_WIDTH = 1928; + +namespace tici_dm_crop { + const int x_offset = -72; + const int y_offset = -144; + const int width = 954; +}; -#include "selfdrive/common/mat.h" -#include "selfdrive/hardware/hw.h" const mat3 fcam_intrinsic_matrix = Hardware::EON() ? (mat3){{910., 0., 1164.0 / 2, 0., 910., 874.0 / 2, @@ -62,5 +68,3 @@ static inline mat3 get_model_yuv_transform(bool bayer = true) { }}; return bayer ? transform_scale_buffer(transform, db_s) : transform; } - -#endif diff --git a/selfdrive/common/params.cc b/selfdrive/common/params.cc index 01bf9a8f4..a405d2160 100644 --- a/selfdrive/common/params.cc +++ b/selfdrive/common/params.cc @@ -130,11 +130,13 @@ std::unordered_map keys = { {"JoystickDebugMode", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, {"LastAthenaPingTime", CLEAR_ON_MANAGER_START}, {"LastGPSPosition", PERSISTENT}, + {"LastPowerDropDetected", CLEAR_ON_MANAGER_START}, {"LastUpdateException", PERSISTENT}, {"LastUpdateTime", PERSISTENT}, {"LiveParameters", PERSISTENT}, {"NavDestination", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, {"NavSettingTime24h", PERSISTENT}, + {"NavdRender", PERSISTENT}, {"OpenpilotEnabledToggle", PERSISTENT}, {"PandaHeartbeatLost", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, {"Passive", PERSISTENT}, @@ -151,7 +153,6 @@ std::unordered_map keys = { {"TrainingVersion", PERSISTENT}, {"UpdateAvailable", CLEAR_ON_MANAGER_START}, {"UpdateFailedCount", CLEAR_ON_MANAGER_START}, - {"UploadRaw", PERSISTENT}, {"Version", PERSISTENT}, {"VisionRadarToggle", PERSISTENT}, {"ApiCache_Device", PERSISTENT}, diff --git a/selfdrive/common/util.cc b/selfdrive/common/util.cc index f115b13dc..534a7f445 100644 --- a/selfdrive/common/util.cc +++ b/selfdrive/common/util.cc @@ -20,6 +20,8 @@ #include #endif // __linux__ +namespace util { + void set_thread_name(const char* name) { #ifdef __linux__ // pthread_setname_np is dumb (fails instead of truncates) @@ -56,8 +58,6 @@ int set_core_affinity(std::vector cores) { #endif } -namespace util { - std::string read_file(const std::string& fn) { std::ifstream f(fn, std::ios::binary | std::ios::in); if (f.is_open()) { diff --git a/selfdrive/common/util.h b/selfdrive/common/util.h index a47cce68f..9a6a4d9bd 100644 --- a/selfdrive/common/util.h +++ b/selfdrive/common/util.h @@ -37,13 +37,12 @@ const double MS_TO_MPH = MS_TO_KPH * KM_TO_MILE; const double METER_TO_MILE = KM_TO_MILE / 1000.0; const double METER_TO_FOOT = 3.28084; -void set_thread_name(const char* name); +namespace util { +void set_thread_name(const char* name); int set_realtime_priority(int level); int set_core_affinity(std::vector cores); -namespace util { - // ***** Time helpers ***** struct tm get_time(); bool time_valid(struct tm sys_time); diff --git a/selfdrive/common/version.h b/selfdrive/common/version.h index 9f00fac4f..aa786903c 100644 --- a/selfdrive/common/version.h +++ b/selfdrive/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "0.8.11" +#define COMMA_VERSION "0.8.12" diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index bbd84d458..19fe328db 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -28,6 +28,7 @@ from selfdrive.locationd.calibrationd import Calibration from selfdrive.hardware import HARDWARE, TICI, EON from selfdrive.manager.process_config import managed_processes +SOFT_DISABLE_TIME = 3 # seconds LDW_MIN_SPEED = 31 * CV.MPH_TO_MS LANE_DEPARTURE_THRESHOLD = 0.1 STEER_ANGLE_SATURATION_TIMEOUT = 1.0 / DT_CTRL @@ -300,7 +301,7 @@ class Controls: # Check for mismatch between openpilot and car's PCM cruise_mismatch = CS.cruiseState.enabled and (not self.enabled or not self.CP.pcmCruise) self.cruise_mismatch_counter = self.cruise_mismatch_counter + 1 if cruise_mismatch else 0 - if self.cruise_mismatch_counter > int(1. / DT_CTRL): + if self.cruise_mismatch_counter > int(3. / DT_CTRL): self.events.add(EventName.cruiseMismatch) # Check for FCW @@ -430,7 +431,7 @@ class Controls: if self.state == State.enabled: if self.events.any(ET.SOFT_DISABLE): self.state = State.softDisabling - self.soft_disable_timer = int(3 / DT_CTRL) + self.soft_disable_timer = int(SOFT_DISABLE_TIME / DT_CTRL) self.current_alert_types.append(ET.SOFT_DISABLE) # SOFT DISABLING @@ -540,8 +541,9 @@ class Controls: if len(lat_plan.dPathPoints): # Check if we deviated from the path - left_deviation = actuators.steer > 0 and lat_plan.dPathPoints[0] < -0.1 - right_deviation = actuators.steer < 0 and lat_plan.dPathPoints[0] > 0.1 + # TODO use desired vs actual curvature + left_deviation = actuators.steer > 0 and lat_plan.dPathPoints[0] < -0.20 + right_deviation = actuators.steer < 0 and lat_plan.dPathPoints[0] > 0.20 if left_deviation or right_deviation: self.events.add(EventName.steerSaturated) @@ -612,8 +614,8 @@ class Controls: self.events.add(EventName.ldw) clear_event = ET.WARNING if ET.WARNING not in self.current_alert_types else None - alerts = self.events.create_alerts(self.current_alert_types, [self.CP, self.sm, self.is_metric]) - self.AM.add_many(self.sm.frame, alerts, self.enabled) + alerts = self.events.create_alerts(self.current_alert_types, [self.CP, self.sm, self.is_metric, self.soft_disable_timer]) + self.AM.add_many(self.sm.frame, alerts) self.AM.process_alerts(self.sm.frame, clear_event) CC.hudControl.visualAlert = self.AM.visual_alert diff --git a/selfdrive/controls/lib/alertmanager.py b/selfdrive/controls/lib/alertmanager.py index 42d9f457b..c8e702c5c 100644 --- a/selfdrive/controls/lib/alertmanager.py +++ b/selfdrive/controls/lib/alertmanager.py @@ -8,7 +8,6 @@ from typing import List, Dict, Optional from cereal import car, log from common.basedir import BASEDIR from common.params import Params -from common.realtime import DT_CTRL from selfdrive.controls.lib.events import Alert @@ -33,14 +32,17 @@ class AlertEntry: start_frame: int = -1 end_frame: int = -1 + def active(self, frame: int) -> bool: + return frame <= self.end_frame class AlertManager: def __init__(self): self.reset() - self.activealerts: Dict[str, AlertEntry] = defaultdict(AlertEntry) + self.alerts: Dict[str, AlertEntry] = defaultdict(AlertEntry) def reset(self) -> None: + self.alert: Optional[Alert] = None self.alert_type: str = "" self.alert_text_1: str = "" self.alert_text_2: str = "" @@ -50,37 +52,39 @@ class AlertManager: self.audible_alert = car.CarControl.HUDControl.AudibleAlert.none self.alert_rate: float = 0. - def add_many(self, frame: int, alerts: List[Alert], enabled: bool = True) -> None: + def add_many(self, frame: int, alerts: List[Alert]) -> None: for alert in alerts: - self.activealerts[alert.alert_type].alert = alert - self.activealerts[alert.alert_type].start_frame = frame - self.activealerts[alert.alert_type].end_frame = frame + int(alert.duration / DT_CTRL) + key = alert.alert_type + self.alerts[key].alert = alert + if not self.alerts[key].active(frame): + self.alerts[key].start_frame = frame + min_end_frame = self.alerts[key].start_frame + alert.duration + self.alerts[key].end_frame = max(frame + 1, min_end_frame) def process_alerts(self, frame: int, clear_event_type=None) -> None: current_alert = AlertEntry() - for k, v in self.activealerts.items(): + for k, v in self.alerts.items(): if v.alert is None: continue - if v.alert.event_type == clear_event_type: - self.activealerts[k].end_frame = -1 + if clear_event_type is not None and v.alert.event_type == clear_event_type: + self.alerts[k].end_frame = -1 # sort by priority first and then by start_frame - active = self.activealerts[k].end_frame > frame greater = current_alert.alert is None or (v.alert.priority, v.start_frame) > (current_alert.alert.priority, current_alert.start_frame) - if active and greater: + if v.active(frame) and greater: current_alert = v # clear current alert self.reset() - a = current_alert.alert - if a is not None: - self.alert_type = a.alert_type - self.audible_alert = a.audible_alert - self.visual_alert = a.visual_alert - self.alert_text_1 = a.alert_text_1 - self.alert_text_2 = a.alert_text_2 - self.alert_status = a.alert_status - self.alert_size = a.alert_size - self.alert_rate = a.alert_rate + self.alert = current_alert.alert + if self.alert is not None: + self.alert_type = self.alert.alert_type + self.audible_alert = self.alert.audible_alert + self.visual_alert = self.alert.visual_alert + self.alert_text_1 = self.alert.alert_text_1 + self.alert_text_2 = self.alert.alert_text_2 + self.alert_status = self.alert.alert_status + self.alert_size = self.alert.alert_size + self.alert_rate = self.alert.alert_rate diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index e9ceb7fd7..f173f5fd9 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -5,10 +5,11 @@ from common.realtime import DT_MDL from selfdrive.config import Conversions as CV from selfdrive.modeld.constants import T_IDXS -# kph -V_CRUISE_MAX = 135 -V_CRUISE_MIN = 8 -V_CRUISE_ENABLE_MIN = 40 +# WARNING: this value was determined based on the model's training distribution, +# model predictions above this speed can be unpredictable +V_CRUISE_MAX = 145 # kph +V_CRUISE_MIN = 8 # kph +V_CRUISE_ENABLE_MIN = 40 # kph LAT_MPC_N = 16 LON_MPC_N = 32 diff --git a/selfdrive/controls/lib/events.py b/selfdrive/controls/lib/events.py index bd728159b..0048ca661 100644 --- a/selfdrive/controls/lib/events.py +++ b/selfdrive/controls/lib/events.py @@ -1,5 +1,5 @@ from enum import IntEnum -from typing import Dict, Union, Callable, Any +from typing import Dict, Union, Callable from cereal import log, car import cereal.messaging as messaging @@ -123,7 +123,7 @@ class Alert: self.visual_alert = visual_alert self.audible_alert = audible_alert - self.duration = duration + self.duration = int(duration / DT_CTRL) self.alert_rate = alert_rate self.creation_delay = creation_delay @@ -139,11 +139,10 @@ class Alert: class NoEntryAlert(Alert): - def __init__(self, alert_text_2, audible_alert=AudibleAlert.chimeError, - visual_alert=VisualAlert.none): + def __init__(self, alert_text_2, visual_alert=VisualAlert.none): super().__init__("openpilot Unavailable", alert_text_2, AlertStatus.normal, AlertSize.mid, Priority.LOW, visual_alert, - audible_alert, 3.) + AudibleAlert.refuse, 3.) class SoftDisableAlert(Alert): @@ -151,7 +150,14 @@ class SoftDisableAlert(Alert): super().__init__("TAKE CONTROL IMMEDIATELY", alert_text_2, AlertStatus.userPrompt, AlertSize.full, Priority.MID, VisualAlert.steerRequired, - AudibleAlert.chimeWarningRepeatInfinite, 2.), + AudibleAlert.warningSoft, 2.), + + +# less harsh version of SoftDisable, where the condition is user-triggered +class UserSoftDisableAlert(SoftDisableAlert): + def __init__(self, alert_text_2): + super().__init__(alert_text_2), + self.alert_text_1 = "openpilot will disengage" class ImmediateDisableAlert(Alert): @@ -159,7 +165,7 @@ class ImmediateDisableAlert(Alert): super().__init__("TAKE CONTROL IMMEDIATELY", alert_text_2, AlertStatus.critical, AlertSize.full, Priority.HIGHEST, VisualAlert.steerRequired, - AudibleAlert.chimeWarningRepeatInfinite, 4.), + AudibleAlert.warningImmediate, 4.), class EngagementAlert(Alert): @@ -192,19 +198,39 @@ def get_display_speed(speed_ms: float, metric: bool) -> str: # ********** alert callback functions ********** -def below_engage_speed_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool) -> Alert: + +AlertCallbackType = Callable[[car.CarParams, messaging.SubMaster, bool, int], Alert] + + +def soft_disable_alert(alert_text_2: str) -> AlertCallbackType: + def func(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: + if soft_disable_time < int(0.5 / DT_CTRL): + return ImmediateDisableAlert(alert_text_2) + return SoftDisableAlert(alert_text_2) + return func + + +def user_soft_disable_alert(alert_text_2: str) -> AlertCallbackType: + def func(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: + if soft_disable_time < int(0.5 / DT_CTRL): + return ImmediateDisableAlert(alert_text_2) + return UserSoftDisableAlert(alert_text_2) + return func + + +def below_engage_speed_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: return NoEntryAlert(f"Speed Below {get_display_speed(CP.minEnableSpeed, metric)}") -def below_steer_speed_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool) -> Alert: +def below_steer_speed_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: return Alert( f"Steer Unavailable Below {get_display_speed(CP.minSteerSpeed, metric)}", "", AlertStatus.userPrompt, AlertSize.small, - Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimePrompt, 0.4) + Priority.MID, VisualAlert.steerRequired, AudibleAlert.prompt, 0.4) -def calibration_incomplete_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool) -> Alert: +def calibration_incomplete_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: return Alert( "Calibration in Progress: %d%%" % sm['liveCalibration'].calPerc, f"Drive Above {get_display_speed(MIN_SPEED_FILTER, metric)}", @@ -212,7 +238,7 @@ def calibration_incomplete_alert(CP: car.CarParams, sm: messaging.SubMaster, met Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .2) -def no_gps_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool) -> Alert: +def no_gps_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: gps_integrated = sm['peripheralState'].pandaType in [log.PandaState.PandaType.uno, log.PandaState.PandaType.dos] return Alert( "Poor GPS reception", @@ -221,21 +247,22 @@ def no_gps_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool) -> Al Priority.LOWER, VisualAlert.none, AudibleAlert.none, .2, creation_delay=300.) -def wrong_car_mode_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool) -> Alert: +def wrong_car_mode_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: text = "Cruise Mode Disabled" if CP.carName == "honda": text = "Main Switch Off" return NoEntryAlert(text) -def joystick_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool) -> Alert: +def joystick_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: axes = sm['testJoystick'].axes gb, steer = list(axes)[:2] if len(axes) else (0., 0.) vals = f"Gas: {round(gb * 100.)}%, Steer: {round(steer * 100.)}%" return NormalPermanentAlert("Joystick Mode", vals) -EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, bool], Alert]]]] = { + +EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { # ********** events with no alerts ********** EventName.stockFcw: {}, @@ -248,7 +275,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo }, EventName.controlsInitializing: { - ET.NO_ENTRY: NoEntryAlert("Controls Initializing"), + ET.NO_ENTRY: NoEntryAlert("System Initializing"), }, EventName.startup: { @@ -282,7 +309,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo }, EventName.invalidLkasSetting: { - ET.PERMANENT: NormalPermanentAlert("Stock LKAS is turned on", + ET.PERMANENT: NormalPermanentAlert("Stock LKAS is on", "Turn off stock LKAS to engage"), }, @@ -294,8 +321,8 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo # detects the use of a community feature it switches to dashcam mode # until these features are allowed using a toggle in settings. EventName.communityFeatureDisallowed: { - ET.PERMANENT: NormalPermanentAlert("openpilot Not Available", - "Enable Community Features in Settings to Engage"), + ET.PERMANENT: NormalPermanentAlert("openpilot Unavailable", + "Enable Community Features in Settings"), }, # openpilot doesn't recognize the car. This switches openpilot into a @@ -321,22 +348,22 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo "BRAKE!", "Risk of Collision", AlertStatus.critical, AlertSize.full, - Priority.HIGHEST, VisualAlert.fcw, AudibleAlert.chimeWarningRepeatInfinite, 2.), + Priority.HIGHEST, VisualAlert.fcw, AudibleAlert.warningSoft, 2.), }, EventName.ldw: { ET.PERMANENT: Alert( - "TAKE CONTROL", "Lane Departure Detected", - AlertStatus.userPrompt, AlertSize.mid, - Priority.LOW, VisualAlert.ldw, AudibleAlert.chimePrompt, 3.), + "", + AlertStatus.userPrompt, AlertSize.small, + Priority.LOW, VisualAlert.ldw, AudibleAlert.prompt, 3.), }, # ********** events only containing alerts that display while engaged ********** EventName.gasPressed: { ET.PRE_ENABLE: Alert( - "openpilot will not brake while gas pressed", + "Release Gas Pedal to Engage", "", AlertStatus.normal, AlertSize.small, Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .1, creation_delay=1.), @@ -352,7 +379,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo # bad alignment or bad sensor data. If this happens consistently consider creating an issue on GitHub EventName.vehicleModelInvalid: { ET.NO_ENTRY: NoEntryAlert("Vehicle Parameter Identification Failed"), - ET.SOFT_DISABLE: SoftDisableAlert("Vehicle Parameter Identification Failed"), + ET.SOFT_DISABLE: soft_disable_alert("Vehicle Parameter Identification Failed"), }, EventName.steerTempUnavailableSilent: { @@ -360,12 +387,12 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo "Steering Temporarily Unavailable", "", AlertStatus.userPrompt, AlertSize.small, - Priority.LOW, VisualAlert.steerRequired, AudibleAlert.chimePrompt, 1.), + Priority.LOW, VisualAlert.steerRequired, AudibleAlert.prompt, 1.), }, EventName.preDriverDistracted: { ET.WARNING: Alert( - "KEEP EYES ON ROAD: Driver Distracted", + "Pay Attention", "", AlertStatus.normal, AlertSize.small, Priority.LOW, VisualAlert.none, AudibleAlert.none, .1), @@ -373,10 +400,10 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo EventName.promptDriverDistracted: { ET.WARNING: Alert( - "KEEP EYES ON ROAD", + "Pay Attention", "Driver Distracted", AlertStatus.userPrompt, AlertSize.mid, - Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarning2RepeatInfinite, .1), + Priority.MID, VisualAlert.steerRequired, AudibleAlert.promptDistracted, .1), }, EventName.driverDistracted: { @@ -384,12 +411,12 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo "DISENGAGE IMMEDIATELY", "Driver Distracted", AlertStatus.critical, AlertSize.full, - Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeatInfinite, .1), + Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.warningImmediate, .1), }, EventName.preDriverUnresponsive: { ET.WARNING: Alert( - "TOUCH STEERING WHEEL: No Face Detected", + "Touch Steering Wheel: No Face Detected", "", AlertStatus.normal, AlertSize.small, Priority.LOW, VisualAlert.steerRequired, AudibleAlert.none, .1, alert_rate=0.75), @@ -397,10 +424,10 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo EventName.promptDriverUnresponsive: { ET.WARNING: Alert( - "TOUCH STEERING WHEEL", + "Touch Steering Wheel", "Driver Unresponsive", AlertStatus.userPrompt, AlertSize.mid, - Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarning2RepeatInfinite, .1), + Priority.MID, VisualAlert.steerRequired, AudibleAlert.promptDistracted, .1), }, EventName.driverUnresponsive: { @@ -408,7 +435,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo "DISENGAGE IMMEDIATELY", "Driver Unresponsive", AlertStatus.critical, AlertSize.full, - Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeatInfinite, .1), + Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.warningImmediate, .1), }, EventName.manualRestart: { @@ -422,7 +449,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo EventName.resumeRequired: { ET.WARNING: Alert( "STOPPED", - "Press Resume to Move", + "Press Resume to Go", AlertStatus.userPrompt, AlertSize.mid, Priority.LOW, VisualAlert.none, AudibleAlert.none, .2), }, @@ -452,7 +479,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo "Car Detected in Blindspot", "", AlertStatus.userPrompt, AlertSize.small, - Priority.LOW, VisualAlert.none, AudibleAlert.chimePrompt, .1), + Priority.LOW, VisualAlert.none, AudibleAlert.prompt, .1), }, EventName.laneChange: { @@ -465,10 +492,10 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo EventName.steerSaturated: { ET.WARNING: Alert( - "TAKE CONTROL", + "Take Control", "Turn Exceeds Steering Limit", AlertStatus.userPrompt, AlertSize.mid, - Priority.LOW, VisualAlert.steerRequired, AudibleAlert.chimeWarning2RepeatInfinite, 1.), + Priority.LOW, VisualAlert.steerRequired, AudibleAlert.promptRepeat, 1.), }, # Thrown when the fan is driven at >50% but is not rotating @@ -496,49 +523,49 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo # ********** events that affect controls state transitions ********** EventName.pcmEnable: { - ET.ENABLE: EngagementAlert(AudibleAlert.chimeEngage), + ET.ENABLE: EngagementAlert(AudibleAlert.engage), }, EventName.buttonEnable: { - ET.ENABLE: EngagementAlert(AudibleAlert.chimeEngage), + ET.ENABLE: EngagementAlert(AudibleAlert.engage), }, EventName.pcmDisable: { - ET.USER_DISABLE: EngagementAlert(AudibleAlert.chimeDisengage), + ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage), }, EventName.buttonCancel: { - ET.USER_DISABLE: EngagementAlert(AudibleAlert.chimeDisengage), + ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage), }, EventName.brakeHold: { - ET.USER_DISABLE: EngagementAlert(AudibleAlert.chimeDisengage), + ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage), ET.NO_ENTRY: NoEntryAlert("Brake Hold Active"), }, EventName.parkBrake: { - ET.USER_DISABLE: EngagementAlert(AudibleAlert.chimeDisengage), - ET.NO_ENTRY: NoEntryAlert("Park Brake Engaged"), + ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage), + ET.NO_ENTRY: NoEntryAlert("Parking Brake Engaged"), }, EventName.pedalPressed: { - ET.USER_DISABLE: EngagementAlert(AudibleAlert.chimeDisengage), - ET.NO_ENTRY: NoEntryAlert("Pedal Pressed During Attempt", + ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage), + ET.NO_ENTRY: NoEntryAlert("Pedal Pressed", visual_alert=VisualAlert.brakePressed), }, EventName.wrongCarMode: { - ET.USER_DISABLE: EngagementAlert(AudibleAlert.chimeDisengage), + ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage), ET.NO_ENTRY: wrong_car_mode_alert, }, EventName.wrongCruiseMode: { - ET.USER_DISABLE: EngagementAlert(AudibleAlert.chimeDisengage), - ET.NO_ENTRY: NoEntryAlert("Enable Adaptive Cruise"), + ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage), + ET.NO_ENTRY: NoEntryAlert("Adaptive Cruise Disabled"), }, EventName.steerTempUnavailable: { - ET.SOFT_DISABLE: SoftDisableAlert("Steering Temporarily Unavailable"), + ET.SOFT_DISABLE: soft_disable_alert("Steering Temporarily Unavailable"), ET.NO_ENTRY: NoEntryAlert("Steering Temporarily Unavailable"), }, @@ -575,12 +602,12 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo EventName.overheat: { ET.PERMANENT: NormalPermanentAlert("System Overheated"), - ET.SOFT_DISABLE: SoftDisableAlert("System Overheated"), + ET.SOFT_DISABLE: soft_disable_alert("System Overheated"), ET.NO_ENTRY: NoEntryAlert("System Overheated"), }, EventName.wrongGear: { - ET.SOFT_DISABLE: SoftDisableAlert("Gear not D"), + ET.SOFT_DISABLE: user_soft_disable_alert("Gear not D"), ET.NO_ENTRY: NoEntryAlert("Gear not D"), }, @@ -591,33 +618,33 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo # See https://comma.ai/setup for more information EventName.calibrationInvalid: { ET.PERMANENT: NormalPermanentAlert("Calibration Invalid", "Remount Device and Recalibrate"), - ET.SOFT_DISABLE: SoftDisableAlert("Calibration Invalid: Remount Device & Recalibrate"), + ET.SOFT_DISABLE: soft_disable_alert("Calibration Invalid: Remount Device & Recalibrate"), ET.NO_ENTRY: NoEntryAlert("Calibration Invalid: Remount Device & Recalibrate"), }, EventName.calibrationIncomplete: { ET.PERMANENT: calibration_incomplete_alert, - ET.SOFT_DISABLE: SoftDisableAlert("Calibration in Progress"), + ET.SOFT_DISABLE: soft_disable_alert("Calibration in Progress"), ET.NO_ENTRY: NoEntryAlert("Calibration in Progress"), }, EventName.doorOpen: { - ET.SOFT_DISABLE: SoftDisableAlert("Door Open"), + ET.SOFT_DISABLE: user_soft_disable_alert("Door Open"), ET.NO_ENTRY: NoEntryAlert("Door Open"), }, EventName.seatbeltNotLatched: { - ET.SOFT_DISABLE: SoftDisableAlert("Seatbelt Unlatched"), + ET.SOFT_DISABLE: user_soft_disable_alert("Seatbelt Unlatched"), ET.NO_ENTRY: NoEntryAlert("Seatbelt Unlatched"), }, EventName.espDisabled: { - ET.SOFT_DISABLE: SoftDisableAlert("ESP Off"), + ET.SOFT_DISABLE: soft_disable_alert("ESP Off"), ET.NO_ENTRY: NoEntryAlert("ESP Off"), }, EventName.lowBattery: { - ET.SOFT_DISABLE: SoftDisableAlert("Low Battery"), + ET.SOFT_DISABLE: soft_disable_alert("Low Battery"), ET.NO_ENTRY: NoEntryAlert("Low Battery"), }, @@ -626,19 +653,17 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo # is thrown. This can mean a service crashed, did not broadcast a message for # ten times the regular interval, or the average interval is more than 10% too high. EventName.commIssue: { - ET.SOFT_DISABLE: SoftDisableAlert("Communication Issue between Processes"), - ET.NO_ENTRY: NoEntryAlert("Communication Issue between Processes", - audible_alert=AudibleAlert.chimeDisengage), + ET.SOFT_DISABLE: soft_disable_alert("Communication Issue between Processes"), + ET.NO_ENTRY: NoEntryAlert("Communication Issue between Processes"), }, # Thrown when manager detects a service exited unexpectedly while driving EventName.processNotRunning: { - ET.NO_ENTRY: NoEntryAlert("System Malfunction: Reboot Your Device", - audible_alert=AudibleAlert.chimeDisengage), + ET.NO_ENTRY: NoEntryAlert("System Malfunction: Reboot Your Device"), }, EventName.radarFault: { - ET.SOFT_DISABLE: SoftDisableAlert("Radar Error: Restart the Car"), + ET.SOFT_DISABLE: soft_disable_alert("Radar Error: Restart the Car"), ET.NO_ENTRY: NoEntryAlert("Radar Error: Restart the Car"), }, @@ -646,7 +671,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo # is not processing frames fast enough they have to be dropped. This alert is # thrown when over 20% of frames are dropped. EventName.modeldLagging: { - ET.SOFT_DISABLE: SoftDisableAlert("Driving model lagging"), + ET.SOFT_DISABLE: soft_disable_alert("Driving model lagging"), ET.NO_ENTRY: NoEntryAlert("Driving model lagging"), }, @@ -656,29 +681,27 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo # usually means the model has trouble understanding the scene. This is used # as a heuristic to warn the driver. EventName.posenetInvalid: { - ET.SOFT_DISABLE: SoftDisableAlert("Model Output Uncertain"), + ET.SOFT_DISABLE: soft_disable_alert("Model Output Uncertain"), ET.NO_ENTRY: NoEntryAlert("Model Output Uncertain"), }, # When the localizer detects an acceleration of more than 40 m/s^2 (~4G) we # alert the driver the device might have fallen from the windshield. EventName.deviceFalling: { - ET.SOFT_DISABLE: SoftDisableAlert("Device Fell Off Mount"), + ET.SOFT_DISABLE: soft_disable_alert("Device Fell Off Mount"), ET.NO_ENTRY: NoEntryAlert("Device Fell Off Mount"), }, EventName.lowMemory: { - ET.SOFT_DISABLE: SoftDisableAlert("Low Memory: Reboot Your Device"), + ET.SOFT_DISABLE: soft_disable_alert("Low Memory: Reboot Your Device"), ET.PERMANENT: NormalPermanentAlert("Low Memory", "Reboot your Device"), - ET.NO_ENTRY: NoEntryAlert("Low Memory: Reboot Your Device", - audible_alert=AudibleAlert.chimeDisengage), + ET.NO_ENTRY: NoEntryAlert("Low Memory: Reboot Your Device"), }, EventName.highCpuUsage: { - #ET.SOFT_DISABLE: SoftDisableAlert("System Malfunction: Reboot Your Device"), + #ET.SOFT_DISABLE: soft_disable_alert("System Malfunction: Reboot Your Device"), #ET.PERMANENT: NormalPermanentAlert("System Malfunction", "Reboot your Device"), - ET.NO_ENTRY: NoEntryAlert("System Malfunction: Reboot Your Device", - audible_alert=AudibleAlert.chimeDisengage), + ET.NO_ENTRY: NoEntryAlert("System Malfunction: Reboot Your Device"), }, EventName.accFaulted: { @@ -712,7 +735,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo # Sometimes the USB stack on the device can get into a bad state # causing the connection to the panda to be lost EventName.usbError: { - ET.SOFT_DISABLE: SoftDisableAlert("USB Error: Reboot Your Device"), + ET.SOFT_DISABLE: soft_disable_alert("USB Error: Reboot Your Device"), ET.PERMANENT: NormalPermanentAlert("USB Error: Reboot Your Device", ""), ET.NO_ENTRY: NoEntryAlert("USB Error: Reboot Your Device"), }, @@ -782,7 +805,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo "openpilot Canceled", "No close lead car", AlertStatus.normal, AlertSize.mid, - Priority.HIGH, VisualAlert.none, AudibleAlert.chimeDisengage, 3.), + Priority.HIGH, VisualAlert.none, AudibleAlert.disengage, 3.), ET.NO_ENTRY: NoEntryAlert("No Close Lead Car"), }, @@ -791,16 +814,16 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo "openpilot Canceled", "Speed too low", AlertStatus.normal, AlertSize.mid, - Priority.HIGH, VisualAlert.none, AudibleAlert.chimeDisengage, 3.), + Priority.HIGH, VisualAlert.none, AudibleAlert.disengage, 3.), }, - # When the car is driving faster than most cars in the training data the model outputs can be unpredictable + # When the car is driving faster than most cars in the training data, the model outputs can be unpredictable. EventName.speedTooHigh: { ET.WARNING: Alert( "Speed Too High", "Model uncertain at this speed", AlertStatus.userPrompt, AlertSize.mid, - Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.chimeWarning2RepeatInfinite, 4.), + Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.promptRepeat, 4.), ET.NO_ENTRY: NoEntryAlert("Slow down to engage"), }, diff --git a/selfdrive/controls/lib/latcontrol_angle.py b/selfdrive/controls/lib/latcontrol_angle.py index a641687b4..8fcb9ae7b 100644 --- a/selfdrive/controls/lib/latcontrol_angle.py +++ b/selfdrive/controls/lib/latcontrol_angle.py @@ -1,4 +1,5 @@ import math + from cereal import log @@ -21,5 +22,7 @@ class LatControlAngle(): angle_steers_des += params.angleOffsetDeg angle_log.saturated = False - angle_log.steeringAngleDeg = angle_steers_des + angle_log.steeringAngleDeg = float(CS.steeringAngleDeg) + angle_log.steeringAngleDesiredDeg = angle_steers_des + return 0, float(angle_steers_des), angle_log diff --git a/selfdrive/controls/lib/latcontrol_indi.py b/selfdrive/controls/lib/latcontrol_indi.py index cee391d45..50a8e22b3 100644 --- a/selfdrive/controls/lib/latcontrol_indi.py +++ b/selfdrive/controls/lib/latcontrol_indi.py @@ -95,14 +95,16 @@ class LatControlINDI(): steers_des = VM.get_steer_from_curvature(-curvature, CS.vEgo) steers_des += math.radians(params.angleOffsetDeg) + indi_log.steeringAngleDesiredDeg = math.degrees(steers_des) + + rate_des = VM.get_steer_from_curvature(-curvature_rate, CS.vEgo) + indi_log.steeringRateDesiredDeg = math.degrees(rate_des) + if CS.vEgo < 0.3 or not active: indi_log.active = False self.output_steer = 0.0 self.steer_filter.x = 0.0 else: - - rate_des = VM.get_steer_from_curvature(-curvature_rate, CS.vEgo) - # Expected actuator value self.steer_filter.update_alpha(self.RC) self.steer_filter.update(self.output_steer) diff --git a/selfdrive/controls/lib/latcontrol_lqr.py b/selfdrive/controls/lib/latcontrol_lqr.py index e445fb3f8..16fffac27 100644 --- a/selfdrive/controls/lib/latcontrol_lqr.py +++ b/selfdrive/controls/lib/latcontrol_lqr.py @@ -57,6 +57,7 @@ class LatControlLQR(): instant_offset = params.angleOffsetDeg - params.angleOffsetAverageDeg desired_angle += instant_offset # Only add offset that originates from vehicle model errors + lqr_log.steeringAngleDesiredDeg = desired_angle # Update Kalman filter angle_steers_k = float(self.C.dot(self.x_hat)) @@ -93,7 +94,7 @@ class LatControlLQR(): check_saturation = (CS.vEgo > 10) and not CS.steeringRateLimited and not CS.steeringPressed saturated = self._check_saturation(output_steer, check_saturation, steers_max) - lqr_log.steeringAngleDeg = angle_steers_k + params.angleOffsetAverageDeg + lqr_log.steeringAngleDeg = angle_steers_k lqr_log.i = self.i_lqr lqr_log.output = output_steer lqr_log.lqrOutput = lqr_output diff --git a/selfdrive/controls/lib/latcontrol_pid.py b/selfdrive/controls/lib/latcontrol_pid.py index 5062df36d..c7730d011 100644 --- a/selfdrive/controls/lib/latcontrol_pid.py +++ b/selfdrive/controls/lib/latcontrol_pid.py @@ -24,6 +24,7 @@ class LatControlPID(): angle_steers_des_no_offset = math.degrees(VM.get_steer_from_curvature(-desired_curvature, CS.vEgo)) angle_steers_des = angle_steers_des_no_offset + params.angleOffsetDeg + pid_log.steeringAngleDesiredDeg = angle_steers_des pid_log.angleError = angle_steers_des - CS.steeringAngleDeg if CS.vEgo < 0.3 or not active: output_steer = 0.0 diff --git a/selfdrive/controls/lib/lateral_planner.py b/selfdrive/controls/lib/lateral_planner.py index 74b27f595..0aa2359ae 100644 --- a/selfdrive/controls/lib/lateral_planner.py +++ b/selfdrive/controls/lib/lateral_planner.py @@ -38,7 +38,7 @@ DESIRES = { } -class LateralPlanner(): +class LateralPlanner: def __init__(self, CP, use_lanelines=True, wide_camera=False): self.use_lanelines = use_lanelines self.LP = LanePlanner(wide_camera) @@ -55,8 +55,8 @@ class LateralPlanner(): self.prev_one_blinker = False self.desire = log.LateralPlan.Desire.none - self.path_xyz = np.zeros((TRAJECTORY_SIZE,3)) - self.path_xyz_stds = np.ones((TRAJECTORY_SIZE,3)) + self.path_xyz = np.zeros((TRAJECTORY_SIZE, 3)) + self.path_xyz_stds = np.ones((TRAJECTORY_SIZE, 3)) self.plan_yaw = np.zeros((TRAJECTORY_SIZE,)) self.t_idxs = np.arange(TRAJECTORY_SIZE) self.y_pts = np.zeros(TRAJECTORY_SIZE) @@ -67,12 +67,8 @@ class LateralPlanner(): def reset_mpc(self, x0=np.zeros(6)): self.x0 = x0 self.lat_mpc.reset(x0=self.x0) - self.desired_curvature = 0.0 - self.safe_desired_curvature = 0.0 - self.desired_curvature_rate = 0.0 - self.safe_desired_curvature_rate = 0.0 - def update(self, sm, CP): + def update(self, sm): v_ego = sm['carState'].vEgo active = sm['controlsState'].active measured_curvature = sm['controlsState'].curvature @@ -110,7 +106,7 @@ class LateralPlanner(): self.lane_change_direction = LaneChangeDirection.none torque_applied = sm['carState'].steeringPressed and \ - ((sm['carState'].steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or + ((sm['carState'].steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or (sm['carState'].steeringTorque < 0 and self.lane_change_direction == LaneChangeDirection.right)) blindspot_detected = ((sm['carState'].leftBlindspot and self.lane_change_direction == LaneChangeDirection.left) or @@ -124,7 +120,7 @@ class LateralPlanner(): # LaneChangeState.laneChangeStarting elif self.lane_change_state == LaneChangeState.laneChangeStarting: # fade out over .5s - self.lane_change_ll_prob = max(self.lane_change_ll_prob - 2*DT_MDL, 0.0) + self.lane_change_ll_prob = max(self.lane_change_ll_prob - 2 * DT_MDL, 0.0) # 98% certainty lane_change_prob = self.LP.l_lane_change_prob + self.LP.r_lane_change_prob @@ -167,14 +163,14 @@ class LateralPlanner(): self.LP.rll_prob *= self.lane_change_ll_prob if self.use_lanelines: d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz) - self.lat_mpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING, CP.steerRateCost) + self.lat_mpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING, self.steer_rate_cost) else: d_path_xyz = self.path_xyz - path_cost = np.clip(abs(self.path_xyz[0,1]/self.path_xyz_stds[0,1]), 0.5, 1.5) * MPC_COST_LAT.PATH + path_cost = np.clip(abs(self.path_xyz[0, 1] / self.path_xyz_stds[0, 1]), 0.5, 1.5) * MPC_COST_LAT.PATH # Heading cost is useful at low speed, otherwise end of plan can be off-heading heading_cost = interp(v_ego, [5.0, 10.0], [MPC_COST_LAT.HEADING, 0.0]) - self.lat_mpc.set_weights(path_cost, heading_cost, CP.steerRateCost) - y_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(d_path_xyz, axis=1), d_path_xyz[:,1]) + self.lat_mpc.set_weights(path_cost, heading_cost, self.steer_rate_cost) + y_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(d_path_xyz, axis=1), d_path_xyz[:, 1]) heading_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(self.path_xyz, axis=1), self.plan_yaw) self.y_pts = y_pts @@ -187,11 +183,10 @@ class LateralPlanner(): y_pts, heading_pts) # init state for next - self.x0[3] = interp(DT_MDL, self.t_idxs[:LAT_MPC_N + 1], self.lat_mpc.x_sol[:,3]) + self.x0[3] = interp(DT_MDL, self.t_idxs[:LAT_MPC_N + 1], self.lat_mpc.x_sol[:, 3]) - - # Check for infeasable MPC solution - mpc_nans = any(math.isnan(x) for x in self.lat_mpc.x_sol[:,3]) + # Check for infeasible MPC solution + mpc_nans = any(math.isnan(x) for x in self.lat_mpc.x_sol[:, 3]) t = sec_since_boot() if mpc_nans or self.lat_mpc.solution_status != 0: self.reset_mpc() @@ -212,8 +207,8 @@ class LateralPlanner(): plan_send.lateralPlan.laneWidth = float(self.LP.lane_width) plan_send.lateralPlan.dPathPoints = [float(x) for x in self.y_pts] plan_send.lateralPlan.psis = [float(x) for x in self.lat_mpc.x_sol[0:CONTROL_N, 2]] - plan_send.lateralPlan.curvatures = [float(x) for x in self.lat_mpc.x_sol[0:CONTROL_N,3]] - plan_send.lateralPlan.curvatureRates = [float(x) for x in self.lat_mpc.u_sol[0:CONTROL_N-1]] +[0.0] + plan_send.lateralPlan.curvatures = [float(x) for x in self.lat_mpc.x_sol[0:CONTROL_N, 3]] + plan_send.lateralPlan.curvatureRates = [float(x) for x in self.lat_mpc.u_sol[0:CONTROL_N - 1]] + [0.0] plan_send.lateralPlan.lProb = float(self.LP.lll_prob) plan_send.lateralPlan.rProb = float(self.LP.rll_prob) plan_send.lateralPlan.dProb = float(self.LP.d_prob) diff --git a/selfdrive/controls/lib/longcontrol.py b/selfdrive/controls/lib/longcontrol.py index f59a6cc0d..beacc518d 100644 --- a/selfdrive/controls/lib/longcontrol.py +++ b/selfdrive/controls/lib/longcontrol.py @@ -10,20 +10,20 @@ LongCtrlState = car.CarControl.Actuators.LongControlState STOPPING_TARGET_SPEED_OFFSET = 0.01 # As per ISO 15622:2018 for all speeds -ACCEL_MIN_ISO = -3.5 # m/s^2 -ACCEL_MAX_ISO = 2.0 # m/s^2 +ACCEL_MIN_ISO = -3.5 # m/s^2 +ACCEL_MAX_ISO = 2.0 # m/s^2 -def long_control_state_trans(CP, active, long_control_state, v_ego, v_target, v_pid, +def long_control_state_trans(CP, active, long_control_state, v_ego, v_target_future, v_pid, output_accel, brake_pressed, cruise_standstill, min_speed_can): """Update longitudinal control state machine""" stopping_target_speed = min_speed_can + STOPPING_TARGET_SPEED_OFFSET stopping_condition = (v_ego < 2.0 and cruise_standstill) or \ (v_ego < CP.vEgoStopping and - ((v_pid < stopping_target_speed and v_target < stopping_target_speed) or + ((v_pid < stopping_target_speed and v_target_future < stopping_target_speed) or brake_pressed)) - starting_condition = v_target > CP.vEgoStarting and not cruise_standstill + starting_condition = v_target_future > CP.vEgoStarting and not cruise_standstill if not active: long_control_state = LongCtrlState.off @@ -75,13 +75,10 @@ class LongControl(): v_target_upper = interp(CP.longitudinalActuatorDelayUpperBound, T_IDXS[:CONTROL_N], long_plan.speeds) a_target_upper = 2 * (v_target_upper - long_plan.speeds[0])/CP.longitudinalActuatorDelayUpperBound - long_plan.accels[0] - - v_target = min(v_target_lower, v_target_upper) a_target = min(a_target_lower, a_target_upper) v_target_future = long_plan.speeds[-1] else: - v_target = 0.0 v_target_future = 0.0 a_target = 0.0 @@ -103,11 +100,11 @@ class LongControl(): # tracking objects and driving elif self.long_control_state == LongCtrlState.pid: - self.v_pid = v_target + self.v_pid = long_plan.speeds[0] # Toyota starts braking more when it thinks you want to stop # Freeze the integrator so we don't accelerate to compensate, and don't allow positive acceleration - prevent_overshoot = not CP.stoppingControl and CS.vEgo < 1.5 and v_target_future < 0.7 + prevent_overshoot = not CP.stoppingControl and CS.vEgo < 1.5 and v_target_future < 0.7 and v_target_future < self.v_pid deadzone = interp(CS.vEgo, CP.longitudinalTuning.deadzoneBP, CP.longitudinalTuning.deadzoneV) freeze_integrator = prevent_overshoot diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index 577651f8d..e0e0208d6 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -49,15 +49,15 @@ T_IDXS_LST = [index_function(idx, max_val=MAX_T, max_idx=N+1) for idx in range(N T_IDXS = np.array(T_IDXS_LST) T_DIFFS = np.diff(T_IDXS, prepend=[0.]) MIN_ACCEL = -3.5 -T_REACT = 1.8 -MAX_BRAKE = 9.81 - +T_FOLLOW = 1.45 +COMFORT_BRAKE = 2.5 +STOP_DISTANCE = 6.0 def get_stopped_equivalence_factor(v_lead): - return T_REACT * v_lead + (v_lead*v_lead) / (2 * MAX_BRAKE) + return (v_lead**2) / (2 * COMFORT_BRAKE) def get_safe_obstacle_distance(v_ego): - return 2 * T_REACT * v_ego + (v_ego*v_ego) / (2 * MAX_BRAKE) + 4.0 + return (v_ego**2) / (2 * COMFORT_BRAKE) + T_FOLLOW * v_ego + STOP_DISTANCE def desired_follow_distance(v_ego, v_lead): return get_safe_obstacle_distance(v_ego) - get_stopped_equivalence_factor(v_lead) @@ -203,7 +203,7 @@ class LongitudinalMpc(): self.solver = AcadosOcpSolverFast('long', N, EXPORT_DIR) self.v_solution = [0.0 for i in range(N+1)] self.a_solution = [0.0 for i in range(N+1)] - self.prev_a = self.a_solution + self.prev_a = np.array(self.a_solution) self.j_solution = [0.0 for i in range(N)] self.yref = np.zeros((N+1, COST_DIM)) for i in range(N): @@ -255,7 +255,7 @@ class LongitudinalMpc(): self.solver.cost_set(i, 'Zl', Zl) def set_cur_state(self, v, a): - if abs(self.x0[1] - v) > 1.: + if abs(self.x0[1] - v) > 2.: self.x0[1] = v self.x0[2] = a for i in range(0, N+1): @@ -298,8 +298,9 @@ class LongitudinalMpc(): self.cruise_min_a = min_a self.cruise_max_a = max_a - def update(self, carstate, radarstate, v_cruise): + def update(self, carstate, radarstate, v_cruise, prev_accel_constraint=False): v_ego = self.x0[1] + a_ego = self.x0[2] self.status = radarstate.leadOne.status or radarstate.leadTwo.status lead_xv_0 = self.process_lead(radarstate.leadOne) @@ -317,17 +318,20 @@ class LongitudinalMpc(): # Fake an obstacle for cruise, this ensures smooth acceleration to set speed # when the leads are no factor. - cruise_lower_bound = v_ego + (3/4) * self.cruise_min_a * T_IDXS - cruise_upper_bound = v_ego + (3/4) * self.cruise_max_a * T_IDXS + v_lower = v_ego + (T_IDXS * self.cruise_min_a * 1.05) + v_upper = v_ego + (T_IDXS * self.cruise_max_a * 1.05) v_cruise_clipped = np.clip(v_cruise * np.ones(N+1), - cruise_lower_bound, - cruise_upper_bound) - cruise_obstacle = T_IDXS*v_cruise_clipped + get_safe_obstacle_distance(v_cruise_clipped) + v_lower, + v_upper) + cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped) x_obstacles = np.column_stack([lead_0_obstacle, lead_1_obstacle, cruise_obstacle]) self.source = SOURCES[np.argmin(x_obstacles[0])] self.params[:,2] = np.min(x_obstacles, axis=1) - self.params[:,3] = np.copy(self.prev_a) + if prev_accel_constraint: + self.params[:,3] = np.copy(self.prev_a) + else: + self.params[:,3] = a_ego self.run() if (np.any(lead_xv_0[:,0] - self.x_sol[:,0] < CRASH_DISTANCE) and @@ -348,7 +352,7 @@ class LongitudinalMpc(): x_obstacle = 1e5*np.ones((N+1)) self.params = np.concatenate([self.accel_limit_arr, x_obstacle[:,None], - self.prev_a], axis=1) + self.prev_a[:,None]], axis=1) self.run() @@ -367,7 +371,7 @@ class LongitudinalMpc(): self.a_solution = self.x_sol[:,2] self.j_solution = self.u_sol[:,0] - self.prev_a = interp(T_IDXS + 0.05, T_IDXS, self.a_solution) + self.prev_a = np.interp(T_IDXS + 0.05, T_IDXS, self.a_solution) t = sec_since_boot() if self.solution_status != 0: diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 051a68a74..41bae4c47 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -14,7 +14,7 @@ from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, CONTROL_N from selfdrive.swaglog import cloudlog LON_MPC_STEP = 0.2 # first step is 0.2s -AWARENESS_DECEL = -0.2 # car smoothly decel at .2m/s^2 when user is distracted +AWARENESS_DECEL = -0.2 # car smoothly decel at .2m/s^2 when user is distracted A_CRUISE_MIN = -1.2 A_CRUISE_MAX_VALS = [1.2, 1.2, 0.8, 0.6] A_CRUISE_MAX_BP = [0., 15., 25., 40.] @@ -35,13 +35,13 @@ def limit_accel_in_turns(v_ego, angle_steers, a_target, CP): """ a_total_max = interp(v_ego, _A_TOTAL_MAX_BP, _A_TOTAL_MAX_V) - a_y = v_ego**2 * angle_steers * CV.DEG_TO_RAD / (CP.steerRatio * CP.wheelbase) - a_x_allowed = math.sqrt(max(a_total_max**2 - a_y**2, 0.)) + a_y = v_ego ** 2 * angle_steers * CV.DEG_TO_RAD / (CP.steerRatio * CP.wheelbase) + a_x_allowed = math.sqrt(max(a_total_max ** 2 - a_y ** 2, 0.)) return [a_target[0], min(a_target[1], a_x_allowed)] -class Planner(): +class Planner: def __init__(self, CP, init_v=0.0, init_a=0.0): self.CP = CP self.mpc = LongitudinalMpc() @@ -50,14 +50,13 @@ class Planner(): self.v_desired = init_v self.a_desired = init_a - self.alpha = np.exp(-DT_MDL/2.0) + self.alpha = np.exp(-DT_MDL / 2.0) self.v_desired_trajectory = np.zeros(CONTROL_N) self.a_desired_trajectory = np.zeros(CONTROL_N) self.j_desired_trajectory = np.zeros(CONTROL_N) - - def update(self, sm, CP): + def update(self, sm): v_ego = sm['carState'].vEgo a_ego = sm['carState'].aEgo @@ -68,10 +67,12 @@ class Planner(): long_control_state = sm['controlsState'].longControlState force_slow_decel = sm['controlsState'].forceDecel - enabled = (long_control_state == LongCtrlState.pid) or (long_control_state == LongCtrlState.stopping) - if not enabled or sm['carState'].gasPressed: + prev_accel_constraint = True + if long_control_state == LongCtrlState.off or sm['carState'].gasPressed: self.v_desired = v_ego self.a_desired = a_ego + # Smoothly changing between accel trajectory is only relevant when OP is driving + prev_accel_constraint = False # Prevent divergence, smooth in current v_ego self.v_desired = self.alpha * self.v_desired + (1 - self.alpha) * v_ego @@ -88,12 +89,12 @@ class Planner(): accel_limits_turns[1] = max(accel_limits_turns[1], self.a_desired - 0.05) self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1]) self.mpc.set_cur_state(self.v_desired, self.a_desired) - self.mpc.update(sm['carState'], sm['radarState'], v_cruise) + self.mpc.update(sm['carState'], sm['radarState'], v_cruise, prev_accel_constraint=prev_accel_constraint) self.v_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC, self.mpc.v_solution) self.a_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC, self.mpc.a_solution) self.j_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC[:-1], self.mpc.j_solution) - #TODO counter is only needed because radar is glitchy, remove once radar is gone + # TODO counter is only needed because radar is glitchy, remove once radar is gone self.fcw = self.mpc.crash_cnt > 5 if self.fcw: cloudlog.info("FCW triggered") @@ -101,7 +102,7 @@ class Planner(): # Interpolate 0.05 seconds and save as starting point for next iteration a_prev = self.a_desired self.a_desired = float(interp(DT_MDL, T_IDXS[:CONTROL_N], self.a_desired_trajectory)) - self.v_desired = self.v_desired + DT_MDL * (self.a_desired + a_prev)/2.0 + self.v_desired = self.v_desired + DT_MDL * (self.a_desired + a_prev) / 2.0 def publish(self, sm, pm): plan_send = messaging.new_message('longitudinalPlan') diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py index 88734b3c1..02f1c19a7 100755 --- a/selfdrive/controls/plannerd.py +++ b/selfdrive/controls/plannerd.py @@ -36,9 +36,9 @@ def plannerd_thread(sm=None, pm=None): sm.update() if sm.updated['modelV2']: - lateral_planner.update(sm, CP) + lateral_planner.update(sm) lateral_planner.publish(sm, pm) - longitudinal_planner.update(sm, CP) + longitudinal_planner.update(sm) longitudinal_planner.publish(sm, pm) diff --git a/selfdrive/crash.py b/selfdrive/crash.py index f85d7c0e6..e42b7532b 100644 --- a/selfdrive/crash.py +++ b/selfdrive/crash.py @@ -1,6 +1,6 @@ """Install exception handler for process crash.""" from selfdrive.swaglog import cloudlog -from selfdrive.version import version +from selfdrive.version import get_version import sentry_sdk from sentry_sdk.integrations.threading import ThreadingIntegration @@ -24,4 +24,4 @@ def bind_extra(**kwargs) -> None: def init() -> None: sentry_sdk.init("https://a8dc76b5bfb34908a601d67e2aa8bcf9@o33823.ingest.sentry.io/77924", default_integrations=False, integrations=[ThreadingIntegration(propagate_hub=True)], - release=version) + release=get_version()) diff --git a/selfdrive/debug/cpu_usage_stat.py b/selfdrive/debug/cpu_usage_stat.py index 5931fda64..aafc696b4 100755 --- a/selfdrive/debug/cpu_usage_stat.py +++ b/selfdrive/debug/cpu_usage_stat.py @@ -66,7 +66,7 @@ if __name__ == "__main__": for p in psutil.process_iter(): if p == psutil.Process(): continue - matched = any([l for l in p.cmdline() if any([pn for pn in monitored_proc_names if re.match(r'.*{}.*'.format(pn), l, re.M | re.I)])]) + matched = any(l for l in p.cmdline() if any(pn for pn in monitored_proc_names if re.match(r'.*{}.*'.format(pn), l, re.M | re.I))) if matched: k = ' '.join(p.cmdline()) print('Add monitored proc:', k) @@ -119,5 +119,5 @@ if __name__ == "__main__": for x in l: print(x[2]) print('avg sum: {0:.2%} over {1} samples {2} seconds\n'.format( - sum([stat['avg']['total'] for k, stat in stats.items()]), i, i * SLEEP_INTERVAL + sum(stat['avg']['total'] for k, stat in stats.items()), i, i * SLEEP_INTERVAL )) diff --git a/selfdrive/debug/cycle_alerts.py b/selfdrive/debug/cycle_alerts.py index 8b01a73b0..f28d5373f 100755 --- a/selfdrive/debug/cycle_alerts.py +++ b/selfdrive/debug/cycle_alerts.py @@ -7,18 +7,31 @@ import time from cereal import car, log import cereal.messaging as messaging +from common.realtime import DT_CTRL from selfdrive.car.honda.interface import CarInterface from selfdrive.controls.lib.events import ET, EVENTS, Events from selfdrive.controls.lib.alertmanager import AlertManager EventName = car.CarEvent.EventName -def cycle_alerts(duration=2000, is_metric=False): - alerts = list(EVENTS.keys()) - print(alerts) +def cycle_alerts(duration=200, is_metric=False): + # all alerts + #alerts = list(EVENTS.keys()) - alerts = [EventName.preDriverDistracted, EventName.promptDriverDistracted, EventName.driverDistracted] - #alerts = [EventName.preLaneChangeLeft, EventName.preLaneChangeRight] + # this plays each type of audible alert + alerts = [ + (EventName.buttonEnable, ET.ENABLE), + (EventName.buttonCancel, ET.USER_DISABLE), + (EventName.wrongGear, ET.NO_ENTRY), + + (EventName.vehicleModelInvalid, ET.SOFT_DISABLE), + (EventName.accFaulted, ET.IMMEDIATE_DISABLE), + + # DM sequence + (EventName.preDriverDistracted, ET.WARNING), + (EventName.promptDriverDistracted, ET.WARNING), + (EventName.driverDistracted, ET.WARNING), + ] CP = CarInterface.get_params("HONDA CIVIC 2016") sm = messaging.SubMaster(['deviceState', 'pandaStates', 'roadCameraState', 'modelV2', 'liveCalibration', @@ -30,43 +43,45 @@ def cycle_alerts(duration=2000, is_metric=False): AM = AlertManager() frame = 0 - idx, last_alert_millis = 0, 0 - while 1: - if frame % duration == 0: - idx = (idx + 1) % len(alerts) - events.clear() - events.add(alerts[idx]) - - + while True: current_alert_types = [ET.PERMANENT, ET.USER_DISABLE, ET.IMMEDIATE_DISABLE, ET.SOFT_DISABLE, ET.PRE_ENABLE, ET.NO_ENTRY, ET.ENABLE, ET.WARNING] - a = events.create_alerts(current_alert_types, [CP, sm, is_metric]) - AM.add_many(frame, a) - AM.process_alerts(frame) - dat = messaging.new_message() - dat.init('controlsState') - dat.controlsState.alertText1 = AM.alert_text_1 - dat.controlsState.alertText2 = AM.alert_text_2 - dat.controlsState.alertSize = AM.alert_size - dat.controlsState.alertStatus = AM.alert_status - dat.controlsState.alertBlinkingRate = AM.alert_rate - dat.controlsState.alertType = AM.alert_type - dat.controlsState.alertSound = AM.audible_alert - pm.send('controlsState', dat) + for alert, et in alerts: + events.clear() + events.add(alert) - dat = messaging.new_message() - dat.init('deviceState') - dat.deviceState.started = True - pm.send('deviceState', dat) + a = events.create_alerts([et, ], [CP, sm, is_metric, 0]) + AM.add_many(frame, a) + AM.process_alerts(frame) + print(AM.alert) + for _ in range(duration): + dat = messaging.new_message() + dat.init('controlsState') + dat.controlsState.enabled = True - dat = messaging.new_message('pandaStates', 1) - dat.pandaStates[0].ignitionLine = True - dat.pandaStates[0].pandaType = log.PandaState.PandaType.uno - pm.send('pandaStates', dat) + dat.controlsState.alertText1 = AM.alert_text_1 + dat.controlsState.alertText2 = AM.alert_text_2 + dat.controlsState.alertSize = AM.alert_size + dat.controlsState.alertStatus = AM.alert_status + dat.controlsState.alertBlinkingRate = AM.alert_rate + dat.controlsState.alertType = AM.alert_type + dat.controlsState.alertSound = AM.audible_alert + pm.send('controlsState', dat) - time.sleep(0.01) + dat = messaging.new_message() + dat.init('deviceState') + dat.deviceState.started = True + pm.send('deviceState', dat) + + dat = messaging.new_message('pandaStates', 1) + dat.pandaStates[0].ignitionLine = True + dat.pandaStates[0].pandaType = log.PandaState.PandaType.uno + pm.send('pandaStates', dat) + + frame += 1 + time.sleep(DT_CTRL) if __name__ == '__main__': cycle_alerts() diff --git a/selfdrive/hardware/eon/androidd.py b/selfdrive/hardware/eon/androidd.py index 6de00d8e7..b836eb012 100755 --- a/selfdrive/hardware/eon/androidd.py +++ b/selfdrive/hardware/eon/androidd.py @@ -52,25 +52,26 @@ def main(): cloudlog.event("android service pid changed", proc=p, cur=cur[p], prev=procs[p]) procs.update(cur) - # check modem state - state = get_modem_state() - if state != modem_state and not modem_killed: - cloudlog.event("modem state changed", state=state) - modem_state = state + if os.path.exists(MODEM_PATH): + # check modem state + state = get_modem_state() + if state != modem_state and not modem_killed: + cloudlog.event("modem state changed", state=state) + modem_state = state - # check modem crashes - cnt = get_modem_crash_count() - if cnt is not None: - if cnt > crash_count: - cloudlog.event("modem crash", count=cnt) - crash_count = cnt + # check modem crashes + cnt = get_modem_crash_count() + if cnt is not None: + if cnt > crash_count: + cloudlog.event("modem crash", count=cnt) + crash_count = cnt - # handle excessive modem crashes - if crash_count > MAX_MODEM_CRASHES and not modem_killed: - cloudlog.event("killing modem") - with open("/sys/kernel/debug/msm_subsys/modem", "w") as f: - f.write("put") - modem_killed = True + # handle excessive modem crashes + if crash_count > MAX_MODEM_CRASHES and not modem_killed: + cloudlog.event("killing modem") + with open("/sys/kernel/debug/msm_subsys/modem", "w") as f: + f.write("put") + modem_killed = True time.sleep(1) diff --git a/selfdrive/hardware/tici/agnos.json b/selfdrive/hardware/tici/agnos.json index 14f5567c7..f22575a33 100644 --- a/selfdrive/hardware/tici/agnos.json +++ b/selfdrive/hardware/tici/agnos.json @@ -1,10 +1,10 @@ [ { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-ab4b6f64a90617ddbebe250f977616d70a25864f82c9c6ea9d88ebc5fe80e37c.img.xz", - "hash": "ab4b6f64a90617ddbebe250f977616d70a25864f82c9c6ea9d88ebc5fe80e37c", - "hash_raw": "ab4b6f64a90617ddbebe250f977616d70a25864f82c9c6ea9d88ebc5fe80e37c", - "size": 14772224, + "url": "https://commadist.azureedge.net/agnosupdate/boot-5ad783213b7de18400f5fd3609fe75677fec80780ae31cbdf5a8ee7106675d7c.img.xz", + "hash": "5ad783213b7de18400f5fd3609fe75677fec80780ae31cbdf5a8ee7106675d7c", + "hash_raw": "5ad783213b7de18400f5fd3609fe75677fec80780ae31cbdf5a8ee7106675d7c", + "size": 14768128, "sparse": false, "full_check": true, "has_ab": true @@ -41,9 +41,9 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-a778d523851d88a78ad7440ab602a80e09decdd1877f9f31ea36a7d7f15970dd.img.xz", - "hash": "540ee7184cc6d8c14f94e652a062027dcc7559e47f4b347b6f8abac570521ec6", - "hash_raw": "a778d523851d88a78ad7440ab602a80e09decdd1877f9f31ea36a7d7f15970dd", + "url": "https://commadist.azureedge.net/agnosupdate/system-0fee88a42385d067756e9b25d57a80228835310deb7b5eef7b7bed5c22c45515.img.xz", + "hash": "a043cba1ae08ca6d17704a8a0978b1e27e5bc79abb85b97efd35203ae26ae1ea", + "hash_raw": "0fee88a42385d067756e9b25d57a80228835310deb7b5eef7b7bed5c22c45515", "size": 10737418240, "sparse": true, "full_check": false, diff --git a/selfdrive/hardware/tici/agnos.py b/selfdrive/hardware/tici/agnos.py index d62d4dbc5..a28b13ac4 100755 --- a/selfdrive/hardware/tici/agnos.py +++ b/selfdrive/hardware/tici/agnos.py @@ -5,6 +5,7 @@ import hashlib import requests import struct import subprocess +import time import os from typing import Generator @@ -224,7 +225,6 @@ def verify_agnos_update(manifest_path: str, target_slot_number: int) -> bool: if __name__ == "__main__": import logging - import time import argparse parser = argparse.ArgumentParser(description="Flash and verify AGNOS update", diff --git a/selfdrive/hardware/tici/amplifier.py b/selfdrive/hardware/tici/amplifier.py index 99b6d0598..a8b279863 100755 --- a/selfdrive/hardware/tici/amplifier.py +++ b/selfdrive/hardware/tici/amplifier.py @@ -31,17 +31,18 @@ BASE_CONFIG = [ AmpConfig("Enable PLL2", 0b1, 0x1A, 7, 0b10000000), AmpConfig("DAI1: I2S mode", 0b00100, 0x14, 2, 0b01111100), AmpConfig("DAI2: I2S mode", 0b00100, 0x1C, 2, 0b01111100), - AmpConfig("Right speaker output volume", 0x1a, 0x3E, 0, 0b00011111), + AmpConfig("Right speaker output volume", 0x1c, 0x3E, 0, 0b00011111), AmpConfig("DAI1 Passband filtering: music mode", 0b1, 0x18, 7, 0b10000000), AmpConfig("DAI1 voice mode gain (DV1G)", 0b00, 0x2F, 4, 0b00110000), AmpConfig("DAI1 attenuation (DV1)", 0x0, 0x2F, 0, 0b00001111), AmpConfig("DAI2 attenuation (DV2)", 0x0, 0x31, 0, 0b00001111), AmpConfig("DAI2: DC blocking", 0b1, 0x20, 0, 0b00000001), AmpConfig("DAI2: High sample rate", 0b0, 0x20, 3, 0b00001000), - AmpConfig("ALC enable", 0b0, 0x43, 7, 0b10000000), + AmpConfig("ALC enable", 0b1, 0x43, 7, 0b10000000), AmpConfig("ALC/excursion limiter release time", 0b101, 0x43, 4, 0b01110000), + AmpConfig("ALC multiband enable", 0b1, 0x43, 3, 0b00001000), AmpConfig("DAI1 EQ enable", 0b0, 0x49, 0, 0b00000001), - AmpConfig("DAI2 EQ enable", 0b0, 0x49, 1, 0b00000010), + AmpConfig("DAI2 EQ enable", 0b1, 0x49, 1, 0b00000010), AmpConfig("DAI2 EQ clip detection disabled", 0b1, 0x32, 4, 0b00010000), AmpConfig("DAI2 EQ attenuation", 0x5, 0x32, 0, 0b00001111), AmpConfig("Excursion limiter upper corner freq", 0b100, 0x41, 4, 0b01110000), @@ -62,11 +63,11 @@ BASE_CONFIG = [ AmpConfig("Zero-crossing detection disabled", 0b0, 0x49, 5, 0b00100000), ] -BASE_CONFIG += configs_from_eq_params(0x84, EQParams(0x65C4, 0xC07C, 0x3D66, 0x07D9, 0x120F)) +BASE_CONFIG += configs_from_eq_params(0x84, EQParams(0x274F, 0xC0FF, 0x3BF9, 0x0B3C, 0x1656)) BASE_CONFIG += configs_from_eq_params(0x8E, EQParams(0x1009, 0xC6BF, 0x2952, 0x1C97, 0x30DF)) -BASE_CONFIG += configs_from_eq_params(0x98, EQParams(0x2822, 0xC1C7, 0x3B50, 0x0EF8, 0x180A)) -BASE_CONFIG += configs_from_eq_params(0xA2, EQParams(0x1009, 0xC5C2, 0x271F, 0x1A87, 0x32A6)) -BASE_CONFIG += configs_from_eq_params(0xAC, EQParams(0x2000, 0xCA1E, 0x4000, 0x2287, 0x0000)) +BASE_CONFIG += configs_from_eq_params(0x98, EQParams(0x0F75, 0xCBE5, 0x0ED2, 0x2528, 0x3E42)) +BASE_CONFIG += configs_from_eq_params(0xA2, EQParams(0x091F, 0x3D4C, 0xCE11, 0x1266, 0x2807)) +BASE_CONFIG += configs_from_eq_params(0xAC, EQParams(0x0A9E, 0x3F20, 0xE573, 0x0A8B, 0x3A3B)) class Amplifier: AMP_I2C_BUS = 0 diff --git a/selfdrive/hardware/tici/hardware.h b/selfdrive/hardware/tici/hardware.h index abd7e9297..c37dbb0a3 100644 --- a/selfdrive/hardware/tici/hardware.h +++ b/selfdrive/hardware/tici/hardware.h @@ -9,8 +9,8 @@ class HardwareTici : public HardwareNone { public: - static constexpr float MAX_VOLUME = 1.0; - static constexpr float MIN_VOLUME = 0.4; + static constexpr float MAX_VOLUME = 0.9; + static constexpr float MIN_VOLUME = 0.2; static bool TICI() { return true; } static std::string get_os_version() { return "AGNOS " + util::read_file("/VERSION"); diff --git a/selfdrive/hardware/tici/hardware.py b/selfdrive/hardware/tici/hardware.py index 5b7596df2..855eee908 100644 --- a/selfdrive/hardware/tici/hardware.py +++ b/selfdrive/hardware/tici/hardware.py @@ -303,6 +303,13 @@ class Tici(HardwareBase): val = "0" if powersave_enabled else "1" os.system(f"sudo su -c 'echo {val} > /sys/devices/system/cpu/cpu{i}/online'") + for n in ('0', '4'): + gov = 'userspace' if powersave_enabled else 'performance' + os.system(f"sudo su -c 'echo {gov} > /sys/devices/system/cpu/cpufreq/policy{n}/scaling_governor'") + + if powersave_enabled: + os.system(f"sudo su -c 'echo 979200 > /sys/devices/system/cpu/cpufreq/policy{n}/scaling_setspeed'") + def get_gpu_usage_percent(self): try: used, total = open('/sys/class/kgsl/kgsl-3d0/gpubusy').read().strip().split() diff --git a/selfdrive/locationd/locationd.cc b/selfdrive/locationd/locationd.cc index 8bdc30a8d..0350625a9 100755 --- a/selfdrive/locationd/locationd.cc +++ b/selfdrive/locationd/locationd.cc @@ -18,6 +18,7 @@ const double MIN_STD_SANITY_CHECK = 1e-5; // m or rad const double VALID_TIME_SINCE_RESET = 1.0; // s const double VALID_POS_STD = 50.0; // m const double MAX_RESET_TRACKER = 5.0; +const double SANE_GPS_UNCERTAINTY = 1500.0; // m static VectorXd floatlist2vector(const capnp::List::Reader& floatlist) { VectorXd res(floatlist.size()); @@ -130,16 +131,16 @@ void Localizer::build_live_location(cereal::LiveLocationKalman::Builder& fix) { Vector3d nans = Vector3d(NAN, NAN, NAN); // write measurements to msg - init_measurement(fix.initPositionGeodetic(), fix_pos_geo_vec, nans, this->last_gps_fix > 0); - init_measurement(fix.initPositionECEF(), fix_ecef, fix_ecef_std, this->last_gps_fix > 0); - init_measurement(fix.initVelocityECEF(), vel_ecef, vel_ecef_std, this->last_gps_fix > 0); - init_measurement(fix.initVelocityNED(), ned_vel, nans, this->last_gps_fix > 0); + init_measurement(fix.initPositionGeodetic(), fix_pos_geo_vec, nans, this->gps_mode); + init_measurement(fix.initPositionECEF(), fix_ecef, fix_ecef_std, this->gps_mode); + init_measurement(fix.initVelocityECEF(), vel_ecef, vel_ecef_std, this->gps_mode); + init_measurement(fix.initVelocityNED(), ned_vel, nans, this->gps_mode); init_measurement(fix.initVelocityDevice(), vel_device, vel_device_std, true); init_measurement(fix.initAccelerationDevice(), accDevice, accDeviceErr, true); - init_measurement(fix.initOrientationECEF(), orientation_ecef, orientation_ecef_std, this->last_gps_fix > 0); - init_measurement(fix.initCalibratedOrientationECEF(), calibrated_orientation_ecef, nans, this->calibrated && this->last_gps_fix > 0); - init_measurement(fix.initOrientationNED(), orientation_ned, nans, this->last_gps_fix > 0); - init_measurement(fix.initCalibratedOrientationNED(), calibrated_orientation_ned, nans, this->calibrated && this->last_gps_fix > 0); + init_measurement(fix.initOrientationECEF(), orientation_ecef, orientation_ecef_std, this->gps_mode); + init_measurement(fix.initCalibratedOrientationECEF(), calibrated_orientation_ecef, nans, this->calibrated && this->gps_mode); + init_measurement(fix.initOrientationNED(), orientation_ned, nans, this->gps_mode); + init_measurement(fix.initCalibratedOrientationNED(), calibrated_orientation_ned, nans, this->calibrated && this->gps_mode); init_measurement(fix.initAngularVelocityDevice(), angVelocityDevice, angVelocityDeviceErr, true); init_measurement(fix.initVelocityCalibrated(), vel_calib, vel_calib_std, this->calibrated); init_measurement(fix.initAngularVelocityCalibrated(), ang_vel_calib, ang_vel_calib_std, this->calibrated); @@ -243,27 +244,39 @@ void Localizer::handle_sensors(double current_time, const capnp::List observe current obs with reasonable STD + this->kf->predict(current_time); + + VectorXd current_x = this->kf->get_x(); + VectorXd ecef_pos = current_x.segment(STATE_ECEF_POS_START); + VectorXd ecef_vel = current_x.segment(STATE_ECEF_VELOCITY_START); + MatrixXdr ecef_pos_R = this->kf->get_fake_gps_pos_cov(); + MatrixXdr ecef_vel_R = this->kf->get_fake_gps_vel_cov(); + + this->kf->predict_and_observe(current_time, OBSERVATION_ECEF_POS, { ecef_pos }, { ecef_pos_R }); + this->kf->predict_and_observe(current_time, OBSERVATION_ECEF_VEL, { ecef_vel }, { ecef_vel_R }); +} + void Localizer::handle_gps(double current_time, const cereal::GpsLocationData::Reader& log) { // ignore the message if the fix is invalid - if (log.getFlags() % 2 == 0) { - return; - } - // Sanity checks - if ((log.getVerticalAccuracy() <= 0) || (log.getSpeedAccuracy() <= 0) || (log.getBearingAccuracyDeg() <= 0)) { - return; - } + bool gps_invalid_flag = (log.getFlags() % 2 == 0); + bool gps_unreasonable = (Vector2d(log.getAccuracy(), log.getVerticalAccuracy()).norm() >= SANE_GPS_UNCERTAINTY); + bool gps_accuracy_insane = ((log.getVerticalAccuracy() <= 0) || (log.getSpeedAccuracy() <= 0) || (log.getBearingAccuracyDeg() <= 0)); + bool gps_lat_lng_alt_insane = ((std::abs(log.getLatitude()) > 90) || (std::abs(log.getLongitude()) > 180) || (std::abs(log.getAltitude()) > ALTITUDE_SANITY_CHECK)); + bool gps_vel_insane = (floatlist2vector(log.getVNED()).norm() > TRANS_SANITY_CHECK); - if ((std::abs(log.getLatitude()) > 90) || (std::abs(log.getLongitude()) > 180) || (std::abs(log.getAltitude()) > ALTITUDE_SANITY_CHECK)) { - return; - } - - if (floatlist2vector(log.getVNED()).norm() > TRANS_SANITY_CHECK) { + if (gps_invalid_flag || gps_unreasonable || gps_accuracy_insane || gps_lat_lng_alt_insane || gps_vel_insane){ + this->determine_gps_mode(current_time); return; } // Process message this->last_gps_fix = current_time; + this->gps_mode = true; Geodetic geodetic = { log.getLatitude(), log.getLongitude(), log.getAltitude() }; this->converter = std::make_unique(geodetic); @@ -273,7 +286,7 @@ void Localizer::handle_gps(double current_time, const cereal::GpsLocationData::R MatrixXdr ecef_vel_R = Vector3d::Constant(std::pow(log.getSpeedAccuracy() * 10.0, 2)).asDiagonal(); this->unix_timestamp_millis = log.getTimestamp(); - double gps_est_error = (this->kf->get_x().head(3) - ecef_pos).norm(); + double gps_est_error = (this->kf->get_x().segment(STATE_ECEF_POS_START) - ecef_pos).norm(); VectorXd orientation_ecef = quat2euler(vector2quat(this->kf->get_x().segment(STATE_ECEF_ORIENTATION_START))); VectorXd orientation_ned = ned_euler_from_ecef({ ecef_pos(0), ecef_pos(1), ecef_pos(2) }, orientation_ecef); @@ -290,11 +303,11 @@ void Localizer::handle_gps(double current_time, const cereal::GpsLocationData::R if (ecef_vel.norm() > 5.0 && orientation_error.norm() > 1.0) { LOGE("Locationd vs ubloxLocation orientation difference too large, kalman reset"); - this->reset_kalman(NAN, initial_pose_ecef_quat, ecef_pos); + this->reset_kalman(NAN, initial_pose_ecef_quat, ecef_pos, ecef_vel, ecef_pos_R, ecef_vel_R); this->kf->predict_and_observe(current_time, OBSERVATION_ECEF_ORIENTATION_FROM_GPS, { initial_pose_ecef_quat }); } else if (gps_est_error > 100.0) { LOGE("Locationd vs ubloxLocation position difference too large, kalman reset"); - this->reset_kalman(NAN, initial_pose_ecef_quat, ecef_pos); + this->reset_kalman(NAN, initial_pose_ecef_quat, ecef_pos, ecef_vel, ecef_pos_R, ecef_vel_R); } this->kf->predict_and_observe(current_time, OBSERVATION_ECEF_POS, { ecef_pos }, { ecef_pos_R }); @@ -358,7 +371,8 @@ void Localizer::handle_live_calib(double current_time, const cereal::LiveCalibra void Localizer::reset_kalman(double current_time) { VectorXd init_x = this->kf->get_initial_x(); - this->reset_kalman(current_time, init_x.segment<4>(3), init_x.head(3)); + MatrixXdr init_P = this->kf->get_initial_P(); + this->reset_kalman(current_time, init_x, init_P); } void Localizer::finite_check(double current_time) { @@ -390,13 +404,27 @@ void Localizer::update_reset_tracker() { } } -void Localizer::reset_kalman(double current_time, VectorXd init_orient, VectorXd init_pos) { +void Localizer::reset_kalman(double current_time, VectorXd init_orient, VectorXd init_pos, VectorXd init_vel, MatrixXdr init_pos_R, MatrixXdr init_vel_R) { // too nonlinear to init on completely wrong - VectorXd init_x = this->kf->get_initial_x(); + VectorXd current_x = this->kf->get_x(); + MatrixXdr current_P = this->kf->get_P(); MatrixXdr init_P = this->kf->get_initial_P(); - init_x.segment<4>(3) = init_orient; - init_x.head(3) = init_pos; + MatrixXdr reset_orientation_P = this->kf->get_reset_orientation_P(); + int non_ecef_state_err_len = init_P.rows() - (STATE_ECEF_POS_ERR_LEN + STATE_ECEF_ORIENTATION_ERR_LEN + STATE_ECEF_VELOCITY_ERR_LEN); + current_x.segment(STATE_ECEF_ORIENTATION_START) = init_orient; + current_x.segment(STATE_ECEF_VELOCITY_START) = init_vel; + current_x.segment(STATE_ECEF_POS_START) = init_pos; + + init_P.block(STATE_ECEF_POS_ERR_START, STATE_ECEF_POS_ERR_START).diagonal() = init_pos_R.diagonal(); + init_P.block(STATE_ECEF_ORIENTATION_ERR_START, STATE_ECEF_ORIENTATION_ERR_START).diagonal() = reset_orientation_P.diagonal(); + init_P.block(STATE_ECEF_VELOCITY_ERR_START, STATE_ECEF_VELOCITY_ERR_START).diagonal() = init_vel_R.diagonal(); + init_P.block(STATE_ANGULAR_VELOCITY_ERR_START, STATE_ANGULAR_VELOCITY_ERR_START, non_ecef_state_err_len, non_ecef_state_err_len).diagonal() = current_P.block(STATE_ANGULAR_VELOCITY_ERR_START, STATE_ANGULAR_VELOCITY_ERR_START, non_ecef_state_err_len, non_ecef_state_err_len).diagonal(); + + this->reset_kalman(current_time, current_x, init_P); +} + +void Localizer::reset_kalman(double current_time, VectorXd init_x, MatrixXdr init_P) { this->kf->init_state(init_x, init_P, current_time); this->last_reset_time = current_time; this->reset_tracker += 1.0; @@ -447,14 +475,28 @@ bool Localizer::isGpsOK() { return this->kf->get_filter_time() - this->last_gps_fix < 1.0; } +void Localizer::determine_gps_mode(double current_time) { + // 1. If the pos_std is greater than what's not acceptible and localizer is in gps-mode, reset to no-gps-mode + // 2. If the pos_std is greater than what's not acceptible and localizer is in no-gps-mode, fake obs + // 3. If the pos_std is smaller than what's not acceptible, let gps-mode be whatever it is + VectorXd current_pos_std = this->kf->get_P().block(STATE_ECEF_POS_ERR_START, STATE_ECEF_POS_ERR_START).diagonal().array().sqrt(); + if (current_pos_std.norm() > SANE_GPS_UNCERTAINTY){ + if (this->gps_mode){ + this->gps_mode = false; + this->reset_kalman(current_time); + } + else{ + this->input_fake_gps_observations(current_time); + } + } +} + int Localizer::locationd_thread() { const std::initializer_list service_list = { "gpsLocationExternal", "sensorEvents", "cameraOdometry", "liveCalibration", "carState" }; PubMaster pm({ "liveLocationKalman" }); SubMaster sm(service_list, nullptr, { "gpsLocationExternal" }); - Params params; - while (!do_exit) { sm.update(); for (const char* service : service_list) { @@ -479,8 +521,8 @@ int Localizer::locationd_thread() { std::string lastGPSPosJSON = util::string_format( "{\"latitude\": %.15f, \"longitude\": %.15f, \"altitude\": %.15f}", posGeo(0), posGeo(1), posGeo(2)); - std::thread([¶ms] (const std::string gpsjson) { - params.put("LastGPSPosition", gpsjson); + std::thread([] (const std::string gpsjson) { + Params().put("LastGPSPosition", gpsjson); }, lastGPSPosJSON).detach(); } } @@ -489,7 +531,7 @@ int Localizer::locationd_thread() { } int main() { - set_realtime_priority(5); + util::set_realtime_priority(5); Localizer localizer; return localizer.locationd_thread(); diff --git a/selfdrive/locationd/locationd.h b/selfdrive/locationd/locationd.h index 60fed112c..9bc864bf6 100755 --- a/selfdrive/locationd/locationd.h +++ b/selfdrive/locationd/locationd.h @@ -27,11 +27,13 @@ public: int locationd_thread(); void reset_kalman(double current_time = NAN); - void reset_kalman(double current_time, Eigen::VectorXd init_orient, Eigen::VectorXd init_pos); + void reset_kalman(double current_time, Eigen::VectorXd init_orient, Eigen::VectorXd init_pos, Eigen::VectorXd init_vel, MatrixXdr init_pos_R, MatrixXdr init_vel_R); + void reset_kalman(double current_time, Eigen::VectorXd init_x, MatrixXdr init_P); void finite_check(double current_time = NAN); void time_check(double current_time = NAN); void update_reset_tracker(); bool isGpsOK(); + void determine_gps_mode(double current_time); kj::ArrayPtr get_message_bytes(MessageBuilder& msg_builder, uint64_t logMonoTime, bool inputsOK, bool sensorsOK, bool gpsOK); @@ -49,6 +51,8 @@ public: void handle_cam_odo(double current_time, const cereal::CameraOdometry::Reader& log); void handle_live_calib(double current_time, const cereal::LiveCalibrationData::Reader& log); + void input_fake_gps_observations(double current_time); + private: std::unique_ptr kf; @@ -67,4 +71,5 @@ private: double last_gps_fix = 0; double reset_tracker = 0.0; bool device_fell = false; + bool gps_mode = false; }; diff --git a/selfdrive/locationd/models/live_kf.cc b/selfdrive/locationd/models/live_kf.cc index 541c0f773..5ff0f2699 100755 --- a/selfdrive/locationd/models/live_kf.cc +++ b/selfdrive/locationd/models/live_kf.cc @@ -28,11 +28,14 @@ std::vector> get_vec_mapmat(std::vector& mat_ve } LiveKalman::LiveKalman() { - this->dim_state = 26; - this->dim_state_err = 25; + this->dim_state = live_initial_x.rows(); + this->dim_state_err = live_initial_P_diag.rows(); this->initial_x = live_initial_x; this->initial_P = live_initial_P_diag.asDiagonal(); + this->fake_gps_pos_cov = live_fake_gps_pos_cov_diag.asDiagonal(); + this->fake_gps_vel_cov = live_fake_gps_vel_cov_diag.asDiagonal(); + this->reset_orientation_P = live_reset_orientation_diag.asDiagonal(); this->Q = live_Q_diag.asDiagonal(); for (auto& pair : live_obs_noise_diag) { this->obs_noise[pair.first] = pair.second.asDiagonal(); @@ -87,6 +90,10 @@ std::optional LiveKalman::predict_and_observe(double t, int kind, std: return r; } +void LiveKalman::predict(double t) { + this->filter->predict(t); +} + Eigen::VectorXd LiveKalman::get_initial_x() { return this->initial_x; } @@ -95,6 +102,18 @@ MatrixXdr LiveKalman::get_initial_P() { return this->initial_P; } +MatrixXdr LiveKalman::get_fake_gps_pos_cov() { + return this->fake_gps_pos_cov; +} + +MatrixXdr LiveKalman::get_fake_gps_vel_cov() { + return this->fake_gps_vel_cov; +} + +MatrixXdr LiveKalman::get_reset_orientation_P() { + return this->reset_orientation_P; +} + MatrixXdr LiveKalman::H(VectorXd in) { assert(in.size() == 6); Matrix res; diff --git a/selfdrive/locationd/models/live_kf.h b/selfdrive/locationd/models/live_kf.h index 4cd4756c9..06ec3854c 100755 --- a/selfdrive/locationd/models/live_kf.h +++ b/selfdrive/locationd/models/live_kf.h @@ -36,9 +36,13 @@ public: std::optional predict_and_update_odo_speed(std::vector speed, double t, int kind); std::optional predict_and_update_odo_trans(std::vector trans, double t, int kind); std::optional predict_and_update_odo_rot(std::vector rot, double t, int kind); + void predict(double t); Eigen::VectorXd get_initial_x(); MatrixXdr get_initial_P(); + MatrixXdr get_fake_gps_pos_cov(); + MatrixXdr get_fake_gps_vel_cov(); + MatrixXdr get_reset_orientation_P(); MatrixXdr H(Eigen::VectorXd in); @@ -52,6 +56,9 @@ private: Eigen::VectorXd initial_x; MatrixXdr initial_P; + MatrixXdr fake_gps_pos_cov; + MatrixXdr fake_gps_vel_cov; + MatrixXdr reset_orientation_P; MatrixXdr Q; // process noise std::unordered_map obs_noise; }; diff --git a/selfdrive/locationd/models/live_kf.py b/selfdrive/locationd/models/live_kf.py index 75415a284..fa5294593 100755 --- a/selfdrive/locationd/models/live_kf.py +++ b/selfdrive/locationd/models/live_kf.py @@ -26,10 +26,8 @@ class States(): ECEF_VELOCITY = slice(7, 10) # ecef velocity in m/s ANGULAR_VELOCITY = slice(10, 13) # roll, pitch and yaw rates in device frame in radians/s GYRO_BIAS = slice(13, 16) # roll, pitch and yaw biases - ODO_SCALE = slice(16, 17) # odometer scale - ACCELERATION = slice(17, 20) # Acceleration in device frame in m/s**2 - IMU_OFFSET = slice(20, 23) # imu offset angles in radians - ACC_BIAS = slice(23, 26) + ACCELERATION = slice(16, 19) # Acceleration in device frame in m/s**2 + ACC_BIAS = slice(19, 22) # Acceletometer bias in m/s**2 # Error-state has different slices because it is an ESKF ECEF_POS_ERR = slice(0, 3) @@ -37,10 +35,8 @@ class States(): ECEF_VELOCITY_ERR = slice(6, 9) ANGULAR_VELOCITY_ERR = slice(9, 12) GYRO_BIAS_ERR = slice(12, 15) - ODO_SCALE_ERR = slice(15, 16) - ACCELERATION_ERR = slice(16, 19) - IMU_OFFSET_ERR = slice(19, 22) - ACC_BIAS_ERR = slice(22, 25) + ACCELERATION_ERR = slice(15, 18) + ACC_BIAS_ERR = slice(18, 21) class LiveKalman(): @@ -51,38 +47,37 @@ class LiveKalman(): 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0]) # state covariance - initial_P_diag = np.array([1e3**2, 1e3**2, 1e3**2, - 0.5**2, 0.5**2, 0.5**2, + initial_P_diag = np.array([10**2, 10**2, 10**2, + 0.01**2, 0.01**2, 0.01**2, 10**2, 10**2, 10**2, 1**2, 1**2, 1**2, 1**2, 1**2, 1**2, - 0.02**2, 100**2, 100**2, 100**2, - 0.01**2, 0.01**2, 0.01**2, 0.01**2, 0.01**2, 0.01**2]) + # state covariance when resetting midway in a segment + reset_orientation_diag = np.array([1**2, 1**2, 1**2]) + + # fake observation covariance, to ensure the uncertainty estimate of the filter is under control + fake_gps_pos_cov_diag = np.array([1000**2, 1000**2, 1000**2]) + fake_gps_vel_cov_diag = np.array([10**2, 10**2, 10**2]) + # process noise Q_diag = np.array([0.03**2, 0.03**2, 0.03**2, 0.001**2, 0.001**2, 0.001**2, 0.01**2, 0.01**2, 0.01**2, 0.1**2, 0.1**2, 0.1**2, (0.005 / 100)**2, (0.005 / 100)**2, (0.005 / 100)**2, - (0.02 / 100)**2, 3**2, 3**2, 3**2, - (0.05 / 60)**2, (0.05 / 60)**2, (0.05 / 60)**2, 0.005**2, 0.005**2, 0.005**2]) - obs_noise_diag = {ObservationKind.ODOMETRIC_SPEED: np.array([0.2**2]), - ObservationKind.PHONE_GYRO: np.array([0.025**2, 0.025**2, 0.025**2]), + obs_noise_diag = {ObservationKind.PHONE_GYRO: np.array([0.025**2, 0.025**2, 0.025**2]), ObservationKind.PHONE_ACCEL: np.array([.5**2, .5**2, .5**2]), ObservationKind.CAMERA_ODO_ROTATION: np.array([0.05**2, 0.05**2, 0.05**2]), - ObservationKind.IMU_FRAME: np.array([0.05**2, 0.05**2, 0.05**2]), ObservationKind.NO_ROT: np.array([0.005**2, 0.005**2, 0.005**2]), ObservationKind.NO_ACCEL: np.array([0.05**2, 0.05**2, 0.05**2]), ObservationKind.ECEF_POS: np.array([5**2, 5**2, 5**2]), @@ -105,7 +100,6 @@ class LiveKalman(): vroll, vpitch, vyaw = omega roll_bias, pitch_bias, yaw_bias = state[States.GYRO_BIAS, :] acceleration = state[States.ACCELERATION, :] - imu_angles = state[States.IMU_OFFSET, :] acc_bias = state[States.ACC_BIAS, :] dt = sp.Symbol('dt') @@ -140,7 +134,6 @@ class LiveKalman(): omega_err = state_err[States.ANGULAR_VELOCITY_ERR, :] acceleration_err = state_err[States.ACCELERATION_ERR, :] - # Time derivative of the state error as a function of state error and state quat_err_matrix = euler_rotate(quat_err[0], quat_err[1], quat_err[2]) q_err_dot = quat_err_matrix * quat_rot * (omega + omega_err) @@ -183,7 +176,6 @@ class LiveKalman(): # # Observation functions # - # imu_rot = euler_rotate(*imu_angles) h_gyro_sym = sp.Matrix([ vroll + roll_bias, vpitch + pitch_bias, @@ -194,19 +186,12 @@ class LiveKalman(): h_acc_sym = (gravity + acceleration + acc_bias) h_acc_stationary_sym = acceleration h_phone_rot_sym = sp.Matrix([vroll, vpitch, vyaw]) - - speed = sp.sqrt(vx**2 + vy**2 + vz**2 + 1e-6) - h_speed_sym = sp.Matrix([speed]) - h_pos_sym = sp.Matrix([x, y, z]) h_vel_sym = sp.Matrix([vx, vy, vz]) h_orientation_sym = q - h_imu_frame_sym = sp.Matrix(imu_angles) - h_relative_motion = sp.Matrix(quat_rot.T * v) - obs_eqs = [[h_speed_sym, ObservationKind.ODOMETRIC_SPEED, None], - [h_gyro_sym, ObservationKind.PHONE_GYRO, None], + obs_eqs = [[h_gyro_sym, ObservationKind.PHONE_GYRO, None], [h_phone_rot_sym, ObservationKind.NO_ROT, None], [h_acc_sym, ObservationKind.PHONE_ACCEL, None], [h_pos_sym, ObservationKind.ECEF_POS, None], @@ -214,12 +199,11 @@ class LiveKalman(): [h_orientation_sym, ObservationKind.ECEF_ORIENTATION_FROM_GPS, None], [h_relative_motion, ObservationKind.CAMERA_ODO_TRANSLATION, None], [h_phone_rot_sym, ObservationKind.CAMERA_ODO_ROTATION, None], - [h_imu_frame_sym, ObservationKind.IMU_FRAME, None], [h_acc_stationary_sym, ObservationKind.NO_ACCEL, None]] # this returns a sympy routine for the jacobian of the observation function of the local vel in_vec = sp.MatrixSymbol('in_vec', 6, 1) # roll, pitch, yaw, vx, vy, vz - h = euler_rotate(in_vec[0], in_vec[1], in_vec[2]).T*(sp.Matrix([in_vec[3], in_vec[4], in_vec[5]])) + h = euler_rotate(in_vec[0], in_vec[1], in_vec[2]).T * (sp.Matrix([in_vec[3], in_vec[4], in_vec[5]])) extra_routines = [('H', h.jacobian(in_vec), [in_vec])] gen_code(generated_dir, name, f_sym, dt, state_sym, obs_eqs, dim_state, dim_state_err, eskf_params, extra_routines=extra_routines) @@ -241,6 +225,9 @@ class LiveKalman(): live_kf_header += f"static const Eigen::VectorXd live_initial_x = {numpy2eigenstring(LiveKalman.initial_x)};\n" live_kf_header += f"static const Eigen::VectorXd live_initial_P_diag = {numpy2eigenstring(LiveKalman.initial_P_diag)};\n" + live_kf_header += f"static const Eigen::VectorXd live_fake_gps_pos_cov_diag = {numpy2eigenstring(LiveKalman.fake_gps_pos_cov_diag)};\n" + live_kf_header += f"static const Eigen::VectorXd live_fake_gps_vel_cov_diag = {numpy2eigenstring(LiveKalman.fake_gps_vel_cov_diag)};\n" + live_kf_header += f"static const Eigen::VectorXd live_reset_orientation_diag = {numpy2eigenstring(LiveKalman.reset_orientation_diag)};\n" live_kf_header += f"static const Eigen::VectorXd live_Q_diag = {numpy2eigenstring(LiveKalman.Q_diag)};\n" live_kf_header += "static const std::unordered_map> live_obs_noise_diag = {\n" for kind, noise in LiveKalman.obs_noise_diag.items(): diff --git a/selfdrive/locationd/ublox_msg.cc b/selfdrive/locationd/ublox_msg.cc index 52c3906f1..9e32c7b07 100644 --- a/selfdrive/locationd/ublox_msg.cc +++ b/selfdrive/locationd/ublox_msg.cc @@ -37,11 +37,11 @@ inline bool UbloxMsgParser::valid_cheksum() { ck_b = (ck_b + ck_a) & 0xFF; } if(ck_a != msg_parse_buf[bytes_in_parse_buf - 2]) { - LOGD("Checksum a mismtach: %02X, %02X", ck_a, msg_parse_buf[6]); + LOGD("Checksum a mismatch: %02X, %02X", ck_a, msg_parse_buf[6]); return false; } if(ck_b != msg_parse_buf[bytes_in_parse_buf - 1]) { - LOGD("Checksum b mismtach: %02X, %02X", ck_b, msg_parse_buf[7]); + LOGD("Checksum b mismatch: %02X, %02X", ck_b, msg_parse_buf[7]); return false; } return true; diff --git a/selfdrive/locationd/ubloxd.cc b/selfdrive/locationd/ubloxd.cc index bcf33b3f7..ae07284c8 100644 --- a/selfdrive/locationd/ubloxd.cc +++ b/selfdrive/locationd/ubloxd.cc @@ -17,14 +17,14 @@ int main() { PubMaster pm({"ubloxGnss", "gpsLocationExternal"}); - Context * context = Context::create(); - SubSocket * subscriber = SubSocket::create(context, "ubloxRaw"); + std::unique_ptr context(Context::create()); + std::unique_ptr subscriber(SubSocket::create(context.get(), "ubloxRaw")); assert(subscriber != NULL); subscriber->setTimeout(100); while (!do_exit) { - Message * msg = subscriber->receive(); + std::unique_ptr msg(subscriber->receive()); if (!msg) { if (errno == EINTR) { do_exit = true; @@ -32,7 +32,7 @@ int main() { continue; } - capnp::FlatArrayMessageReader cmsg(aligned_buf.align(msg)); + capnp::FlatArrayMessageReader cmsg(aligned_buf.align(msg.get())); cereal::Event::Reader event = cmsg.getRoot(); auto ubloxRaw = event.getUbloxRaw(); @@ -58,11 +58,7 @@ int main() { } bytes_consumed += bytes_consumed_this_time; } - delete msg; } - delete subscriber; - delete context; - return 0; } diff --git a/selfdrive/loggerd/SConscript b/selfdrive/loggerd/SConscript index 7e41c9d3e..2adcfb846 100644 --- a/selfdrive/loggerd/SConscript +++ b/selfdrive/loggerd/SConscript @@ -24,8 +24,8 @@ if arch == "Darwin": del libs[libs.index('OpenCL')] env['FRAMEWORKS'] = ['OpenCL'] -env.Program(src, LIBS=libs) +env.Program('loggerd', ['main.cc'] + src, LIBS=libs) env.Program('bootlog.cc', LIBS=libs) if GetOption('test'): - env.Program('tests/test_logger', ['tests/test_runner.cc', 'tests/test_logger.cc', env.Object('logger_util', '#/selfdrive/ui/replay/util.cc')], LIBS=[libs] + ['curl', 'crypto']) + env.Program('tests/test_logger', ['tests/test_runner.cc', 'tests/test_loggerd.cc', 'tests/test_logger.cc', env.Object('logger_util', '#/selfdrive/ui/replay/util.cc')] + src, LIBS=[libs] + ['curl', 'crypto', 'bz2']) diff --git a/selfdrive/loggerd/bootlog.cc b/selfdrive/loggerd/bootlog.cc index 481b3ee47..520995837 100644 --- a/selfdrive/loggerd/bootlog.cc +++ b/selfdrive/loggerd/bootlog.cc @@ -56,6 +56,7 @@ static kj::Array build_boot_log() { } int main(int argc, char** argv) { + clear_locks(LOG_ROOT); const std::string path = LOG_ROOT + "/boot/" + logger_get_route_name() + ".bz2"; LOGW("bootlog to %s", path.c_str()); diff --git a/selfdrive/loggerd/logger.cc b/selfdrive/loggerd/logger.cc index a73fefb8c..81cfd131f 100644 --- a/selfdrive/loggerd/logger.cc +++ b/selfdrive/loggerd/logger.cc @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -266,3 +267,15 @@ void lh_close(LoggerHandle* h) { } pthread_mutex_unlock(&h->lock); } + +int clear_locks_fn(const char* fpath, const struct stat *sb, int tyupeflag) { + const char* dot = strrchr(fpath, '.'); + if (dot && strcmp(dot, ".lock") == 0) { + unlink(fpath); + } + return 0; +} + +void clear_locks(const std::string log_root) { + ftw(log_root.c_str(), clear_locks_fn, 16); +} diff --git a/selfdrive/loggerd/logger.h b/selfdrive/loggerd/logger.h index bdda9d691..e85d7810e 100644 --- a/selfdrive/loggerd/logger.h +++ b/selfdrive/loggerd/logger.h @@ -96,3 +96,4 @@ void logger_log(LoggerState *s, uint8_t* data, size_t data_size, bool in_qlog); void lh_log(LoggerHandle* h, uint8_t* data, size_t data_size, bool in_qlog); void lh_close(LoggerHandle* h); +void clear_locks(const std::string log_root); diff --git a/selfdrive/loggerd/loggerd.cc b/selfdrive/loggerd/loggerd.cc index f6d89b809..37f03ef4e 100644 --- a/selfdrive/loggerd/loggerd.cc +++ b/selfdrive/loggerd/loggerd.cc @@ -1,153 +1,44 @@ -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "cereal/messaging/messaging.h" -#include "cereal/services.h" -#include "cereal/visionipc/visionipc.h" -#include "cereal/visionipc/visionipc_client.h" -#include "selfdrive/camerad/cameras/camera_common.h" -#include "selfdrive/common/params.h" -#include "selfdrive/common/swaglog.h" -#include "selfdrive/common/timing.h" -#include "selfdrive/common/util.h" -#include "selfdrive/hardware/hw.h" - -#include "selfdrive/loggerd/encoder.h" -#include "selfdrive/loggerd/logger.h" -#if defined(QCOM) || defined(QCOM2) -#include "selfdrive/loggerd/omx_encoder.h" -#define Encoder OmxEncoder -#else -#include "selfdrive/loggerd/raw_logger.h" -#define Encoder RawLogger -#endif - -namespace { - -constexpr int MAIN_FPS = 20; -const int MAIN_BITRATE = Hardware::TICI() ? 10000000 : 5000000; -const int DCAM_BITRATE = Hardware::TICI() ? MAIN_BITRATE : 2500000; - -#define NO_CAMERA_PATIENCE 500 // fall back to time-based rotation if all cameras are dead - -const bool LOGGERD_TEST = getenv("LOGGERD_TEST"); -const int SEGMENT_LENGTH = LOGGERD_TEST ? atoi(getenv("LOGGERD_SEGMENT_LENGTH")) : 60; +#include "selfdrive/loggerd/loggerd.h" ExitHandler do_exit; -const LogCameraInfo cameras_logged[] = { - { - .type = RoadCam, - .stream_type = VISION_STREAM_YUV_BACK, - .filename = "fcamera.hevc", - .frame_packet_name = "roadCameraState", - .fps = MAIN_FPS, - .bitrate = MAIN_BITRATE, - .is_h265 = true, - .downscale = false, - .has_qcamera = true, - .trigger_rotate = true, - .enable = true, - .record = true, - }, - { - .type = DriverCam, - .stream_type = VISION_STREAM_YUV_FRONT, - .filename = "dcamera.hevc", - .frame_packet_name = "driverCameraState", - .fps = MAIN_FPS, // on EONs, more compressed this way - .bitrate = DCAM_BITRATE, - .is_h265 = true, - .downscale = false, - .has_qcamera = false, - .trigger_rotate = Hardware::TICI(), - .enable = !Hardware::PC(), - .record = Params().getBool("RecordFront"), - }, - { - .type = WideRoadCam, - .stream_type = VISION_STREAM_YUV_WIDE, - .filename = "ecamera.hevc", - .frame_packet_name = "wideRoadCameraState", - .fps = MAIN_FPS, - .bitrate = MAIN_BITRATE, - .is_h265 = true, - .downscale = false, - .has_qcamera = false, - .trigger_rotate = true, - .enable = Hardware::TICI(), - .record = Hardware::TICI(), - }, -}; -const LogCameraInfo qcam_info = { - .filename = "qcamera.ts", - .fps = MAIN_FPS, - .bitrate = 256000, - .is_h265 = false, - .downscale = true, - .frame_width = Hardware::TICI() ? 526 : 480, - .frame_height = Hardware::TICI() ? 330 : 360 // keep pixel count the same? -}; - -struct LoggerdState { - Context *ctx; - LoggerState logger = {}; - char segment_path[4096]; - std::mutex rotate_lock; - std::condition_variable rotate_cv; - std::atomic rotate_segment; - std::atomic last_camera_seen_tms; - std::atomic ready_to_rotate; // count of encoders ready to rotate - int max_waiting = 0; - double last_rotate_tms = 0.; // last rotate time in ms - - // Sync logic for startup - std::atomic encoders_ready = 0; - std::atomic start_frame_id = 0; - bool camera_ready[WideRoadCam + 1] = {}; - bool camera_synced[WideRoadCam + 1] = {}; -}; -LoggerdState s; - // Handle initial encoder syncing by waiting for all encoders to reach the same frame id -bool sync_encoders(LoggerdState *state, CameraType cam_type, uint32_t frame_id) { - if (state->camera_synced[cam_type]) return true; +bool sync_encoders(LoggerdState *s, CameraType cam_type, uint32_t frame_id) { + if (s->camera_synced[cam_type]) return true; - if (state->max_waiting > 1 && state->encoders_ready != state->max_waiting) { + if (s->max_waiting > 1 && s->encoders_ready != s->max_waiting) { // add a small margin to the start frame id in case one of the encoders already dropped the next frame - update_max_atomic(state->start_frame_id, frame_id + 2); - if (std::exchange(state->camera_ready[cam_type], true) == false) { - ++state->encoders_ready; + update_max_atomic(s->start_frame_id, frame_id + 2); + if (std::exchange(s->camera_ready[cam_type], true) == false) { + ++s->encoders_ready; LOGE("camera %d encoder ready", cam_type); } return false; } else { - if (state->max_waiting == 1) update_max_atomic(state->start_frame_id, frame_id); - bool synced = frame_id >= state->start_frame_id; - state->camera_synced[cam_type] = synced; - if (!synced) LOGE("camera %d waiting for frame %d, cur %d", cam_type, (int)state->start_frame_id, frame_id); + if (s->max_waiting == 1) update_max_atomic(s->start_frame_id, frame_id); + bool synced = frame_id >= s->start_frame_id; + s->camera_synced[cam_type] = synced; + if (!synced) LOGE("camera %d waiting for frame %d, cur %d", cam_type, (int)s->start_frame_id, frame_id); return synced; } } -void encoder_thread(const LogCameraInfo &cam_info) { - set_thread_name(cam_info.filename); +bool trigger_rotate_if_needed(LoggerdState *s, int cur_seg, uint32_t frame_id) { + const int frames_per_seg = SEGMENT_LENGTH * MAIN_FPS; + if (cur_seg >= 0 && frame_id >= ((cur_seg + 1) * frames_per_seg) + s->start_frame_id) { + // trigger rotate and wait until the main logger has rotated to the new segment + ++s->ready_to_rotate; + std::unique_lock lk(s->rotate_lock); + s->rotate_cv.wait(lk, [&] { + return s->rotate_segment > cur_seg || do_exit; + }); + return !do_exit; + } + return false; +} + +void encoder_thread(LoggerdState *s, const LogCameraInfo &cam_info) { + util::set_thread_name(cam_info.filename); int cur_seg = -1; int encode_idx = 0; @@ -183,37 +74,29 @@ void encoder_thread(const LogCameraInfo &cam_info) { if (buf == nullptr) continue; if (cam_info.trigger_rotate) { - s.last_camera_seen_tms = millis_since_boot(); - if (!sync_encoders(&s, cam_info.type, extra.frame_id)) { + s->last_camera_seen_tms = millis_since_boot(); + if (!sync_encoders(s, cam_info.type, extra.frame_id)) { continue; } // check if we're ready to rotate - const int frames_per_seg = SEGMENT_LENGTH * MAIN_FPS; - if (cur_seg >= 0 && extra.frame_id >= ((cur_seg+1) * frames_per_seg) + s.start_frame_id) { - // trigger rotate and wait until the main logger has rotated to the new segment - ++s.ready_to_rotate; - std::unique_lock lk(s.rotate_lock); - s.rotate_cv.wait(lk, [&] { - return s.rotate_segment > cur_seg || do_exit; - }); - if (do_exit) break; - } + trigger_rotate_if_needed(s, cur_seg, extra.frame_id); + if (do_exit) break; } // rotate the encoder if the logger is on a newer segment - if (s.rotate_segment > cur_seg) { - cur_seg = s.rotate_segment; + if (s->rotate_segment > cur_seg) { + cur_seg = s->rotate_segment; - LOGW("camera %d rotate encoder to %s", cam_info.type, s.segment_path); + LOGW("camera %d rotate encoder to %s", cam_info.type, s->segment_path); for (auto &e : encoders) { e->encoder_close(); - e->encoder_open(s.segment_path); + e->encoder_open(s->segment_path); } if (lh) { lh_close(lh); } - lh = logger_get_handle(&s.logger); + lh = logger_get_handle(&s->logger); } // encode a frame @@ -266,84 +149,63 @@ void encoder_thread(const LogCameraInfo &cam_info) { } } -int clear_locks_fn(const char* fpath, const struct stat *sb, int tyupeflag) { - const char* dot = strrchr(fpath, '.'); - if (dot && strcmp(dot, ".lock") == 0) { - unlink(fpath); - } - return 0; -} - -void clear_locks() { - ftw(LOG_ROOT.c_str(), clear_locks_fn, 16); -} - -void logger_rotate() { +void logger_rotate(LoggerdState *s) { { - std::unique_lock lk(s.rotate_lock); + std::unique_lock lk(s->rotate_lock); int segment = -1; - int err = logger_next(&s.logger, LOG_ROOT.c_str(), s.segment_path, sizeof(s.segment_path), &segment); + int err = logger_next(&s->logger, LOG_ROOT.c_str(), s->segment_path, sizeof(s->segment_path), &segment); assert(err == 0); - s.rotate_segment = segment; - s.ready_to_rotate = 0; - s.last_rotate_tms = millis_since_boot(); + s->rotate_segment = segment; + s->ready_to_rotate = 0; + s->last_rotate_tms = millis_since_boot(); } - s.rotate_cv.notify_all(); - LOGW((s.logger.part == 0) ? "logging to %s" : "rotated to %s", s.segment_path); + s->rotate_cv.notify_all(); + LOGW((s->logger.part == 0) ? "logging to %s" : "rotated to %s", s->segment_path); } -void rotate_if_needed() { - if (s.ready_to_rotate == s.max_waiting) { - logger_rotate(); +void rotate_if_needed(LoggerdState *s) { + if (s->ready_to_rotate == s->max_waiting) { + logger_rotate(s); } double tms = millis_since_boot(); - if ((tms - s.last_rotate_tms) > SEGMENT_LENGTH * 1000 && - (tms - s.last_camera_seen_tms) > NO_CAMERA_PATIENCE && + if ((tms - s->last_rotate_tms) > SEGMENT_LENGTH * 1000 && + (tms - s->last_camera_seen_tms) > NO_CAMERA_PATIENCE && !LOGGERD_TEST) { LOGW("no camera packet seen. auto rotating"); - logger_rotate(); + logger_rotate(s); } } -} // namespace - -int main(int argc, char** argv) { - if (Hardware::EON()) { - setpriority(PRIO_PROCESS, 0, -20); - } else if (Hardware::TICI()) { - int ret; - ret = set_core_affinity({0, 1, 2, 3}); - assert(ret == 0); - // TODO: why does this impact camerad timings? - //ret = set_realtime_priority(1); - //assert(ret == 0); - } - - clear_locks(); - +void loggerd_thread() { // setup messaging typedef struct QlogState { + std::string name; int counter, freq; } QlogState; std::unordered_map qlog_states; - s.ctx = Context::create(); - Poller * poller = Poller::create(); + std::unique_ptr ctx(Context::create()); + std::unique_ptr poller(Poller::create()); // subscribe to all socks for (const auto& it : services) { if (!it.should_log) continue; - SubSocket * sock = SubSocket::create(s.ctx, it.name); + SubSocket * sock = SubSocket::create(ctx.get(), it.name); assert(sock != NULL); poller->registerSocket(sock); - qlog_states[sock] = {.counter = 0, .freq = it.decimation}; + qlog_states[sock] = { + .name = it.name, + .counter = 0, + .freq = it.decimation, + }; } + LoggerdState s; // init logger logger_init(&s.logger, "rlog", true); - logger_rotate(); + logger_rotate(&s); Params().put("CurrentRoute", s.logger.route_name); // init encoders @@ -351,7 +213,7 @@ int main(int argc, char** argv) { std::vector encoder_threads; for (const auto &cam : cameras_logged) { if (cam.enable) { - encoder_threads.push_back(std::thread(encoder_thread, cam)); + encoder_threads.push_back(std::thread(encoder_thread, &s, cam)); if (cam.trigger_rotate) s.max_waiting++; } } @@ -361,7 +223,10 @@ int main(int argc, char** argv) { while (!do_exit) { // poll for new messages on all sockets for (auto sock : poller->poll(1000)) { + if (do_exit) break; + // drain socket + int count = 0; QlogState &qs = qlog_states[sock]; Message *msg = nullptr; while (!do_exit && (msg = sock->receive(true))) { @@ -370,12 +235,18 @@ int main(int argc, char** argv) { bytes_count += msg->getSize(); delete msg; - rotate_if_needed(); + rotate_if_needed(&s); if ((++msg_count % 1000) == 0) { double seconds = (millis_since_boot() - start_ts) / 1000.0; LOGD("%lu messages, %.2f msg/sec, %.2f KB/sec", msg_count, msg_count / seconds, bytes_count * 0.001 / seconds); } + + count++; + if (count >= 200) { + LOGE("large volume of '%s' messages", qs.name.c_str()); + break; + } } } } @@ -395,8 +266,4 @@ int main(int argc, char** argv) { // messaging cleanup for (auto &[sock, qs] : qlog_states) delete sock; - delete poller; - delete s.ctx; - - return 0; } diff --git a/selfdrive/loggerd/loggerd.h b/selfdrive/loggerd/loggerd.h new file mode 100644 index 000000000..bdf5ef8f9 --- /dev/null +++ b/selfdrive/loggerd/loggerd.h @@ -0,0 +1,131 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cereal/messaging/messaging.h" +#include "cereal/services.h" +#include "cereal/visionipc/visionipc.h" +#include "cereal/visionipc/visionipc_client.h" +#include "selfdrive/camerad/cameras/camera_common.h" +#include "selfdrive/common/params.h" +#include "selfdrive/common/swaglog.h" +#include "selfdrive/common/timing.h" +#include "selfdrive/common/util.h" +#include "selfdrive/hardware/hw.h" + +#include "selfdrive/loggerd/encoder.h" +#include "selfdrive/loggerd/logger.h" +#if defined(QCOM) || defined(QCOM2) +#include "selfdrive/loggerd/omx_encoder.h" +#define Encoder OmxEncoder +#else +#include "selfdrive/loggerd/raw_logger.h" +#define Encoder RawLogger +#endif + +constexpr int MAIN_FPS = 20; +const int MAIN_BITRATE = Hardware::TICI() ? 10000000 : 5000000; +const int DCAM_BITRATE = Hardware::TICI() ? MAIN_BITRATE : 2500000; + +#define NO_CAMERA_PATIENCE 500 // fall back to time-based rotation if all cameras are dead + +const bool LOGGERD_TEST = getenv("LOGGERD_TEST"); +const int SEGMENT_LENGTH = LOGGERD_TEST ? atoi(getenv("LOGGERD_SEGMENT_LENGTH")) : 60; + +struct LogCameraInfo { + CameraType type; + const char *filename; + VisionStreamType stream_type; + int frame_width, frame_height; + int fps; + int bitrate; + bool is_h265; + bool downscale; + bool has_qcamera; + bool trigger_rotate; + bool enable; + bool record; +}; + +const LogCameraInfo cameras_logged[] = { + { + .type = RoadCam, + .stream_type = VISION_STREAM_ROAD, + .filename = "fcamera.hevc", + .fps = MAIN_FPS, + .bitrate = MAIN_BITRATE, + .is_h265 = true, + .downscale = false, + .has_qcamera = true, + .trigger_rotate = true, + .enable = true, + .record = true, + }, + { + .type = DriverCam, + .stream_type = VISION_STREAM_DRIVER, + .filename = "dcamera.hevc", + .fps = MAIN_FPS, // on EONs, more compressed this way + .bitrate = DCAM_BITRATE, + .is_h265 = true, + .downscale = false, + .has_qcamera = false, + .trigger_rotate = Hardware::TICI(), + .enable = !Hardware::PC(), + .record = Params().getBool("RecordFront"), + }, + { + .type = WideRoadCam, + .stream_type = VISION_STREAM_WIDE_ROAD, + .filename = "ecamera.hevc", + .fps = MAIN_FPS, + .bitrate = MAIN_BITRATE, + .is_h265 = true, + .downscale = false, + .has_qcamera = false, + .trigger_rotate = true, + .enable = Hardware::TICI(), + .record = Hardware::TICI(), + }, +}; +const LogCameraInfo qcam_info = { + .filename = "qcamera.ts", + .fps = MAIN_FPS, + .bitrate = 256000, + .is_h265 = false, + .downscale = true, + .frame_width = Hardware::TICI() ? 526 : 480, + .frame_height = Hardware::TICI() ? 330 : 360 // keep pixel count the same? +}; + +struct LoggerdState { + LoggerState logger = {}; + char segment_path[4096]; + std::mutex rotate_lock; + std::condition_variable rotate_cv; + std::atomic rotate_segment; + std::atomic last_camera_seen_tms; + std::atomic ready_to_rotate; // count of encoders ready to rotate + int max_waiting = 0; + double last_rotate_tms = 0.; // last rotate time in ms + + // Sync logic for startup + std::atomic encoders_ready = 0; + std::atomic start_frame_id = 0; + bool camera_ready[WideRoadCam + 1] = {}; + bool camera_synced[WideRoadCam + 1] = {}; +}; + +bool sync_encoders(LoggerdState *s, CameraType cam_type, uint32_t frame_id); +bool trigger_rotate_if_needed(LoggerdState *s, int cur_seg, uint32_t frame_id); +void rotate_if_needed(LoggerdState *s); +void loggerd_thread(); diff --git a/selfdrive/loggerd/main.cc b/selfdrive/loggerd/main.cc new file mode 100644 index 000000000..7069aa706 --- /dev/null +++ b/selfdrive/loggerd/main.cc @@ -0,0 +1,20 @@ +#include "selfdrive/loggerd/loggerd.h" + +#include + +int main(int argc, char** argv) { + if (Hardware::EON()) { + setpriority(PRIO_PROCESS, 0, -20); + } else if (Hardware::TICI()) { + int ret; + ret = util::set_core_affinity({0, 1, 2, 3}); + assert(ret == 0); + // TODO: why does this impact camerad timings? + //ret = util::set_realtime_priority(1); + //assert(ret == 0); + } + + loggerd_thread(); + + return 0; +} diff --git a/selfdrive/loggerd/raw_logger.cc b/selfdrive/loggerd/raw_logger.cc index c490a0813..b0f9f7a9c 100644 --- a/selfdrive/loggerd/raw_logger.cc +++ b/selfdrive/loggerd/raw_logger.cc @@ -127,7 +127,7 @@ int RawLogger::encode_frame(const uint8_t *y_ptr, const uint8_t *u_ptr, const ui frame->data[0] = (uint8_t*)y_ptr; frame->data[1] = (uint8_t*)u_ptr; frame->data[2] = (uint8_t*)v_ptr; - frame->pts = ts; + frame->pts = counter; int ret = counter; diff --git a/selfdrive/loggerd/uploader.py b/selfdrive/loggerd/uploader.py index e99725f0c..10bf218b0 100644 --- a/selfdrive/loggerd/uploader.py +++ b/selfdrive/loggerd/uploader.py @@ -60,8 +60,6 @@ class Uploader(): self.last_resp = None self.last_exc = None - self.raw_size = 0 - self.raw_count = 0 self.immediate_size = 0 self.immediate_count = 0 @@ -72,21 +70,16 @@ class Uploader(): self.immediate_folders = ["crash/", "boot/"] self.immediate_priority = {"qlog.bz2": 0, "qcamera.ts": 1} - self.high_priority = {"rlog.bz2": 0, "fcamera.hevc": 1, "dcamera.hevc": 2, "ecamera.hevc": 3} def get_upload_sort(self, name): if name in self.immediate_priority: return self.immediate_priority[name] - if name in self.high_priority: - return self.high_priority[name] + 100 return 1000 def list_upload_files(self): if not os.path.isdir(self.root): return - self.raw_size = 0 - self.raw_count = 0 self.immediate_size = 0 self.immediate_count = 0 @@ -116,38 +109,27 @@ class Uploader(): if name in self.immediate_priority: self.immediate_count += 1 self.immediate_size += os.path.getsize(fn) - else: - self.raw_count += 1 - self.raw_size += os.path.getsize(fn) except OSError: pass yield (name, key, fn) - def next_file_to_upload(self, with_raw): + def next_file_to_upload(self): upload_files = list(self.list_upload_files()) - # try to upload qlog files first for name, key, fn in upload_files: - if name in self.immediate_priority or any(f in fn for f in self.immediate_folders): + if any(f in fn for f in self.immediate_folders): return (key, fn) - if with_raw: - # then upload the full log files, rear and front camera files - for name, key, fn in upload_files: - if name in self.high_priority: - return (key, fn) - - # then upload other files - for name, key, fn in upload_files: - if not name.endswith('.lock') and not name.endswith(".tmp"): - return (key, fn) + for name, key, fn in upload_files: + if name in self.immediate_priority: + return (key, fn) return None def do_upload(self, key, fn): try: - url_resp = self.api.get("v1.4/"+self.dongle_id+"/upload_url/", timeout=10, path=key, access_token=self.api.get_token()) + url_resp = self.api.get("v1.4/" + self.dongle_id + "/upload_url/", timeout=10, path=key, access_token=self.api.get_token()) if url_resp.status_code == 412: self.last_resp = url_resp return @@ -226,8 +208,6 @@ class Uploader(): def get_msg(self): msg = messaging.new_message("uploaderState") us = msg.uploaderState - us.rawQueueSize = int(self.raw_size / 1e6) - us.rawQueueCount = self.raw_count us.immediateQueueSize = int(self.immediate_size / 1e6) us.immediateQueueCount = self.immediate_count us.lastTime = self.last_time @@ -260,10 +240,7 @@ def uploader_fn(exit_event): time.sleep(60 if offroad else 5) continue - good_internet = network_type in [NetworkType.wifi, NetworkType.ethernet] - allow_raw_upload = params.get_bool("UploadRaw") - - d = uploader.next_file_to_upload(with_raw=allow_raw_upload and good_internet and offroad) + d = uploader.next_file_to_upload() if d is None: # Nothing to upload if allow_sleep: time.sleep(60 if offroad else 5) diff --git a/selfdrive/manager/build.py b/selfdrive/manager/build.py index 66f836ce6..078f18fd2 100755 --- a/selfdrive/manager/build.py +++ b/selfdrive/manager/build.py @@ -12,9 +12,9 @@ from common.spinner import Spinner from common.text_window import TextWindow from selfdrive.hardware import TICI from selfdrive.swaglog import cloudlog, add_file_handler -from selfdrive.version import dirty +from selfdrive.version import get_dirty -MAX_CACHE_SIZE = 2e9 +MAX_CACHE_SIZE = 4e9 if "CI" in os.environ else 2e9 CACHE_DIR = Path("/data/scons_cache" if TICI else "/tmp/scons_cache") TOTAL_SCONS_NODES = 2405 @@ -69,7 +69,7 @@ def build(spinner, dirty=False): else: # Build failed log errors errors = [line.decode('utf8', 'replace') for line in compile_output - if any([err in line for err in [b'error: ', b'not found, needed by target']])] + if any(err in line for err in [b'error: ', b'not found, needed by target'])] error_s = "\n".join(errors) add_file_handler(cloudlog) cloudlog.error("scons build failed\n" + error_s) @@ -77,7 +77,7 @@ def build(spinner, dirty=False): # Show TextWindow spinner.close() if not os.getenv("CI"): - error_s = "\n \n".join(["\n".join(textwrap.wrap(e, 65)) for e in errors]) + error_s = "\n \n".join("\n".join(textwrap.wrap(e, 65)) for e in errors) with TextWindow("openpilot failed to build\n \n" + error_s) as t: t.wait_for_exit() exit(1) @@ -98,4 +98,4 @@ def build(spinner, dirty=False): if __name__ == "__main__" and not PREBUILT: spinner = Spinner() spinner.update_progress(0, 100) - build(spinner, dirty) + build(spinner, get_dirty()) diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index 6c71c2a37..386876b69 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -18,17 +18,20 @@ from selfdrive.manager.process import ensure_running from selfdrive.manager.process_config import managed_processes from selfdrive.athena.registration import register, UNREGISTERED_DONGLE_ID from selfdrive.swaglog import cloudlog, add_file_handler -from selfdrive.version import dirty, get_git_commit, version, origin, branch, commit, \ - terms_version, training_version, comma_remote, \ - get_git_branch, get_git_remote +from selfdrive.version import get_dirty, get_commit, get_version, get_origin, get_short_branch, \ + terms_version, training_version, get_comma_remote + sys.path.append(os.path.join(BASEDIR, "pyextra")) -def manager_init(): +def manager_init(): # update system time from panda set_time(cloudlog) + # save boot log + subprocess.call("./bootlog", cwd=os.path.join(BASEDIR, "selfdrive/loggerd")) + params = Params() params.clear_all(ParamKeyType.CLEAR_ON_MANAGER_START) @@ -67,12 +70,12 @@ def manager_init(): print("WARNING: failed to make /dev/shm") # set version params - params.put("Version", version) + params.put("Version", get_version()) params.put("TermsVersion", terms_version) params.put("TrainingVersion", training_version) - params.put("GitCommit", get_git_commit(default="")) - params.put("GitBranch", get_git_branch(default="")) - params.put("GitRemote", get_git_remote(default="")) + params.put("GitCommit", get_commit(default="")) + params.put("GitBranch", get_short_branch(default="")) + params.put("GitRemote", get_origin(default="")) # set dongle id reg_res = register(show_spinner=True) @@ -83,16 +86,16 @@ def manager_init(): raise Exception(f"Registration failed for device {serial}") os.environ['DONGLE_ID'] = dongle_id # Needed for swaglog - if not dirty: + if not get_dirty(): os.environ['CLEAN'] = '1' - cloudlog.bind_global(dongle_id=dongle_id, version=version, dirty=dirty, + cloudlog.bind_global(dongle_id=dongle_id, version=get_version(), dirty=get_dirty(), device=HARDWARE.get_device_type()) - if comma_remote and not (os.getenv("NOLOG") or os.getenv("NOCRASH") or PC): + if get_comma_remote() and not (os.getenv("NOLOG") or os.getenv("NOCRASH") or PC): crash.init() crash.bind_user(id=dongle_id) - crash.bind_extra(dirty=dirty, origin=origin, branch=branch, commit=commit, + crash.bind_extra(dirty=get_dirty(), origin=get_origin(), branch=get_short_branch(), commit=get_commit(), device=HARDWARE.get_device_type()) @@ -102,8 +105,13 @@ def manager_prepare(): def manager_cleanup(): + # send signals to kill all procs for p in managed_processes.values(): - p.stop() + p.stop(block=False) + + # ensure all are killed + for p in managed_processes.values(): + p.stop(block=True) cloudlog.info("everything is dead") @@ -112,9 +120,6 @@ def manager_thread(): cloudlog.info("manager start") cloudlog.info({"environ": os.environ}) - # save boot log - subprocess.call("./bootlog", cwd=os.path.join(BASEDIR, "selfdrive/loggerd")) - params = Params() ignore = [] @@ -149,8 +154,8 @@ def manager_thread(): started_prev = started - running = ' '.join(["%s%s\u001b[0m" % ("\u001b[32m" if p.proc.is_alive() else "\u001b[31m", p.name) - for p in managed_processes.values() if p.proc]) + running = ' '.join("%s%s\u001b[0m" % ("\u001b[32m" if p.proc.is_alive() else "\u001b[31m", p.name) + for p in managed_processes.values() if p.proc) print(running) cloudlog.debug(running) diff --git a/selfdrive/manager/test/test_manager.py b/selfdrive/manager/test/test_manager.py index 6acda36a2..d16a14503 100755 --- a/selfdrive/manager/test/test_manager.py +++ b/selfdrive/manager/test/test_manager.py @@ -19,6 +19,7 @@ ALL_PROCESSES = [p.name for p in managed_processes.values() if (type(p) is not D class TestManager(unittest.TestCase): def setUp(self): os.environ['PASSIVE'] = '0' + HARDWARE.set_power_save(False) def tearDown(self): manager.manager_cleanup() diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 8e4f2569e..ceb3c560b 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -65,7 +65,7 @@ if use_thneed and arch in ("aarch64", "larch64"): compiler = lenv.Program('thneed/compile', ["thneed/compile.cc"]+common_model, LIBS=libs) cmd = f"cd {Dir('.').abspath} && {compiler[0].abspath} ../../models/supercombo.dlc ../../models/supercombo.thneed --binary" - lib_paths = ':'.join([Dir(p).abspath for p in lenv["LIBPATH"]]) + lib_paths = ':'.join(Dir(p).abspath for p in lenv["LIBPATH"]) cenv = Environment(ENV={'LD_LIBRARY_PATH': f"{lib_paths}:{lenv['ENV']['LD_LIBRARY_PATH']}"}) cenv.Command("../../models/supercombo.thneed", ["../../models/supercombo.dlc", compiler], cmd) diff --git a/selfdrive/modeld/dmonitoringmodeld.cc b/selfdrive/modeld/dmonitoringmodeld.cc index 6ae6e780c..c85c05c9d 100644 --- a/selfdrive/modeld/dmonitoringmodeld.cc +++ b/selfdrive/modeld/dmonitoringmodeld.cc @@ -39,7 +39,7 @@ int main(int argc, char **argv) { DMonitoringModelState model; dmonitoring_init(&model); - VisionIpcClient vipc_client = VisionIpcClient("camerad", VISION_STREAM_YUV_FRONT, true); + VisionIpcClient vipc_client = VisionIpcClient("camerad", VISION_STREAM_DRIVER, true); while (!do_exit && !vipc_client.connect(false)) { util::sleep_for(100); } diff --git a/selfdrive/modeld/modeld.cc b/selfdrive/modeld/modeld.cc index 3d3e43590..9e1750eea 100644 --- a/selfdrive/modeld/modeld.cc +++ b/selfdrive/modeld/modeld.cc @@ -20,8 +20,8 @@ mat3 cur_transform; std::mutex transform_lock; void calibration_thread(bool wide_camera) { - set_thread_name("calibration"); - set_realtime_priority(50); + util::set_thread_name("calibration"); + util::set_realtime_priority(50); SubMaster sm({"liveCalibration"}); @@ -104,7 +104,7 @@ void run_model(ModelState &model, VisionIpcClient &vipc_client) { } double mt1 = millis_since_boot(); - ModelDataRaw model_buf = model_eval_frame(&model, buf->buf_cl, buf->width, buf->height, + ModelOutput *model_output = model_eval_frame(&model, buf->buf_cl, buf->width, buf->height, model_transform, vec_desire); double mt2 = millis_since_boot(); float model_execution_time = (mt2 - mt1) / 1000.0; @@ -119,9 +119,9 @@ void run_model(ModelState &model, VisionIpcClient &vipc_client) { float frame_drop_ratio = frames_dropped / (1 + frames_dropped); - model_publish(pm, extra.frame_id, frame_id, frame_drop_ratio, model_buf, extra.timestamp_eof, model_execution_time, + model_publish(pm, extra.frame_id, frame_id, frame_drop_ratio, *model_output, extra.timestamp_eof, model_execution_time, kj::ArrayPtr(model.output.data(), model.output.size())); - posenet_publish(pm, extra.frame_id, vipc_dropped_frames, model_buf, extra.timestamp_eof); + posenet_publish(pm, extra.frame_id, vipc_dropped_frames, *model_output, extra.timestamp_eof); //printf("model process: %.2fms, from last %.2fms, vipc_frame_id %u, frame_id, %u, frame_drop %.3f\n", mt2 - mt1, mt1 - last, extra.frame_id, frame_id, frame_drop_ratio); last = mt1; @@ -133,9 +133,9 @@ void run_model(ModelState &model, VisionIpcClient &vipc_client) { int main(int argc, char **argv) { if (!Hardware::PC()) { int ret; - ret = set_realtime_priority(54); + ret = util::set_realtime_priority(54); assert(ret == 0); - set_core_affinity({Hardware::EON() ? 2 : 7}); + util::set_core_affinity({Hardware::EON() ? 2 : 7}); assert(ret == 0); } @@ -153,7 +153,7 @@ int main(int argc, char **argv) { model_init(&model, device_id, context); LOGW("models loaded, modeld starting"); - VisionIpcClient vipc_client = VisionIpcClient("camerad", wide_camera ? VISION_STREAM_YUV_WIDE : VISION_STREAM_YUV_BACK, true, device_id, context); + VisionIpcClient vipc_client = VisionIpcClient("camerad", wide_camera ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD, true, device_id, context); while (!do_exit && !vipc_client.connect(false)) { util::sleep_for(100); } diff --git a/selfdrive/modeld/models/commonmodel.h b/selfdrive/modeld/models/commonmodel.h index d7904489b..19af75eef 100644 --- a/selfdrive/modeld/models/commonmodel.h +++ b/selfdrive/modeld/models/commonmodel.h @@ -16,10 +16,6 @@ #include "selfdrive/modeld/transforms/loadyuv.h" #include "selfdrive/modeld/transforms/transform.h" -constexpr int MODEL_WIDTH = 512; -constexpr int MODEL_HEIGHT = 256; -constexpr int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT * 3 / 2; - const bool send_raw_pred = getenv("SEND_RAW_PRED") != NULL; void softmax(const float* input, float* output, size_t len); @@ -27,14 +23,17 @@ float softplus(float input); float sigmoid(float input); class ModelFrame { - public: +public: ModelFrame(cl_device_id device_id, cl_context context); ~ModelFrame(); float* prepare(cl_mem yuv_cl, int width, int height, const mat3& transform, cl_mem *output); + const int MODEL_WIDTH = 512; + const int MODEL_HEIGHT = 256; + const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT * 3 / 2; const int buf_size = MODEL_FRAME_SIZE * 2; - private: +private: Transform transform; LoadYUVState loadyuv; cl_command_queue q; diff --git a/selfdrive/modeld/models/dmonitoring.cc b/selfdrive/modeld/models/dmonitoring.cc index 9d87e320a..2acdf7d2b 100644 --- a/selfdrive/modeld/models/dmonitoring.cc +++ b/selfdrive/modeld/models/dmonitoring.cc @@ -3,15 +3,15 @@ #include "libyuv.h" #include "selfdrive/common/mat.h" +#include "selfdrive/common/modeldata.h" #include "selfdrive/common/params.h" #include "selfdrive/common/timing.h" #include "selfdrive/hardware/hw.h" #include "selfdrive/modeld/models/dmonitoring.h" -#define MODEL_WIDTH 320 -#define MODEL_HEIGHT 640 -#define FULL_W 852 // should get these numbers from camerad +constexpr int MODEL_WIDTH = 320; +constexpr int MODEL_HEIGHT = 640; template static inline T *get_buffer(std::vector &buf, const size_t size) { @@ -67,21 +67,15 @@ void crop_yuv(uint8_t *raw, int width, int height, uint8_t *y, uint8_t *u, uint8 DMonitoringResult dmonitoring_eval_frame(DMonitoringModelState* s, void* stream_buf, int width, int height) { Rect crop_rect; - if (Hardware::TICI()) { - const int full_width_tici = 1928; - const int full_height_tici = 1208; - const int adapt_width_tici = 954; - const int x_offset_tici = -72; - const int y_offset_tici = -144; - const int cropped_height = adapt_width_tici / 1.33; - crop_rect = {full_width_tici / 2 - adapt_width_tici / 2 + x_offset_tici, - full_height_tici / 2 - cropped_height / 2 + y_offset_tici, + if (width == TICI_CAM_WIDTH) { + const int cropped_height = tici_dm_crop::width / 1.33; + crop_rect = {width / 2 - tici_dm_crop::width / 2 + tici_dm_crop::x_offset, + height / 2 - cropped_height / 2 + tici_dm_crop::y_offset, cropped_height / 2, cropped_height}; if (!s->is_rhd) { - crop_rect.x += adapt_width_tici - crop_rect.w; + crop_rect.x += tici_dm_crop::width - crop_rect.w; } - } else { const int adapt_width = 372; crop_rect = {0, 0, adapt_width, height}; diff --git a/selfdrive/modeld/models/driving.cc b/selfdrive/modeld/models/driving.cc index a2ff9e811..629e1d7ed 100644 --- a/selfdrive/modeld/models/driving.cc +++ b/selfdrive/modeld/models/driving.cc @@ -16,8 +16,8 @@ constexpr float FCW_THRESHOLD_5MS2_HIGH = 0.15; constexpr float FCW_THRESHOLD_5MS2_LOW = 0.05; constexpr float FCW_THRESHOLD_3MS2 = 0.7; -float prev_brake_5ms2_probs[5] = {0,0,0,0,0}; -float prev_brake_3ms2_probs[3] = {0,0,0}; +std::array prev_brake_5ms2_probs = {0,0,0,0,0}; +std::array prev_brake_3ms2_probs = {0,0,0}; // #define DUMP_YUV @@ -52,7 +52,7 @@ void model_init(ModelState* s, cl_device_id device_id, cl_context context) { #endif } -ModelDataRaw model_eval_frame(ModelState* s, cl_mem yuv_cl, int width, int height, +ModelOutput* model_eval_frame(ModelState* s, cl_mem yuv_cl, int width, int height, const mat3 &transform, float *desire_in) { #ifdef DESIRE if (desire_in != NULL) { @@ -69,35 +69,18 @@ ModelDataRaw model_eval_frame(ModelState* s, cl_mem yuv_cl, int width, int heigh } #endif - //for (int i = 0; i < NET_OUTPUT_SIZE; i++) { printf("%f ", s->output[i]); } printf("\n"); - // if getInputBuf is not NULL, net_input_buf will be auto net_input_buf = s->frame->prepare(yuv_cl, width, height, transform, static_cast(s->m->getInputBuf())); s->m->execute(net_input_buf, s->frame->buf_size); - // net outputs - ModelDataRaw net_outputs { - .plans = (ModelDataRawPlans*)&s->output[PLAN_IDX], - .lane_lines = (ModelDataRawLaneLines*)&s->output[LL_IDX], - .road_edges = (ModelDataRawRoadEdges*)&s->output[RE_IDX], - .leads = (ModelDataRawLeads*)&s->output[LEAD_IDX], - .meta = &s->output[DESIRE_STATE_IDX], - .pose = (ModelDataRawPose*)&s->output[POSE_IDX], - }; - return net_outputs; + return (ModelOutput*)&s->output; } void model_free(ModelState* s) { delete s->frame; } -void fill_sigmoid(const float *input, float *output, int len, int stride) { - for (int i=0; i lead_t = {0.0, 2.0, 4.0, 6.0, 8.0, 10.0}; const auto &best_prediction = leads.get_best_prediction(t_idx); lead.setProb(sigmoid(leads.prob[t_idx])); @@ -125,56 +108,54 @@ void fill_lead(cereal::ModelDataV2::LeadDataV3::Builder lead, const ModelDataRaw lead.setAStd(to_kj_array_ptr(lead_a_std)); } -void fill_meta(cereal::ModelDataV2::MetaData::Builder meta, const float *meta_data) { - float desire_state_softmax[DESIRE_LEN]; - float desire_pred_softmax[4*DESIRE_LEN]; - softmax(&meta_data[0], desire_state_softmax, DESIRE_LEN); - for (int i=0; i<4; i++) { - softmax(&meta_data[DESIRE_LEN + OTHER_META_SIZE + i*DESIRE_LEN], - &desire_pred_softmax[i*DESIRE_LEN], DESIRE_LEN); +void fill_meta(cereal::ModelDataV2::MetaData::Builder meta, const ModelOutputMeta &meta_data) { + std::array desire_state_softmax; + softmax(meta_data.desire_state_prob.array.data(), desire_state_softmax.data(), DESIRE_LEN); + + std::array desire_pred_softmax; + for (int i=0; i lat_long_t = {2,4,6,8,10}; + std::array gas_disengage_sigmoid, brake_disengage_sigmoid, steer_override_sigmoid, + brake_3ms2_sigmoid, brake_4ms2_sigmoid, brake_5ms2_sigmoid; + for (int i=0; i threshold; } - for (int i=0; i<3; i++) { + for (int i=0; i FCW_THRESHOLD_3MS2; } auto disengage = meta.initDisengagePredictions(); - disengage.setT({2,4,6,8,10}); - disengage.setGasDisengageProbs(gas_disengage_sigmoid); - disengage.setBrakeDisengageProbs(brake_disengage_sigmoid); - disengage.setSteerOverrideProbs(steer_override_sigmoid); - disengage.setBrake3MetersPerSecondSquaredProbs(brake_3ms2_sigmoid); - disengage.setBrake4MetersPerSecondSquaredProbs(brake_4ms2_sigmoid); - disengage.setBrake5MetersPerSecondSquaredProbs(brake_5ms2_sigmoid); + disengage.setT(to_kj_array_ptr(lat_long_t)); + disengage.setGasDisengageProbs(to_kj_array_ptr(gas_disengage_sigmoid)); + disengage.setBrakeDisengageProbs(to_kj_array_ptr(brake_disengage_sigmoid)); + disengage.setSteerOverrideProbs(to_kj_array_ptr(steer_override_sigmoid)); + disengage.setBrake3MetersPerSecondSquaredProbs(to_kj_array_ptr(brake_3ms2_sigmoid)); + disengage.setBrake4MetersPerSecondSquaredProbs(to_kj_array_ptr(brake_4ms2_sigmoid)); + disengage.setBrake5MetersPerSecondSquaredProbs(to_kj_array_ptr(brake_5ms2_sigmoid)); - meta.setEngagedProb(sigmoid(meta_data[DESIRE_LEN])); - meta.setDesirePrediction(desire_pred_softmax); - meta.setDesireState(desire_state_softmax); + meta.setEngagedProb(sigmoid(meta_data.engaged_prob)); + meta.setDesirePrediction(to_kj_array_ptr(desire_pred_softmax)); + meta.setDesireState(to_kj_array_ptr(desire_state_softmax)); meta.setHardBrakePredicted(above_fcw_threshold); } @@ -197,7 +178,7 @@ void fill_xyzt(cereal::ModelDataV2::XYZTData::Builder xyzt, const std::array pos_x, pos_y, pos_z; std::array pos_x_std, pos_y_std, pos_z_std; std::array vel_x, vel_y, vel_z; @@ -229,7 +210,7 @@ void fill_plan(cereal::ModelDataV2::Builder &framed, const ModelDataRawPlanPredi } void fill_lane_lines(cereal::ModelDataV2::Builder &framed, const std::array &plan_t, - const ModelDataRawLaneLines &lanes) { + const ModelOutputLaneLines &lanes) { std::array left_far_y, left_far_z; std::array left_near_y, left_near_z; std::array right_near_y, right_near_z; @@ -267,7 +248,7 @@ void fill_lane_lines(cereal::ModelDataV2::Builder &framed, const std::array &plan_t, - const ModelDataRawRoadEdges &edges) { + const ModelOutputRoadEdges &edges) { std::array left_y, left_z; std::array right_y, right_z; for (int j=0; jget_best_prediction(); +void fill_model(cereal::ModelDataV2::Builder &framed, const ModelOutput &net_outputs) { + const auto &best_plan = net_outputs.plans.get_best_prediction(); std::array plan_t; std::fill_n(plan_t.data(), plan_t.size(), NAN); plan_t[0] = 0.0; @@ -311,8 +292,8 @@ void fill_model(cereal::ModelDataV2::Builder &framed, const ModelDataRaw &net_ou } fill_plan(framed, best_plan); - fill_lane_lines(framed, plan_t, *net_outputs.lane_lines); - fill_road_edges(framed, plan_t, *net_outputs.road_edges); + fill_lane_lines(framed, plan_t, net_outputs.lane_lines); + fill_road_edges(framed, plan_t, net_outputs.road_edges); // meta fill_meta(framed.initMeta(), net_outputs.meta); @@ -321,12 +302,12 @@ void fill_model(cereal::ModelDataV2::Builder &framed, const ModelDataRaw &net_ou auto leads = framed.initLeadsV3(LEAD_MHP_SELECTION); std::array t_offsets = {0.0, 2.0, 4.0}; for (int i=0; i raw_pred) { const uint32_t frame_age = (frame_id > vipc_frame_id) ? (frame_id - vipc_frame_id) : 0; MessageBuilder msg; @@ -344,12 +325,12 @@ void model_publish(PubMaster &pm, uint32_t vipc_frame_id, uint32_t frame_id, flo } void posenet_publish(PubMaster &pm, uint32_t vipc_frame_id, uint32_t vipc_dropped_frames, - const ModelDataRaw &net_outputs, uint64_t timestamp_eof) { + const ModelOutput &net_outputs, uint64_t timestamp_eof) { MessageBuilder msg; - auto v_mean = net_outputs.pose->velocity_mean; - auto r_mean = net_outputs.pose->rotation_mean; - auto v_std = net_outputs.pose->velocity_std; - auto r_std = net_outputs.pose->rotation_std; + auto v_mean = net_outputs.pose.velocity_mean; + auto r_mean = net_outputs.pose.rotation_mean; + auto v_std = net_outputs.pose.velocity_std; + auto r_std = net_outputs.pose.rotation_std; auto posenetd = msg.initEvent(vipc_dropped_frames < 1).initCameraOdometry(); posenetd.setTrans({v_mean.x, v_mean.y, v_mean.z}); diff --git a/selfdrive/modeld/models/driving.h b/selfdrive/modeld/models/driving.h index c6d3e00ac..5749fb5ec 100644 --- a/selfdrive/modeld/models/driving.h +++ b/selfdrive/modeld/models/driving.h @@ -16,78 +16,54 @@ #include "selfdrive/modeld/runners/run.h" constexpr int DESIRE_LEN = 8; +constexpr int DESIRE_PRED_LEN = 4; constexpr int TRAFFIC_CONVENTION_LEN = 2; constexpr int MODEL_FREQ = 20; -constexpr int DESIRE_PRED_SIZE = 32; -constexpr int OTHER_META_SIZE = 48; -constexpr int NUM_META_INTERVALS = 5; +constexpr int DISENGAGE_LEN = 5; +constexpr int BLINKER_LEN = 6; constexpr int META_STRIDE = 7; constexpr int PLAN_MHP_N = 5; -constexpr int PLAN_MHP_VALS = 15*33; -constexpr int PLAN_MHP_SELECTION = 1; -constexpr int PLAN_MHP_GROUP_SIZE = (2*PLAN_MHP_VALS + PLAN_MHP_SELECTION); constexpr int LEAD_MHP_N = 2; constexpr int LEAD_TRAJ_LEN = 6; constexpr int LEAD_PRED_DIM = 4; -constexpr int LEAD_MHP_VALS = LEAD_PRED_DIM*LEAD_TRAJ_LEN; constexpr int LEAD_MHP_SELECTION = 3; -constexpr int LEAD_MHP_GROUP_SIZE = (2*LEAD_MHP_VALS + LEAD_MHP_SELECTION); -constexpr int POSE_SIZE = 12; - -constexpr int PLAN_IDX = 0; -constexpr int LL_IDX = PLAN_IDX + PLAN_MHP_N*PLAN_MHP_GROUP_SIZE; -constexpr int LL_PROB_IDX = LL_IDX + 4*2*2*33; -constexpr int RE_IDX = LL_PROB_IDX + 8; -constexpr int LEAD_IDX = RE_IDX + 2*2*2*33; -constexpr int LEAD_PROB_IDX = LEAD_IDX + LEAD_MHP_N*(LEAD_MHP_GROUP_SIZE); -constexpr int DESIRE_STATE_IDX = LEAD_PROB_IDX + 3; -constexpr int META_IDX = DESIRE_STATE_IDX + DESIRE_LEN; -constexpr int POSE_IDX = META_IDX + OTHER_META_SIZE + DESIRE_PRED_SIZE; -constexpr int OUTPUT_SIZE = POSE_IDX + POSE_SIZE; -#ifdef TEMPORAL - constexpr int TEMPORAL_SIZE = 512; -#else - constexpr int TEMPORAL_SIZE = 0; -#endif -constexpr int NET_OUTPUT_SIZE = OUTPUT_SIZE + TEMPORAL_SIZE; - -struct ModelDataRawXYZ { +struct ModelOutputXYZ { float x; float y; float z; }; -static_assert(sizeof(ModelDataRawXYZ) == sizeof(float)*3); +static_assert(sizeof(ModelOutputXYZ) == sizeof(float)*3); -struct ModelDataRawYZ { +struct ModelOutputYZ { float y; float z; }; -static_assert(sizeof(ModelDataRawYZ) == sizeof(float)*2); +static_assert(sizeof(ModelOutputYZ) == sizeof(float)*2); -struct ModelDataRawPlanElement { - ModelDataRawXYZ position; - ModelDataRawXYZ velocity; - ModelDataRawXYZ acceleration; - ModelDataRawXYZ rotation; - ModelDataRawXYZ rotation_rate; +struct ModelOutputPlanElement { + ModelOutputXYZ position; + ModelOutputXYZ velocity; + ModelOutputXYZ acceleration; + ModelOutputXYZ rotation; + ModelOutputXYZ rotation_rate; }; -static_assert(sizeof(ModelDataRawPlanElement) == sizeof(ModelDataRawXYZ)*5); +static_assert(sizeof(ModelOutputPlanElement) == sizeof(ModelOutputXYZ)*5); -struct ModelDataRawPlanPrediction { - std::array mean; - std::array std; +struct ModelOutputPlanPrediction { + std::array mean; + std::array std; float prob; }; -static_assert(sizeof(ModelDataRawPlanPrediction) == (sizeof(ModelDataRawPlanElement)*TRAJECTORY_SIZE*2) + sizeof(float)); +static_assert(sizeof(ModelOutputPlanPrediction) == (sizeof(ModelOutputPlanElement)*TRAJECTORY_SIZE*2) + sizeof(float)); -struct ModelDataRawPlans { - std::array prediction; +struct ModelOutputPlans { + std::array prediction; - constexpr const ModelDataRawPlanPrediction &get_best_prediction() const { + constexpr const ModelOutputPlanPrediction &get_best_prediction() const { int max_idx = 0; for (int i = 1; i < prediction.size(); i++) { if (prediction[i].prob > prediction[max_idx].prob) { @@ -97,69 +73,69 @@ struct ModelDataRawPlans { return prediction[max_idx]; } }; -static_assert(sizeof(ModelDataRawPlans) == sizeof(ModelDataRawPlanPrediction)*PLAN_MHP_N); +static_assert(sizeof(ModelOutputPlans) == sizeof(ModelOutputPlanPrediction)*PLAN_MHP_N); -struct ModelDataRawLinesXY { - std::array left_far; - std::array left_near; - std::array right_near; - std::array right_far; +struct ModelOutputLinesXY { + std::array left_far; + std::array left_near; + std::array right_near; + std::array right_far; }; -static_assert(sizeof(ModelDataRawLinesXY) == sizeof(ModelDataRawYZ)*TRAJECTORY_SIZE*4); +static_assert(sizeof(ModelOutputLinesXY) == sizeof(ModelOutputYZ)*TRAJECTORY_SIZE*4); -struct ModelDataRawLineProbVal { +struct ModelOutputLineProbVal { float val_deprecated; float val; }; -static_assert(sizeof(ModelDataRawLineProbVal) == sizeof(float)*2); +static_assert(sizeof(ModelOutputLineProbVal) == sizeof(float)*2); -struct ModelDataRawLinesProb { - ModelDataRawLineProbVal left_far; - ModelDataRawLineProbVal left_near; - ModelDataRawLineProbVal right_near; - ModelDataRawLineProbVal right_far; +struct ModelOutputLinesProb { + ModelOutputLineProbVal left_far; + ModelOutputLineProbVal left_near; + ModelOutputLineProbVal right_near; + ModelOutputLineProbVal right_far; }; -static_assert(sizeof(ModelDataRawLinesProb) == sizeof(ModelDataRawLineProbVal)*4); +static_assert(sizeof(ModelOutputLinesProb) == sizeof(ModelOutputLineProbVal)*4); -struct ModelDataRawLaneLines { - ModelDataRawLinesXY mean; - ModelDataRawLinesXY std; - ModelDataRawLinesProb prob; +struct ModelOutputLaneLines { + ModelOutputLinesXY mean; + ModelOutputLinesXY std; + ModelOutputLinesProb prob; }; -static_assert(sizeof(ModelDataRawLaneLines) == (sizeof(ModelDataRawLinesXY)*2) + sizeof(ModelDataRawLinesProb)); +static_assert(sizeof(ModelOutputLaneLines) == (sizeof(ModelOutputLinesXY)*2) + sizeof(ModelOutputLinesProb)); -struct ModelDataRawEdgessXY { - std::array left; - std::array right; +struct ModelOutputEdgessXY { + std::array left; + std::array right; }; -static_assert(sizeof(ModelDataRawEdgessXY) == sizeof(ModelDataRawYZ)*TRAJECTORY_SIZE*2); +static_assert(sizeof(ModelOutputEdgessXY) == sizeof(ModelOutputYZ)*TRAJECTORY_SIZE*2); -struct ModelDataRawRoadEdges { - ModelDataRawEdgessXY mean; - ModelDataRawEdgessXY std; +struct ModelOutputRoadEdges { + ModelOutputEdgessXY mean; + ModelOutputEdgessXY std; }; -static_assert(sizeof(ModelDataRawRoadEdges) == (sizeof(ModelDataRawEdgessXY)*2)); +static_assert(sizeof(ModelOutputRoadEdges) == (sizeof(ModelOutputEdgessXY)*2)); -struct ModelDataRawLeadElement { +struct ModelOutputLeadElement { float x; float y; float velocity; float acceleration; }; -static_assert(sizeof(ModelDataRawLeadElement) == sizeof(float)*4); +static_assert(sizeof(ModelOutputLeadElement) == sizeof(float)*4); -struct ModelDataRawLeadPrediction { - std::array mean; - std::array std; +struct ModelOutputLeadPrediction { + std::array mean; + std::array std; std::array prob; }; -static_assert(sizeof(ModelDataRawLeadPrediction) == (sizeof(ModelDataRawLeadElement)*LEAD_TRAJ_LEN*2) + (sizeof(float)*LEAD_MHP_SELECTION)); +static_assert(sizeof(ModelOutputLeadPrediction) == (sizeof(ModelOutputLeadElement)*LEAD_TRAJ_LEN*2) + (sizeof(float)*LEAD_MHP_SELECTION)); -struct ModelDataRawLeads { - std::array prediction; +struct ModelOutputLeads { + std::array prediction; std::array prob; - constexpr const ModelDataRawLeadPrediction &get_best_prediction(int t_idx) const { + constexpr const ModelOutputLeadPrediction &get_best_prediction(int t_idx) const { int max_idx = 0; for (int i = 1; i < prediction.size(); i++) { if (prediction[i].prob[t_idx] > prediction[max_idx].prob[t_idx]) { @@ -169,26 +145,77 @@ struct ModelDataRawLeads { return prediction[max_idx]; } }; -static_assert(sizeof(ModelDataRawLeads) == (sizeof(ModelDataRawLeadPrediction)*LEAD_MHP_N) + (sizeof(float)*LEAD_MHP_SELECTION)); +static_assert(sizeof(ModelOutputLeads) == (sizeof(ModelOutputLeadPrediction)*LEAD_MHP_N) + (sizeof(float)*LEAD_MHP_SELECTION)); -struct ModelDataRawPose { - ModelDataRawXYZ velocity_mean; - ModelDataRawXYZ rotation_mean; - ModelDataRawXYZ velocity_std; - ModelDataRawXYZ rotation_std; +struct ModelOutputPose { + ModelOutputXYZ velocity_mean; + ModelOutputXYZ rotation_mean; + ModelOutputXYZ velocity_std; + ModelOutputXYZ rotation_std; }; -static_assert(sizeof(ModelDataRawPose) == sizeof(ModelDataRawXYZ)*4); +static_assert(sizeof(ModelOutputPose) == sizeof(ModelOutputXYZ)*4); -struct ModelDataRaw { - const ModelDataRawPlans *const plans; - const ModelDataRawLaneLines *const lane_lines; - const ModelDataRawRoadEdges *const road_edges; - const ModelDataRawLeads *const leads; - const float *const desire_state; - const float *const meta; - const float *const desire_pred; - const ModelDataRawPose *const pose; +struct ModelOutputDisengageProb { + float gas_disengage; + float brake_disengage; + float steer_override; + float brake_3ms2; + float brake_4ms2; + float brake_5ms2; + float gas_pressed; }; +static_assert(sizeof(ModelOutputDisengageProb) == sizeof(float)*7); + +struct ModelOutputBlinkerProb { + float left; + float right; +}; +static_assert(sizeof(ModelOutputBlinkerProb) == sizeof(float)*2); + +struct ModelOutputDesireProb { + union { + struct { + float none; + float turn_left; + float turn_right; + float lane_change_left; + float lane_change_right; + float keep_left; + float keep_right; + float null; + }; + struct { + std::array array; + }; + }; +}; +static_assert(sizeof(ModelOutputDesireProb) == sizeof(float)*DESIRE_LEN); + +struct ModelOutputMeta { + ModelOutputDesireProb desire_state_prob; + float engaged_prob; + std::array disengage_prob; + std::array blinker_prob; + std::array desire_pred_prob; +}; +static_assert(sizeof(ModelOutputMeta) == sizeof(ModelOutputDesireProb) + sizeof(float) + (sizeof(ModelOutputDisengageProb)*DISENGAGE_LEN) + (sizeof(ModelOutputBlinkerProb)*BLINKER_LEN) + (sizeof(ModelOutputDesireProb)*DESIRE_PRED_LEN)); + +struct ModelOutput { + const ModelOutputPlans plans; + const ModelOutputLaneLines lane_lines; + const ModelOutputRoadEdges road_edges; + const ModelOutputLeads leads; + const ModelOutputMeta meta; + const ModelOutputPose pose; +}; + +constexpr int OUTPUT_SIZE = sizeof(ModelOutput) / sizeof(float); +#ifdef TEMPORAL + constexpr int TEMPORAL_SIZE = 512; +#else + constexpr int TEMPORAL_SIZE = 0; +#endif +constexpr int NET_OUTPUT_SIZE = OUTPUT_SIZE + TEMPORAL_SIZE; // TODO: convert remaining arrays to std::array and update model runners struct ModelState { @@ -205,12 +232,12 @@ struct ModelState { }; void model_init(ModelState* s, cl_device_id device_id, cl_context context); -ModelDataRaw model_eval_frame(ModelState* s, cl_mem yuv_cl, int width, int height, +ModelOutput *model_eval_frame(ModelState* s, cl_mem yuv_cl, int width, int height, const mat3 &transform, float *desire_in); void model_free(ModelState* s); void poly_fit(float *in_pts, float *in_stds, float *out); void model_publish(PubMaster &pm, uint32_t vipc_frame_id, uint32_t frame_id, float frame_drop, - const ModelDataRaw &net_outputs, uint64_t timestamp_eof, + const ModelOutput &net_outputs, uint64_t timestamp_eof, float model_execution_time, kj::ArrayPtr raw_pred); void posenet_publish(PubMaster &pm, uint32_t vipc_frame_id, uint32_t vipc_dropped_frames, - const ModelDataRaw &net_outputs, uint64_t timestamp_eof); + const ModelOutput &net_outputs, uint64_t timestamp_eof); diff --git a/selfdrive/modeld/runners/runmodel.h b/selfdrive/modeld/runners/runmodel.h index 0893a4acc..948048f5d 100644 --- a/selfdrive/modeld/runners/runmodel.h +++ b/selfdrive/modeld/runners/runmodel.h @@ -1,6 +1,7 @@ #pragma once class RunModel { public: + virtual ~RunModel() {} virtual void addRecurrent(float *state, int state_size) {} virtual void addDesire(float *state, int state_size) {} virtual void addTrafficConvention(float *state, int state_size) {} diff --git a/selfdrive/test/setup_device_ci.sh b/selfdrive/test/setup_device_ci.sh index 53c7b3909..260a1b448 100755 --- a/selfdrive/test/setup_device_ci.sh +++ b/selfdrive/test/setup_device_ci.sh @@ -1,4 +1,6 @@ -#!/usr/bin/bash -e +#!/usr/bin/bash + +set -e if [ -z "$SOURCE_DIR" ]; then echo "SOURCE_DIR must be set" @@ -24,8 +26,35 @@ if [ -f "/EON" ]; then rm -rf /data/safe_staging fi +export KEYS_PATH="/usr/comma/setup_keys" +export CONTINUE_PATH="/data/continue.sh" +if [ -f "/EON" ]; then + export KEYS_PATH="/data/data/com.termux/files/home/setup_keys" + export CONTINUE_PATH="/data/data/com.termux/files/continue.sh" +fi +tee $CONTINUE_PATH << EOF +#!/usr/bin/bash + +PARAMS_ROOT="/data/params/d" + +while true; do + mkdir -p \$PARAMS_ROOT + cp $KEYS_PATH \$PARAMS_ROOT/GithubSshKeys + echo -n 1 > \$PARAMS_ROOT/SshEnabled + sleep 1m +done + +sleep infinity +EOF +chmod +x $CONTINUE_PATH + # set up environment +if [ ! -d "$SOURCE_DIR" ]; then + git clone https://github.com/commaai/openpilot.git $SOURCE_DIR +fi cd $SOURCE_DIR + +rm -f .git/index.lock git reset --hard git fetch find . -maxdepth 1 -not -path './.git' -not -name '.' -not -name '..' -exec rm -rf '{}' \; diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index f1aad012e..bc44aa3ea 100755 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -26,7 +26,7 @@ PROCS = { "./loggerd": 45.0, "./locationd": 9.1, "selfdrive.controls.plannerd": 22.6, - "./_ui": 15.0, + "./_ui": 20.0, "selfdrive.locationd.paramsd": 9.1, "./camerad": 7.07, "./_sensord": 6.17, @@ -138,21 +138,18 @@ class TestOnroad(unittest.TestCase): cls.lr = list(LogReader(os.path.join(segs[-1], "rlog.bz2"))) return + # setup env os.environ['REPLAY'] = "1" os.environ['SKIP_FW_QUERY'] = "1" os.environ['FINGERPRINT'] = "TOYOTA COROLLA TSS2 2019" + + params = Params() + params.clear_all() set_params_enabled() # Make sure athena isn't running - Params().delete("DongleId") - Params().delete("AthenadPid") os.system("pkill -9 -f athena") - logger_root = Path(ROOT) - initial_segments = set() - if logger_root.exists(): - initial_segments = set(Path(ROOT).iterdir()) - # start manager and run openpilot for a minute try: manager_path = os.path.join(BASEDIR, "selfdrive/manager/manager.py") @@ -164,15 +161,22 @@ class TestOnroad(unittest.TestCase): sm.update(1000) # make sure we get at least two full segments + route = None cls.segments = [] with Timeout(300, "timed out waiting for logs"): + while route is None: + route = params.get("CurrentRoute", encoding="utf-8") + time.sleep(0.1) + while len(cls.segments) < 3: - new_paths = set() - if logger_root.exists(): - new_paths = set(logger_root.iterdir()) - initial_segments - segs = [p for p in new_paths if "--" in str(p)] + segs = set() + if Path(ROOT).exists(): + segs = set(Path(ROOT).glob(f"{route}--*")) cls.segments = sorted(segs, key=lambda s: int(str(s).rsplit('--')[-1])) - time.sleep(5) + time.sleep(2) + + # chop off last, incomplete segment + cls.segments = cls.segments[:-1] finally: proc.terminate() @@ -190,7 +194,7 @@ class TestOnroad(unittest.TestCase): total_size = sum(len(m.as_builder().to_bytes()) for m in msgs) self.assertLess(total_size, 3.5e5) - cnt = Counter([json.loads(m.logMessage)['filename'] for m in msgs]) + cnt = Counter(json.loads(m.logMessage)['filename'] for m in msgs) big_logs = [f for f, n in cnt.most_common(3) if n / sum(cnt.values()) > 30.] self.assertEqual(len(big_logs), 0, f"Log spam: {big_logs}") diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 114110891..1b467f962 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -22,15 +22,13 @@ from selfdrive.hardware import EON, TICI, PC, HARDWARE from selfdrive.loggerd.config import get_available_percent from selfdrive.swaglog import cloudlog from selfdrive.thermald.power_monitoring import PowerMonitoring -from selfdrive.version import tested_branch, terms_version, training_version +from selfdrive.version import terms_version, training_version ThermalStatus = log.DeviceState.ThermalStatus NetworkType = log.DeviceState.NetworkType NetworkStrength = log.DeviceState.NetworkStrength CURRENT_TAU = 15. # 15s time constant TEMP_TAU = 5. # 5s time constant -DAYS_NO_CONNECTIVITY_MAX = 14 # do not allow to engage after this many days -DAYS_NO_CONNECTIVITY_PROMPT = 10 # send an offroad prompt after this many days DISCONNECT_TIMEOUT = 5. # wait 5 seconds before going offroad after disconnect so you get an alert ThermalBand = namedtuple("ThermalBand", ['min_temp', 'max_temp']) @@ -138,7 +136,7 @@ def handle_fan_tici(controller, max_cpu_temp, fan_speed, ignition): controller.reset() fan_pwr_out = -int(controller.update( - setpoint=(75 if ignition else (OFFROAD_DANGER_TEMP - 2)), + setpoint=75, measurement=max_cpu_temp, feedforward=interp(max_cpu_temp, [60.0, 100.0], [0, -80]) )) @@ -165,10 +163,11 @@ def thermald_thread(): fan_speed = 0 count = 0 - startup_conditions = { + onroad_conditions = { "ignition": False, } - startup_conditions_prev = startup_conditions.copy() + startup_conditions = {} + startup_conditions_prev = {} off_ts = None started_ts = None @@ -222,12 +221,12 @@ def thermald_thread(): if pandaState.pandaType == log.PandaState.PandaType.unknown: no_panda_cnt += 1 if no_panda_cnt > DISCONNECT_TIMEOUT / DT_TRML: - if startup_conditions["ignition"]: + if onroad_conditions["ignition"]: cloudlog.error("Lost panda connection while onroad") - startup_conditions["ignition"] = False + onroad_conditions["ignition"] = False else: no_panda_cnt = 0 - startup_conditions["ignition"] = pandaState.ignitionLine or pandaState.ignitionCan + onroad_conditions["ignition"] = pandaState.ignitionLine or pandaState.ignitionCan in_car = pandaState.harnessStatus != log.PandaState.HarnessStatus.notConnected usb_power = peripheralState.usbPowerMode != log.PeripheralState.UsbPowerMode.client @@ -306,7 +305,7 @@ def thermald_thread(): ) if handle_fan is not None: - fan_speed = handle_fan(controller, max_comp_temp, fan_speed, startup_conditions["ignition"]) + fan_speed = handle_fan(controller, max_comp_temp, fan_speed, onroad_conditions["ignition"]) msg.deviceState.fanSpeedPercentDesired = fan_speed is_offroad_for_5_min = (started_ts is None) and ((not started_seen) or (off_ts is None) or (sec_since_boot() - off_ts > 60 * 5)) @@ -324,47 +323,11 @@ def thermald_thread(): # **** starting logic **** - # Check for last update time and display alerts if needed + # Ensure date/time are valid now = datetime.datetime.utcnow() - - # show invalid date/time alert startup_conditions["time_valid"] = (now.year > 2020) or (now.year == 2020 and now.month >= 10) set_offroad_alert_if_changed("Offroad_InvalidTime", (not startup_conditions["time_valid"])) - # Show update prompt - try: - last_update = datetime.datetime.fromisoformat(params.get("LastUpdateTime", encoding='utf8')) - except (TypeError, ValueError): - last_update = now - dt = now - last_update - - update_failed_count = params.get("UpdateFailedCount") - update_failed_count = 0 if update_failed_count is None else int(update_failed_count) - last_update_exception = params.get("LastUpdateException", encoding='utf8') - - if update_failed_count > 15 and last_update_exception is not None: - if tested_branch: - extra_text = "Ensure the software is correctly installed" - else: - extra_text = last_update_exception - - set_offroad_alert_if_changed("Offroad_ConnectivityNeeded", False) - set_offroad_alert_if_changed("Offroad_ConnectivityNeededPrompt", False) - set_offroad_alert_if_changed("Offroad_UpdateFailed", True, extra_text=extra_text) - elif dt.days > DAYS_NO_CONNECTIVITY_MAX and update_failed_count > 1: - set_offroad_alert_if_changed("Offroad_UpdateFailed", False) - set_offroad_alert_if_changed("Offroad_ConnectivityNeededPrompt", False) - set_offroad_alert_if_changed("Offroad_ConnectivityNeeded", True) - elif dt.days > DAYS_NO_CONNECTIVITY_PROMPT: - remaining = max(DAYS_NO_CONNECTIVITY_MAX - dt.days, 1) - set_offroad_alert_if_changed("Offroad_UpdateFailed", False) - set_offroad_alert_if_changed("Offroad_ConnectivityNeeded", False) - set_offroad_alert_if_changed("Offroad_ConnectivityNeededPrompt", True, extra_text=f"{remaining} day{'' if remaining == 1 else 's'}.") - else: - set_offroad_alert_if_changed("Offroad_UpdateFailed", False) - set_offroad_alert_if_changed("Offroad_ConnectivityNeeded", False) - set_offroad_alert_if_changed("Offroad_ConnectivityNeededPrompt", False) - startup_conditions["up_to_date"] = params.get("Offroad_ConnectivityNeeded") is None or params.get_bool("DisableUpdates") or params.get_bool("SnoozeUpdate") startup_conditions["not_uninstalling"] = not params.get_bool("DoUninstall") startup_conditions["accepted_terms"] = params.get("HasAcceptedTerms") == terms_version @@ -377,14 +340,17 @@ def thermald_thread(): startup_conditions["not_taking_snapshot"] = not params.get_bool("IsTakingSnapshot") # if any CPU gets above 107 or the battery gets above 63, kill all processes # controls will warn with CPU above 95 or battery above 60 - startup_conditions["device_temp_good"] = thermal_status < ThermalStatus.danger - set_offroad_alert_if_changed("Offroad_TemperatureTooHigh", (not startup_conditions["device_temp_good"])) + onroad_conditions["device_temp_good"] = thermal_status < ThermalStatus.danger + set_offroad_alert_if_changed("Offroad_TemperatureTooHigh", (not onroad_conditions["device_temp_good"])) if TICI: set_offroad_alert_if_changed("Offroad_StorageMissing", (not Path("/data/media").is_mount())) # Handle offroad/onroad transition - should_start = all(startup_conditions.values()) + should_start = all(onroad_conditions.values()) + if started_ts is None: + should_start = should_start and all(startup_conditions.values()) + if should_start != should_start_prev or (count == 0): params.put_bool("IsOnroad", should_start) params.put_bool("IsOffroad", not should_start) @@ -396,25 +362,25 @@ def thermald_thread(): started_ts = sec_since_boot() started_seen = True else: - if startup_conditions["ignition"] and (startup_conditions != startup_conditions_prev): - cloudlog.event("Startup blocked", startup_conditions=startup_conditions) + if onroad_conditions["ignition"] and (startup_conditions != startup_conditions_prev): + cloudlog.event("Startup blocked", startup_conditions=startup_conditions, onroad_conditions=onroad_conditions) started_ts = None if off_ts is None: off_ts = sec_since_boot() # Offroad power monitoring - power_monitor.calculate(peripheralState, startup_conditions["ignition"]) + power_monitor.calculate(peripheralState, onroad_conditions["ignition"]) msg.deviceState.offroadPowerUsageUwh = power_monitor.get_power_used() msg.deviceState.carBatteryCapacityUwh = max(0, power_monitor.get_car_battery_capacity()) current_power_draw = HARDWARE.get_current_power_draw() # pylint: disable=assignment-from-none msg.deviceState.powerDrawW = current_power_draw if current_power_draw is not None else 0 # Check if we need to disable charging (handled by boardd) - msg.deviceState.chargingDisabled = power_monitor.should_disable_charging(startup_conditions["ignition"], in_car, off_ts) + msg.deviceState.chargingDisabled = power_monitor.should_disable_charging(onroad_conditions["ignition"], in_car, off_ts) # Check if we need to shut down - if power_monitor.should_shutdown(peripheralState, startup_conditions["ignition"], in_car, off_ts, started_seen): + if power_monitor.should_shutdown(peripheralState, onroad_conditions["ignition"], in_car, off_ts, started_seen): cloudlog.info(f"shutting device down, offroad since {off_ts}") # TODO: add function for blocking cloudlog instead of sleep time.sleep(10) diff --git a/selfdrive/tombstoned.py b/selfdrive/tombstoned.py index fb7a1a2c9..a301725ba 100755 --- a/selfdrive/tombstoned.py +++ b/selfdrive/tombstoned.py @@ -15,7 +15,7 @@ from common.file_helpers import mkdirs_exists_ok from selfdrive.hardware import TICI, HARDWARE from selfdrive.loggerd.config import ROOT from selfdrive.swaglog import cloudlog -from selfdrive.version import branch, commit, dirty, origin, version +from selfdrive.version import get_branch, get_commit, get_dirty, get_origin, get_version MAX_SIZE = 100000 * 10 # mal size is 40-100k, allow up to 1M if TICI: @@ -109,7 +109,7 @@ def report_tombstone_android(fn): clean_path = executable.replace('./', '').replace('/', '_') date = datetime.datetime.now().strftime("%Y-%m-%d--%H-%M-%S") - new_fn = f"{date}_{commit[:8]}_{safe_fn(clean_path)}"[:MAX_TOMBSTONE_FN_LEN] + new_fn = f"{date}_{get_commit(default='nocommit')[:8]}_{safe_fn(clean_path)}"[:MAX_TOMBSTONE_FN_LEN] crashlog_dir = os.path.join(ROOT, "crash") mkdirs_exists_ok(crashlog_dir) @@ -183,7 +183,7 @@ def report_tombstone_apport(fn): clean_path = path.replace('/', '_') date = datetime.datetime.now().strftime("%Y-%m-%d--%H-%M-%S") - new_fn = f"{date}_{commit[:8]}_{safe_fn(clean_path)}"[:MAX_TOMBSTONE_FN_LEN] + new_fn = f"{date}_{get_commit(default='nocommit')[:8]}_{safe_fn(clean_path)}"[:MAX_TOMBSTONE_FN_LEN] crashlog_dir = os.path.join(ROOT, "crash") mkdirs_exists_ok(crashlog_dir) @@ -203,14 +203,14 @@ def main(): sentry_sdk.utils.MAX_STRING_LENGTH = 8192 sentry_sdk.init("https://a40f22e13cbc4261873333c125fc9d38@o33823.ingest.sentry.io/157615", - default_integrations=False, release=version) + default_integrations=False, release=get_version()) dongle_id = Params().get("DongleId", encoding='utf-8') sentry_sdk.set_user({"id": dongle_id}) - sentry_sdk.set_tag("dirty", dirty) - sentry_sdk.set_tag("origin", origin) - sentry_sdk.set_tag("branch", branch) - sentry_sdk.set_tag("commit", commit) + sentry_sdk.set_tag("dirty", get_dirty()) + sentry_sdk.set_tag("origin", get_origin()) + sentry_sdk.set_tag("branch", get_branch()) + sentry_sdk.set_tag("commit", get_commit()) sentry_sdk.set_tag("device", HARDWARE.get_device_type()) while True: diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index bc21022dd..52e4e0c42 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -56,10 +56,9 @@ qt_env.Program("qt/text", ["qt/text.cc"], LIBS=qt_libs) qt_env.Program("qt/spinner", ["qt/spinner.cc"], LIBS=qt_libs) # build main UI -qt_src = ["main.cc", "ui.cc", "paint.cc", "qt/sidebar.cc", "qt/onroad.cc", +qt_src = ["main.cc", "ui.cc", "qt/sidebar.cc", "qt/onroad.cc", "qt/window.cc", "qt/home.cc", "qt/offroad/settings.cc", - "qt/offroad/onboarding.cc", "qt/offroad/driverview.cc", - "#third_party/nanovg/nanovg.c"] + "qt/offroad/onboarding.cc", "qt/offroad/driverview.cc"] qt_env.Program("_ui", qt_src + [asset_obj], LIBS=qt_libs) @@ -116,7 +115,7 @@ if arch in ['x86_64', 'Darwin'] or GetOption('extras'): replay_lib_src = ["replay/replay.cc", "replay/camera.cc", "replay/filereader.cc", "replay/logreader.cc", "replay/framereader.cc", "replay/route.cc", "replay/util.cc"] replay_lib = qt_env.Library("qt_replay", replay_lib_src, LIBS=base_libs) - replay_libs = [replay_lib, 'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'swscale', 'yuv'] + qt_libs + replay_libs = [replay_lib, 'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv'] + qt_libs qt_env.Program("replay/replay", ["replay/main.cc"], LIBS=replay_libs) qt_env.Program("watch3", ["watch3.cc"], LIBS=qt_libs + ['common', 'json11']) @@ -126,5 +125,8 @@ if arch in ['x86_64', 'Darwin'] or GetOption('extras'): # navd if maps: - navd_src = ["navd/main.cc", "navd/route_engine.cc"] + navd_src = ["navd/main.cc", "navd/route_engine.cc", "navd/map_renderer.cc"] qt_env.Program("navd/_navd", navd_src, LIBS=qt_libs + ['common', 'json11']) + + if GetOption('extras'): + qt_env.SharedLibrary("navd/map_renderer", ["navd/map_renderer.cc"], LIBS=qt_libs + ['common', 'messaging']) diff --git a/selfdrive/ui/navd/main.cc b/selfdrive/ui/navd/main.cc index cc8d9c339..7ae0393a4 100644 --- a/selfdrive/ui/navd/main.cc +++ b/selfdrive/ui/navd/main.cc @@ -5,9 +5,11 @@ #include #include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/qt/maps/map_helpers.h" #include "selfdrive/ui/navd/route_engine.h" - -RouteEngine* route_engine = nullptr; +#include "selfdrive/ui/navd/map_renderer.h" +#include "selfdrive/hardware/hw.h" +#include "selfdrive/common/params.h" void sigHandler(int s) { qInfo() << "Shutting down"; @@ -30,7 +32,14 @@ int main(int argc, char *argv[]) { parser.process(app); const QStringList args = parser.positionalArguments(); - route_engine = new RouteEngine(); + + RouteEngine* route_engine = new RouteEngine(); + + if (Params().getBool("NavdRender")) { + MapRenderer * m = new MapRenderer(get_mapbox_settings()); + QObject::connect(route_engine, &RouteEngine::positionUpdated, m, &MapRenderer::updatePosition); + QObject::connect(route_engine, &RouteEngine::routeUpdated, m, &MapRenderer::updateRoute); + } return app.exec(); } diff --git a/selfdrive/ui/navd/map_renderer.cc b/selfdrive/ui/navd/map_renderer.cc new file mode 100644 index 000000000..1d78d68df --- /dev/null +++ b/selfdrive/ui/navd/map_renderer.cc @@ -0,0 +1,200 @@ +#include "selfdrive/ui/navd/map_renderer.h" + +#include +#include +#include + +#include "selfdrive/ui/qt/maps/map_helpers.h" +#include "selfdrive/common/timing.h" + +const float ZOOM = 13.5; // Don't go below 13 or features will start to disappear +const int WIDTH = 256; +const int HEIGHT = WIDTH; + +const int NUM_VIPC_BUFFERS = 4; + +MapRenderer::MapRenderer(const QMapboxGLSettings &settings, bool enable_vipc) : m_settings(settings) { + QSurfaceFormat fmt; + fmt.setRenderableType(QSurfaceFormat::OpenGLES); + + ctx = std::make_unique(); + ctx->setFormat(fmt); + ctx->create(); + assert(ctx->isValid()); + + surface = std::make_unique(); + surface->setFormat(ctx->format()); + surface->create(); + + ctx->makeCurrent(surface.get()); + assert(QOpenGLContext::currentContext() == ctx.get()); + + gl_functions.reset(ctx->functions()); + gl_functions->initializeOpenGLFunctions(); + + QOpenGLFramebufferObjectFormat fbo_format; + fbo.reset(new QOpenGLFramebufferObject(WIDTH, HEIGHT, fbo_format)); + + m_map.reset(new QMapboxGL(nullptr, m_settings, fbo->size(), 1)); + m_map->setCoordinateZoom(QMapbox::Coordinate(0, 0), ZOOM); + m_map->setStyleUrl("mapbox://styles/commaai/ckvmksrpd4n0a14pfdo5heqzr"); + m_map->createRenderer(); + + m_map->resize(fbo->size()); + m_map->setFramebufferObject(fbo->handle(), fbo->size()); + gl_functions->glViewport(0, 0, WIDTH, HEIGHT); + + if (enable_vipc) { + qWarning() << "Enabling navd map rendering"; + vipc_server.reset(new VisionIpcServer("navd")); + vipc_server->create_buffers(VisionStreamType::VISION_STREAM_RGB_MAP, NUM_VIPC_BUFFERS, true, WIDTH, HEIGHT); + vipc_server->start_listener(); + + pm.reset(new PubMaster({"navThumbnail"})); + } +} + +void MapRenderer::updatePosition(QMapbox::Coordinate position, float bearing) { + if (m_map.isNull()) { + return; + } + + m_map->setCoordinate(position); + m_map->setBearing(bearing); + update(); +} + +bool MapRenderer::loaded() { + return m_map->isFullyLoaded(); +} + +void MapRenderer::update() { + gl_functions->glClear(GL_COLOR_BUFFER_BIT); + m_map->render(); + gl_functions->glFlush(); + + sendVipc(); +} + +void MapRenderer::sendVipc() { + if (!vipc_server || !loaded()) { + return; + } + + QImage cap = fbo->toImage().convertToFormat(QImage::Format_RGB888, Qt::AutoColor); + uint64_t ts = nanos_since_boot(); + VisionBuf* buf = vipc_server->get_buffer(VisionStreamType::VISION_STREAM_RGB_MAP); + VisionIpcBufExtra extra = { + .frame_id = frame_id, + .timestamp_sof = ts, + .timestamp_eof = ts, + }; + + assert(cap.sizeInBytes() == buf->len); + memcpy(buf->addr, cap.bits(), buf->len); + vipc_server->send(buf, &extra); + + if (frame_id % 100 == 0) { + // Write jpeg into buffer + QByteArray buffer_bytes; + QBuffer buffer(&buffer_bytes); + buffer.open(QIODevice::WriteOnly); + cap.save(&buffer, "JPG", 50); + + kj::Array buffer_kj = kj::heapArray((const capnp::byte*)buffer_bytes.constData(), buffer_bytes.size()); + + // Send thumbnail + MessageBuilder msg; + auto thumbnaild = msg.initEvent().initNavThumbnail(); + thumbnaild.setFrameId(frame_id); + thumbnaild.setTimestampEof(ts); + thumbnaild.setThumbnail(buffer_kj); + pm->send("navThumbnail", msg); + } + + frame_id++; +} + +uint8_t* MapRenderer::getImage() { + QImage cap = fbo->toImage().convertToFormat(QImage::Format_RGB888, Qt::AutoColor); + uint8_t* buf = new uint8_t[cap.sizeInBytes()]; + memcpy(buf, cap.bits(), cap.sizeInBytes()); + + return buf; +} + +void MapRenderer::updateRoute(QList coordinates) { + if (m_map.isNull()) return; + initLayers(); + + auto route_points = coordinate_list_to_collection(coordinates); + QMapbox::Feature feature(QMapbox::Feature::LineStringType, route_points, {}, {}); + QVariantMap navSource; + navSource["type"] = "geojson"; + navSource["data"] = QVariant::fromValue(feature); + m_map->updateSource("navSource", navSource); + m_map->setLayoutProperty("navLayer", "visibility", "visible"); +} + +void MapRenderer::initLayers() { + if (!m_map->layerExists("navLayer")) { + QVariantMap nav; + nav["id"] = "navLayer"; + nav["type"] = "line"; + nav["source"] = "navSource"; + m_map->addLayer(nav, "road-intersection"); + m_map->setPaintProperty("navLayer", "line-color", QColor("blue")); + m_map->setPaintProperty("navLayer", "line-width", 3); + m_map->setLayoutProperty("navLayer", "line-cap", "round"); + } +} + +MapRenderer::~MapRenderer() { +} + +extern "C" { + MapRenderer* map_renderer_init() { + char *argv[] = { + (char*)"navd", + nullptr + }; + int argc = 0; + QApplication *app = new QApplication(argc, argv); + assert(app); + + QMapboxGLSettings settings; + settings.setApiBaseUrl(MAPS_HOST); + settings.setAccessToken(get_mapbox_token()); + + return new MapRenderer(settings, false); + } + + void map_renderer_update_position(MapRenderer *inst, float lat, float lon, float bearing) { + inst->updatePosition({lat, lon}, bearing); + QApplication::processEvents(); + } + + void map_renderer_update_route(MapRenderer *inst, char* polyline) { + inst->updateRoute(polyline_to_coordinate_list(QString::fromUtf8(polyline))); + } + + void map_renderer_update(MapRenderer *inst) { + inst->update(); + } + + void map_renderer_process(MapRenderer *inst) { + QApplication::processEvents(); + } + + bool map_renderer_loaded(MapRenderer *inst) { + return inst->loaded(); + } + + uint8_t * map_renderer_get_image(MapRenderer *inst) { + return inst->getImage(); + } + + void map_renderer_free_image(MapRenderer *inst, uint8_t * buf) { + delete[] buf; + } +} diff --git a/selfdrive/ui/navd/map_renderer.h b/selfdrive/ui/navd/map_renderer.h new file mode 100644 index 000000000..1746e7669 --- /dev/null +++ b/selfdrive/ui/navd/map_renderer.h @@ -0,0 +1,49 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cereal/visionipc/visionipc_server.h" +#include "cereal/messaging/messaging.h" + + +class MapRenderer : public QObject { + Q_OBJECT + +public: + MapRenderer(const QMapboxGLSettings &, bool enable_vipc=true); + uint8_t* getImage(); + void update(); + bool loaded(); + ~MapRenderer(); + + +private: + std::unique_ptr ctx; + std::unique_ptr surface; + std::unique_ptr gl_functions; + std::unique_ptr fbo; + + std::unique_ptr vipc_server; + std::unique_ptr pm; + void sendVipc(); + + QMapboxGLSettings m_settings; + QScopedPointer m_map; + + void initLayers(); + + uint32_t frame_id = 0; + +public slots: + void updatePosition(QMapbox::Coordinate position, float bearing); + void updateRoute(QList coordinates); +}; diff --git a/selfdrive/ui/navd/route_engine.cc b/selfdrive/ui/navd/route_engine.cc index 30d070dc7..f3cb7df3d 100644 --- a/selfdrive/ui/navd/route_engine.cc +++ b/selfdrive/ui/navd/route_engine.cc @@ -91,7 +91,6 @@ static void parse_banner(cereal::NavInstruction::Builder &instruction, const QMa } } } - } RouteEngine::RouteEngine() { @@ -99,14 +98,17 @@ RouteEngine::RouteEngine() { pm = new PubMaster({"navInstruction", "navRoute"}); // Timers - timer = new QTimer(this); - QObject::connect(timer, SIGNAL(timeout()), this, SLOT(timerUpdate())); - timer->start(1000); + route_timer = new QTimer(this); + QObject::connect(route_timer, SIGNAL(timeout()), this, SLOT(routeUpdate())); + route_timer->start(1000); + + msg_timer = new QTimer(this); + QObject::connect(msg_timer, SIGNAL(timeout()), this, SLOT(msgUpdate())); + msg_timer->start(50); // Build routing engine QVariantMap parameters; - QString token = MAPBOX_TOKEN.isEmpty() ? CommaApi::create_jwt({}, 4 * 7 * 24 * 3600) : MAPBOX_TOKEN; - parameters["mapbox.access_token"] = token; + parameters["mapbox.access_token"] = get_mapbox_token(); parameters["mapbox.directions_api_url"] = MAPS_HOST + "/directions/v5/mapbox/"; geoservice_provider = new QGeoServiceProvider("mapbox", parameters); @@ -124,12 +126,14 @@ RouteEngine::RouteEngine() { } } -void RouteEngine::timerUpdate() { - sm->update(0); +void RouteEngine::msgUpdate() { + sm->update(1000); if (!sm->updated("liveLocationKalman")) { + active = false; return; } + if (sm->updated("managerState")) { for (auto const &p : (*sm)["managerState"].getManagerState().getProcesses()) { if (p.getName() == "ui" && p.getRunning()) { @@ -153,6 +157,15 @@ void RouteEngine::timerUpdate() { if (localizer_valid) { last_bearing = RAD2DEG(orientation.getValue()[2]); last_position = QMapbox::Coordinate(pos.getValue()[0], pos.getValue()[1]); + emit positionUpdated(*last_position, *last_bearing); + } + + active = true; +} + +void RouteEngine::routeUpdate() { + if (!active) { + return; } recomputeRoute(); @@ -261,6 +274,10 @@ bool RouteEngine::shouldRecompute() { } void RouteEngine::recomputeRoute() { + if (!last_position) { + return; + } + auto new_destination = coordinate_from_param("NavDestination"); if (!new_destination) { clearRoute(); @@ -308,6 +325,9 @@ void RouteEngine::routeCalculated(QGeoRouteReply *reply) { route = reply->routes().at(0); segment = route.firstRouteSegment(); + + auto path = route.path(); + emit routeUpdated(path); } else { qWarning() << "Got empty route response"; } diff --git a/selfdrive/ui/navd/route_engine.h b/selfdrive/ui/navd/route_engine.h index 9a6c99041..33cbc7910 100644 --- a/selfdrive/ui/navd/route_engine.h +++ b/selfdrive/ui/navd/route_engine.h @@ -23,7 +23,8 @@ public: SubMaster *sm; PubMaster *pm; - QTimer* timer; + QTimer* msg_timer; + QTimer* route_timer; std::optional ui_pid; @@ -41,6 +42,7 @@ public: bool localizer_valid = false; // Route recompute + bool active = false; int recompute_backoff = 0; int recompute_countdown = 0; void calculateRoute(QMapbox::Coordinate destination); @@ -48,8 +50,13 @@ public: bool shouldRecompute(); private slots: - void timerUpdate(); + void routeUpdate(); + void msgUpdate(); void routeCalculated(QGeoRouteReply *reply); void recomputeRoute(); void sendRoute(); + +signals: + void positionUpdated(QMapbox::Coordinate position, float bearing); + void routeUpdated(QList coordinates); }; diff --git a/selfdrive/ui/paint.cc b/selfdrive/ui/paint.cc deleted file mode 100644 index 612b924f3..000000000 --- a/selfdrive/ui/paint.cc +++ /dev/null @@ -1,311 +0,0 @@ -#include "selfdrive/ui/paint.h" - -#include - -#ifdef __APPLE__ -#include -#define NANOVG_GL3_IMPLEMENTATION -#define nvgCreate nvgCreateGL3 -#else -#include -#define NANOVG_GLES3_IMPLEMENTATION -#define nvgCreate nvgCreateGLES3 -#endif - -#define NANOVG_GLES3_IMPLEMENTATION -#include -#include - -#include "selfdrive/common/util.h" -#include "selfdrive/hardware/hw.h" -#include "selfdrive/ui/ui.h" - -static void ui_draw_text(const UIState *s, float x, float y, const char *string, float size, NVGcolor color, const char *font_name) { - nvgFontFace(s->vg, font_name); - nvgFontSize(s->vg, size); - nvgFillColor(s->vg, color); - nvgText(s->vg, x, y, string, NULL); -} - -static void draw_chevron(UIState *s, float x, float y, float sz, NVGcolor fillColor, NVGcolor glowColor) { - // glow - float g_xo = sz/5; - float g_yo = sz/10; - nvgBeginPath(s->vg); - nvgMoveTo(s->vg, x+(sz*1.35)+g_xo, y+sz+g_yo); - nvgLineTo(s->vg, x, y-g_xo); - nvgLineTo(s->vg, x-(sz*1.35)-g_xo, y+sz+g_yo); - nvgClosePath(s->vg); - nvgFillColor(s->vg, glowColor); - nvgFill(s->vg); - - // chevron - nvgBeginPath(s->vg); - nvgMoveTo(s->vg, x+(sz*1.25), y+sz); - nvgLineTo(s->vg, x, y); - nvgLineTo(s->vg, x-(sz*1.25), y+sz); - nvgClosePath(s->vg); - nvgFillColor(s->vg, fillColor); - nvgFill(s->vg); -} - -static void ui_draw_circle_image(const UIState *s, int center_x, int center_y, int radius, const char *image, NVGcolor color, float img_alpha) { - nvgBeginPath(s->vg); - nvgCircle(s->vg, center_x, center_y, radius); - nvgFillColor(s->vg, color); - nvgFill(s->vg); - const int img_size = radius * 1.5; - ui_draw_image(s, {center_x - (img_size / 2), center_y - (img_size / 2), img_size, img_size}, image, img_alpha); -} - -static void ui_draw_circle_image(const UIState *s, int center_x, int center_y, int radius, const char *image, bool active) { - float bg_alpha = active ? 0.3f : 0.1f; - float img_alpha = active ? 1.0f : 0.15f; - ui_draw_circle_image(s, center_x, center_y, radius, image, nvgRGBA(0, 0, 0, (255 * bg_alpha)), img_alpha); -} - -static void draw_lead(UIState *s, const cereal::RadarState::LeadData::Reader &lead_data, const vertex_data &vd) { - // Draw lead car indicator - auto [x, y] = vd; - - float fillAlpha = 0; - float speedBuff = 10.; - float leadBuff = 40.; - float d_rel = lead_data.getDRel(); - float v_rel = lead_data.getVRel(); - if (d_rel < leadBuff) { - fillAlpha = 255*(1.0-(d_rel/leadBuff)); - if (v_rel < 0) { - fillAlpha += 255*(-1*(v_rel/speedBuff)); - } - fillAlpha = (int)(fmin(fillAlpha, 255)); - } - - float sz = std::clamp((25 * 30) / (d_rel / 3 + 30), 15.0f, 30.0f) * 2.35; - x = std::clamp(x, 0.f, s->fb_w - sz / 2); - y = std::fmin(s->fb_h - sz * .6, y); - draw_chevron(s, x, y, sz, nvgRGBA(201, 34, 49, fillAlpha), COLOR_YELLOW); -} - -static void ui_draw_line(UIState *s, const line_vertices_data &vd, NVGcolor *color, NVGpaint *paint) { - if (vd.cnt == 0) return; - - const vertex_data *v = &vd.v[0]; - nvgBeginPath(s->vg); - nvgMoveTo(s->vg, v[0].x, v[0].y); - for (int i = 1; i < vd.cnt; i++) { - nvgLineTo(s->vg, v[i].x, v[i].y); - } - nvgClosePath(s->vg); - if (color) { - nvgFillColor(s->vg, *color); - } else if (paint) { - nvgFillPaint(s->vg, *paint); - } - nvgFill(s->vg); -} - -static void ui_draw_vision_lane_lines(UIState *s) { - const UIScene &scene = s->scene; - NVGpaint track_bg; - if (!scene.end_to_end) { - // paint lanelines - for (int i = 0; i < std::size(scene.lane_line_vertices); i++) { - NVGcolor color = nvgRGBAf(1.0, 1.0, 1.0, scene.lane_line_probs[i]); - ui_draw_line(s, scene.lane_line_vertices[i], &color, nullptr); - } - - // paint road edges - for (int i = 0; i < std::size(scene.road_edge_vertices); i++) { - NVGcolor color = nvgRGBAf(1.0, 0.0, 0.0, std::clamp(1.0 - scene.road_edge_stds[i], 0.0, 1.0)); - ui_draw_line(s, scene.road_edge_vertices[i], &color, nullptr); - } - track_bg = nvgLinearGradient(s->vg, s->fb_w, s->fb_h, s->fb_w, s->fb_h * .4, - COLOR_WHITE, COLOR_WHITE_ALPHA(0)); - } else { - track_bg = nvgLinearGradient(s->vg, s->fb_w, s->fb_h, s->fb_w, s->fb_h * .4, - COLOR_RED, COLOR_RED_ALPHA(0)); - } - // paint path - ui_draw_line(s, scene.track_vertices, nullptr, &track_bg); -} - -// Draw all world space objects. -static void ui_draw_world(UIState *s) { - nvgScissor(s->vg, 0, 0, s->fb_w, s->fb_h); - - // Draw lane edges and vision/mpc tracks - ui_draw_vision_lane_lines(s); - - // Draw lead indicators if openpilot is handling longitudinal - if (s->scene.longitudinal_control) { - auto lead_one = (*s->sm)["radarState"].getRadarState().getLeadOne(); - auto lead_two = (*s->sm)["radarState"].getRadarState().getLeadTwo(); - if (lead_one.getStatus()) { - draw_lead(s, lead_one, s->scene.lead_vertices[0]); - } - if (lead_two.getStatus() && (std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0)) { - draw_lead(s, lead_two, s->scene.lead_vertices[1]); - } - } - nvgResetScissor(s->vg); -} - -static void ui_draw_vision_maxspeed(UIState *s) { - const int SET_SPEED_NA = 255; - float maxspeed = (*s->sm)["controlsState"].getControlsState().getVCruise(); - const bool is_cruise_set = maxspeed != 0 && maxspeed != SET_SPEED_NA; - if (is_cruise_set && !s->scene.is_metric) { maxspeed *= KM_TO_MILE; } - - const Rect rect = {bdr_s * 2, int(bdr_s * 1.5), 184, 202}; - ui_fill_rect(s->vg, rect, COLOR_BLACK_ALPHA(100), 30.); - ui_draw_rect(s->vg, rect, COLOR_WHITE_ALPHA(100), 10, 20.); - - nvgTextAlign(s->vg, NVG_ALIGN_CENTER | NVG_ALIGN_BASELINE); - ui_draw_text(s, rect.centerX(), 118, "MAX", 26 * 2.5, COLOR_WHITE_ALPHA(is_cruise_set ? 200 : 100), "sans-regular"); - if (is_cruise_set) { - const std::string maxspeed_str = std::to_string((int)std::nearbyint(maxspeed)); - ui_draw_text(s, rect.centerX(), 212, maxspeed_str.c_str(), 48 * 2.5, COLOR_WHITE, "sans-bold"); - } else { - ui_draw_text(s, rect.centerX(), 212, "N/A", 42 * 2.5, COLOR_WHITE_ALPHA(100), "sans-semibold"); - } -} - -static void ui_draw_vision_speed(UIState *s) { - const float speed = std::max(0.0, (*s->sm)["carState"].getCarState().getVEgo() * (s->scene.is_metric ? MS_TO_KPH : MS_TO_MPH)); - const std::string speed_str = std::to_string((int)std::nearbyint(speed)); - nvgTextAlign(s->vg, NVG_ALIGN_CENTER | NVG_ALIGN_BASELINE); - ui_draw_text(s, s->fb_w/2, 210, speed_str.c_str(), 96 * 2.5, COLOR_WHITE, "sans-bold"); - ui_draw_text(s, s->fb_w/2, 290, s->scene.is_metric ? "km/h" : "mph", 36 * 2.5, COLOR_WHITE_ALPHA(200), "sans-regular"); -} - -static void ui_draw_vision_event(UIState *s) { - if (s->scene.engageable) { - // draw steering wheel - const int radius = 96; - const int center_x = s->fb_w - radius - bdr_s * 2; - const int center_y = radius + (bdr_s * 1.5); - const QColor &color = bg_colors[s->status]; - NVGcolor nvg_color = nvgRGBA(color.red(), color.green(), color.blue(), color.alpha()); - ui_draw_circle_image(s, center_x, center_y, radius, "wheel", nvg_color, 1.0f); - } -} - -static void ui_draw_vision_face(UIState *s) { - const int radius = 96; - const int center_x = radius + (bdr_s * 2); - const int center_y = s->fb_h - footer_h / 2; - ui_draw_circle_image(s, center_x, center_y, radius, "driver_face", s->scene.dm_active); -} - -static void ui_draw_vision_header(UIState *s) { - NVGpaint gradient = nvgLinearGradient(s->vg, 0, header_h - (header_h / 2.5), 0, header_h, - nvgRGBAf(0, 0, 0, 0.45), nvgRGBAf(0, 0, 0, 0)); - ui_fill_rect(s->vg, {0, 0, s->fb_w , header_h}, gradient); - ui_draw_vision_maxspeed(s); - ui_draw_vision_speed(s); - ui_draw_vision_event(s); -} - -static void ui_draw_vision(UIState *s) { - const UIScene *scene = &s->scene; - // Draw augmented elements - if (scene->world_objects_visible) { - ui_draw_world(s); - } - // Set Speed, Current Speed, Status/Events - ui_draw_vision_header(s); - if ((*s->sm)["controlsState"].getControlsState().getAlertSize() == cereal::ControlsState::AlertSize::NONE) { - ui_draw_vision_face(s); - } -} - -void ui_draw(UIState *s, int w, int h) { - // Update intrinsics matrix after possible wide camera toggle change - if (s->fb_w != w || s->fb_h != h) { - ui_resize(s, w, h); - } - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - nvgBeginFrame(s->vg, s->fb_w, s->fb_h, 1.0f); - ui_draw_vision(s); - nvgEndFrame(s->vg); - glDisable(GL_BLEND); -} - -void ui_draw_image(const UIState *s, const Rect &r, const char *name, float alpha) { - nvgBeginPath(s->vg); - NVGpaint imgPaint = nvgImagePattern(s->vg, r.x, r.y, r.w, r.h, 0, s->images.at(name), alpha); - nvgRect(s->vg, r.x, r.y, r.w, r.h); - nvgFillPaint(s->vg, imgPaint); - nvgFill(s->vg); -} - -void ui_draw_rect(NVGcontext *vg, const Rect &r, NVGcolor color, int width, float radius) { - nvgBeginPath(vg); - radius > 0 ? nvgRoundedRect(vg, r.x, r.y, r.w, r.h, radius) : nvgRect(vg, r.x, r.y, r.w, r.h); - nvgStrokeColor(vg, color); - nvgStrokeWidth(vg, width); - nvgStroke(vg); -} - -static inline void fill_rect(NVGcontext *vg, const Rect &r, const NVGcolor *color, const NVGpaint *paint, float radius) { - nvgBeginPath(vg); - radius > 0 ? nvgRoundedRect(vg, r.x, r.y, r.w, r.h, radius) : nvgRect(vg, r.x, r.y, r.w, r.h); - if (color) nvgFillColor(vg, *color); - if (paint) nvgFillPaint(vg, *paint); - nvgFill(vg); -} -void ui_fill_rect(NVGcontext *vg, const Rect &r, const NVGcolor &color, float radius) { - fill_rect(vg, r, &color, nullptr, radius); -} -void ui_fill_rect(NVGcontext *vg, const Rect &r, const NVGpaint &paint, float radius) { - fill_rect(vg, r, nullptr, &paint, radius); -} - -void ui_nvg_init(UIState *s) { - // on EON, we enable MSAA - s->vg = Hardware::EON() ? nvgCreate(0) : nvgCreate(NVG_ANTIALIAS | NVG_STENCIL_STROKES | NVG_DEBUG); - assert(s->vg); - - // init fonts - std::pair fonts[] = { - {"sans-regular", "../assets/fonts/opensans_regular.ttf"}, - {"sans-semibold", "../assets/fonts/opensans_semibold.ttf"}, - {"sans-bold", "../assets/fonts/opensans_bold.ttf"}, - }; - for (auto [name, file] : fonts) { - int font_id = nvgCreateFont(s->vg, name, file); - assert(font_id >= 0); - } - - // init images - std::vector> images = { - {"wheel", "../assets/img_chffr_wheel.png"}, - {"driver_face", "../assets/img_driver_face.png"}, - }; - for (auto [name, file] : images) { - s->images[name] = nvgCreateImage(s->vg, file, 1); - assert(s->images[name] != 0); - } -} - -void ui_resize(UIState *s, int width, int height) { - s->fb_w = width; - s->fb_h = height; - - auto intrinsic_matrix = s->wide_camera ? ecam_intrinsic_matrix : fcam_intrinsic_matrix; - float zoom = ZOOM / intrinsic_matrix.v[0]; - if (s->wide_camera) { - zoom *= 0.5; - } - - // Apply transformation such that video pixel coordinates match video - // 1) Put (0, 0) in the middle of the video - // 2) Apply same scaling as video - // 3) Put (0, 0) in top left corner of video - s->car_space_transform.reset(); - s->car_space_transform.translate(width / 2, height / 2 + y_offset) - .scale(zoom, zoom) - .translate(-intrinsic_matrix.v[2], -intrinsic_matrix.v[5]); -} diff --git a/selfdrive/ui/paint.h b/selfdrive/ui/paint.h deleted file mode 100644 index 4ce7cbd13..000000000 --- a/selfdrive/ui/paint.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include "selfdrive/ui/ui.h" - -void ui_draw(UIState *s, int w, int h); -void ui_draw_image(const UIState *s, const Rect &r, const char *name, float alpha); -void ui_draw_rect(NVGcontext *vg, const Rect &r, NVGcolor color, int width, float radius = 0); -void ui_fill_rect(NVGcontext *vg, const Rect &r, const NVGpaint &paint, float radius = 0); -void ui_fill_rect(NVGcontext *vg, const Rect &r, const NVGcolor &color, float radius = 0); -void ui_nvg_init(UIState *s); -void ui_resize(UIState *s, int width, int height); -void ui_update_params(UIState *s); diff --git a/selfdrive/ui/qt/api.cc b/selfdrive/ui/qt/api.cc index 6339884ae..8ac9b4131 100644 --- a/selfdrive/ui/qt/api.cc +++ b/selfdrive/ui/qt/api.cc @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -62,18 +63,20 @@ QString create_jwt(const QJsonObject &payloads, int expiry) { } // namespace CommaApi HttpRequest::HttpRequest(QObject *parent, bool create_jwt, int timeout) : create_jwt(create_jwt), QObject(parent) { - networkAccessManager = new QNetworkAccessManager(this); - networkTimer = new QTimer(this); networkTimer->setSingleShot(true); networkTimer->setInterval(timeout); connect(networkTimer, &QTimer::timeout, this, &HttpRequest::requestTimeout); } -bool HttpRequest::active() { +bool HttpRequest::active() const { return reply != nullptr; } +bool HttpRequest::timeout() const { + return reply && reply->error() == QNetworkReply::OperationCanceledError; +} + void HttpRequest::sendRequest(const QString &requestURL, const HttpRequest::Method method) { if (active()) { qDebug() << "HttpRequest is active"; @@ -97,9 +100,9 @@ void HttpRequest::sendRequest(const QString &requestURL, const HttpRequest::Meth } if (method == HttpRequest::Method::GET) { - reply = networkAccessManager->get(request); + reply = nam()->get(request); } else if (method == HttpRequest::Method::DELETE) { - reply = networkAccessManager->deleteResource(request); + reply = nam()->deleteResource(request); } networkTimer->start(); @@ -110,29 +113,31 @@ void HttpRequest::requestTimeout() { reply->abort(); } -// This function should always emit something void HttpRequest::requestFinished() { - bool success = false; - if (reply->error() != QNetworkReply::OperationCanceledError) { - networkTimer->stop(); - QString response = reply->readAll(); + networkTimer->stop(); - if (reply->error() == QNetworkReply::NoError) { - success = true; - emit receivedResponse(response); + if (reply->error() == QNetworkReply::NoError) { + emit requestDone(reply->readAll(), true); + } else { + QString error; + if (reply->error() == QNetworkReply::OperationCanceledError) { + nam()->clearAccessCache(); + nam()->clearConnectionCache(); + error = "Request timed out"; } else { - emit failedResponse(reply->errorString()); - if (reply->error() == QNetworkReply::ContentAccessDenied || reply->error() == QNetworkReply::AuthenticationRequiredError) { qWarning() << ">> Unauthorized. Authenticate with tools/lib/auth.py <<"; } + error = reply->errorString(); } - } else { - networkAccessManager->clearAccessCache(); - networkAccessManager->clearConnectionCache(); - emit timeoutResponse("timeout"); + emit requestDone(error, false); } - emit requestDone(success); + reply->deleteLater(); reply = nullptr; } + +QNetworkAccessManager *HttpRequest::nam() { + static QNetworkAccessManager *networkAccessManager = new QNetworkAccessManager(qApp); + return networkAccessManager; +} diff --git a/selfdrive/ui/qt/api.h b/selfdrive/ui/qt/api.h index 1c7c333ea..8f7467810 100644 --- a/selfdrive/ui/qt/api.h +++ b/selfdrive/ui/qt/api.h @@ -27,23 +27,21 @@ public: explicit HttpRequest(QObject* parent, bool create_jwt = true, int timeout = 20000); void sendRequest(const QString &requestURL, const Method method = Method::GET); - bool active(); + bool active() const; + bool timeout() const; + +signals: + void requestDone(const QString &response, bool success); protected: QNetworkReply *reply = nullptr; private: - QNetworkAccessManager *networkAccessManager = nullptr; + static QNetworkAccessManager *nam(); QTimer *networkTimer = nullptr; bool create_jwt; private slots: void requestTimeout(); void requestFinished(); - -signals: - void requestDone(bool success); - void receivedResponse(const QString &response); - void failedResponse(const QString &errorString); - void timeoutResponse(const QString &errorString); }; diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc index 5484c5d74..1e8926495 100644 --- a/selfdrive/ui/qt/home.cc +++ b/selfdrive/ui/qt/home.cc @@ -178,6 +178,6 @@ void OffroadHome::refresh() { update_notif->setVisible(updateAvailable); alert_notif->setVisible(alerts); if (alerts) { - alert_notif->setText(QString::number(alerts) + " ALERT" + (alerts > 1 ? "S" : "")); + alert_notif->setText(QString::number(alerts) + (alerts > 1 ? " ALERTS" : " ALERT")); } } diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc index 7bee7f7ca..e1bfe96c8 100644 --- a/selfdrive/ui/qt/maps/map.cc +++ b/selfdrive/ui/qt/maps/map.cc @@ -112,10 +112,6 @@ void MapWindow::timerUpdate() { update(); - if (m_map.isNull()) { - return; - } - sm->update(0); if (sm->updated("liveLocationKalman")) { auto location = (*sm)["liveLocationKalman"].getLiveLocationKalman(); @@ -134,6 +130,21 @@ void MapWindow::timerUpdate() { velocity_filter.update(velocity); } } + + if (sm->updated("navRoute")) { + qWarning() << "Got new navRoute from navd. Opening map:" << allow_open; + + // Only open the map on setting destination the first time + if (allow_open) { + setVisible(true); // Show map on destination set/change + allow_open = false; + } + } + + if (m_map.isNull()) { + return; + } + loaded_once = loaded_once || m_map->isFullyLoaded(); if (!loaded_once) { map_instructions->showError("Map Loading"); @@ -186,7 +197,7 @@ void MapWindow::timerUpdate() { } if (sm->rcv_frame("navRoute") != route_rcv_frame) { - qWarning() << "Got new navRoute from navd"; + qWarning() << "Updating navLayer with new route"; auto route = (*sm)["navRoute"].getNavRoute(); auto route_points = capnp_coordinate_list_to_collection(route.getCoordinates()); QMapbox::Feature feature(QMapbox::Feature::LineStringType, route_points, {}, {}); @@ -196,11 +207,6 @@ void MapWindow::timerUpdate() { m_map->updateSource("navSource", navSource); m_map->setLayoutProperty("navLayer", "visibility", "visible"); - // Only open the map on setting destination the first time - if (allow_open) { - setVisible(true); // Show map on destination set/change - allow_open = false; - } route_rcv_frame = sm->rcv_frame("navRoute"); } } diff --git a/selfdrive/ui/qt/maps/map.h b/selfdrive/ui/qt/maps/map.h index b74f5bf29..c62089cb2 100644 --- a/selfdrive/ui/qt/maps/map.h +++ b/selfdrive/ui/qt/maps/map.h @@ -22,9 +22,6 @@ #include "selfdrive/common/util.h" #include "cereal/messaging/messaging.h" -const QString MAPBOX_TOKEN = util::getenv("MAPBOX_TOKEN").c_str(); -const QString MAPS_HOST = util::getenv("MAPS_HOST", MAPBOX_TOKEN.isEmpty() ? "https://maps.comma.ai" : "https://api.mapbox.com").c_str(); - class MapInstructions : public QWidget { Q_OBJECT diff --git a/selfdrive/ui/qt/maps/map_helpers.cc b/selfdrive/ui/qt/maps/map_helpers.cc index 422ae99b6..f87e80403 100644 --- a/selfdrive/ui/qt/maps/map_helpers.cc +++ b/selfdrive/ui/qt/maps/map_helpers.cc @@ -4,7 +4,25 @@ #include #include "selfdrive/common/params.h" +#include "selfdrive/hardware/hw.h" +#include "selfdrive/ui/qt/api.h" +QString get_mapbox_token() { + // Valid for 4 weeks since we can't swap tokens on the fly + return MAPBOX_TOKEN.isEmpty() ? CommaApi::create_jwt({}, 4 * 7 * 24 * 3600) : MAPBOX_TOKEN; +} + +QMapboxGLSettings get_mapbox_settings() { + QMapboxGLSettings settings; + + if (!Hardware::PC()) { + settings.setCacheDatabasePath(MAPS_CACHE_PATH); + } + settings.setApiBaseUrl(MAPS_HOST); + settings.setAccessToken(get_mapbox_token()); + + return settings; +} QGeoCoordinate to_QGeoCoordinate(const QMapbox::Coordinate &in) { return QGeoCoordinate(in.first, in.second); @@ -83,6 +101,48 @@ QMapbox::CoordinatesCollections coordinate_list_to_collection(QList polyline_to_coordinate_list(const QString &polylineString) { + QList path; + if (polylineString.isEmpty()) + return path; + + QByteArray data = polylineString.toLatin1(); + + bool parsingLatitude = true; + + int shift = 0; + int value = 0; + + QGeoCoordinate coord(0, 0); + + for (int i = 0; i < data.length(); ++i) { + unsigned char c = data.at(i) - 63; + + value |= (c & 0x1f) << shift; + shift += 5; + + // another chunk + if (c & 0x20) + continue; + + int diff = (value & 1) ? ~(value >> 1) : (value >> 1); + + if (parsingLatitude) { + coord.setLatitude(coord.latitude() + (double)diff/1e6); + } else { + coord.setLongitude(coord.longitude() + (double)diff/1e6); + path.append(coord); + } + + parsingLatitude = !parsingLatitude; + + value = 0; + shift = 0; + } + + return path; +} + static QGeoCoordinate sub(QGeoCoordinate v, QGeoCoordinate w) { return QGeoCoordinate(v.latitude() - w.latitude(), v.longitude() - w.longitude()); } diff --git a/selfdrive/ui/qt/maps/map_helpers.h b/selfdrive/ui/qt/maps/map_helpers.h index 53a44f42d..1c8cacbeb 100644 --- a/selfdrive/ui/qt/maps/map_helpers.h +++ b/selfdrive/ui/qt/maps/map_helpers.h @@ -5,12 +5,17 @@ #include #include +#include "selfdrive/common/util.h" #include "common/transformations/coordinates.hpp" #include "common/transformations/orientation.hpp" #include "cereal/messaging/messaging.h" -#define RAD2DEG(x) ((x) * 180.0 / M_PI) +const QString MAPBOX_TOKEN = util::getenv("MAPBOX_TOKEN").c_str(); +const QString MAPS_HOST = util::getenv("MAPS_HOST", MAPBOX_TOKEN.isEmpty() ? "https://maps.comma.ai" : "https://api.mapbox.com").c_str(); +const QString MAPS_CACHE_PATH = "/data/mbgl-cache-navd.db"; +QString get_mapbox_token(); +QMapboxGLSettings get_mapbox_settings(); QGeoCoordinate to_QGeoCoordinate(const QMapbox::Coordinate &in); QMapbox::CoordinatesCollections model_to_collection( const cereal::LiveLocationKalman::Measurement::Reader &calibratedOrientationECEF, @@ -19,6 +24,7 @@ QMapbox::CoordinatesCollections model_to_collection( QMapbox::CoordinatesCollections coordinate_to_collection(QMapbox::Coordinate c); QMapbox::CoordinatesCollections capnp_coordinate_list_to_collection(const capnp::List::Reader &coordinate_list); QMapbox::CoordinatesCollections coordinate_list_to_collection(QList coordinate_list); +QList polyline_to_coordinate_list(const QString &polylineString); float minimum_distance(QGeoCoordinate a, QGeoCoordinate b, QGeoCoordinate p); std::optional coordinate_from_param(std::string param); diff --git a/selfdrive/ui/qt/maps/map_settings.cc b/selfdrive/ui/qt/maps/map_settings.cc index 60a781226..e130a9a1e 100644 --- a/selfdrive/ui/qt/maps/map_settings.cc +++ b/selfdrive/ui/qt/maps/map_settings.cc @@ -127,8 +127,7 @@ MapPanel::MapPanel(QWidget* parent) : QWidget(parent) { { QString url = CommaApi::BASE_URL + "/v1/navigation/" + *dongle_id + "/locations"; RequestRepeater* repeater = new RequestRepeater(this, url, "ApiCache_NavDestinations", 30, true); - QObject::connect(repeater, &RequestRepeater::receivedResponse, this, &MapPanel::parseResponse); - QObject::connect(repeater, &RequestRepeater::failedResponse, this, &MapPanel::failedResponse); + QObject::connect(repeater, &RequestRepeater::requestDone, this, &MapPanel::parseResponse); } // Destination set while offline @@ -137,8 +136,8 @@ MapPanel::MapPanel(QWidget* parent) : QWidget(parent) { RequestRepeater* repeater = new RequestRepeater(this, url, "", 10, true); HttpRequest* deleter = new HttpRequest(this); - QObject::connect(repeater, &RequestRepeater::receivedResponse, [=](QString resp) { - if (resp != "null") { + QObject::connect(repeater, &RequestRepeater::requestDone, [=](const QString &resp, bool success) { + if (success && resp != "null") { if (params.get("NavDestination").empty()) { qWarning() << "Setting NavDestination from /next" << resp; params.put("NavDestination", resp.toStdString()); @@ -183,7 +182,12 @@ void MapPanel::updateCurrentRoute() { current_widget->setVisible(dest.size() && !doc.isNull()); } -void MapPanel::parseResponse(const QString &response) { +void MapPanel::parseResponse(const QString &response, bool success) { + if (!success) { + stack->setCurrentIndex(1); + return; + } + QJsonDocument doc = QJsonDocument::fromJson(response.trimmed().toUtf8()); if (doc.isNull()) { qDebug() << "JSON Parse failed on navigation locations"; @@ -283,10 +287,6 @@ void MapPanel::parseResponse(const QString &response) { repaint(); } -void MapPanel::failedResponse(const QString &response) { - stack->setCurrentIndex(1); -} - void MapPanel::navigateTo(const QJsonObject &place) { QJsonDocument doc(place); params.put("NavDestination", doc.toJson().toStdString()); diff --git a/selfdrive/ui/qt/maps/map_settings.h b/selfdrive/ui/qt/maps/map_settings.h index ec409c496..03720edee 100644 --- a/selfdrive/ui/qt/maps/map_settings.h +++ b/selfdrive/ui/qt/maps/map_settings.h @@ -17,8 +17,7 @@ public: explicit MapPanel(QWidget* parent = nullptr); void navigateTo(const QJsonObject &place); - void parseResponse(const QString &response); - void failedResponse(const QString &response); + void parseResponse(const QString &response, bool success); void updateCurrentRoute(); void clear(); diff --git a/selfdrive/ui/qt/offroad/driverview.cc b/selfdrive/ui/qt/offroad/driverview.cc index f988d193e..6f03e2ed4 100644 --- a/selfdrive/ui/qt/offroad/driverview.cc +++ b/selfdrive/ui/qt/offroad/driverview.cc @@ -12,7 +12,7 @@ DriverViewWindow::DriverViewWindow(QWidget* parent) : QWidget(parent) { layout = new QStackedLayout(this); layout->setStackingMode(QStackedLayout::StackAll); - cameraView = new CameraViewWidget(VISION_STREAM_RGB_FRONT, true, this); + cameraView = new CameraViewWidget("camerad", VISION_STREAM_RGB_FRONT, true, this); layout->addWidget(cameraView); scene = new DriverViewScene(this); diff --git a/selfdrive/ui/qt/offroad/networking.h b/selfdrive/ui/qt/offroad/networking.h index b5a0c2ba2..037ef82f6 100644 --- a/selfdrive/ui/qt/offroad/networking.h +++ b/selfdrive/ui/qt/offroad/networking.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include diff --git a/selfdrive/ui/qt/offroad/onboarding.cc b/selfdrive/ui/qt/offroad/onboarding.cc index aa03ac63d..458464239 100644 --- a/selfdrive/ui/qt/offroad/onboarding.cc +++ b/selfdrive/ui/qt/offroad/onboarding.cc @@ -16,6 +16,11 @@ TrainingGuide::TrainingGuide(QWidget *parent) : QFrame(parent) { } void TrainingGuide::mouseReleaseEvent(QMouseEvent *e) { + if (click_timer.elapsed() < 250) { + return; + } + click_timer.restart(); + if (boundingRect[currentIndex].contains(e->x(), e->y())) { if (currentIndex == 9) { const QRect yes = QRect(692, 842, 492, 148); @@ -40,6 +45,7 @@ void TrainingGuide::showEvent(QShowEvent *event) { currentIndex = 0; image.load(img_path + "step0.png"); + click_timer.start(); } void TrainingGuide::paintEvent(QPaintEvent *event) { @@ -145,7 +151,7 @@ void DeclinePage::showEvent(QShowEvent *event) { QObject::connect(back_btn, &QPushButton::clicked, this, &DeclinePage::getBack); - QPushButton *uninstall_btn = new QPushButton("Decline, uninstall " + getBrand()); + QPushButton *uninstall_btn = new QPushButton(QString("Decline, uninstall %1").arg(getBrand())); uninstall_btn->setStyleSheet("background-color: #B73D3D"); buttons->addWidget(uninstall_btn); QObject::connect(uninstall_btn, &QPushButton::clicked, [=]() { diff --git a/selfdrive/ui/qt/offroad/onboarding.h b/selfdrive/ui/qt/offroad/onboarding.h index 7ae72649d..9424c07d1 100644 --- a/selfdrive/ui/qt/offroad/onboarding.h +++ b/selfdrive/ui/qt/offroad/onboarding.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -74,6 +75,7 @@ private: QString img_path; QVector boundingRect; + QElapsedTimer click_timer; signals: void completedTraining(); diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index e015e830a..c01da2140 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -1,6 +1,7 @@ #include "selfdrive/ui/qt/offroad/settings.h" #include +#include #include #include @@ -58,12 +59,6 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { "Use features, such as community supported hardware, from the open source community that are not maintained or supported by comma.ai and have not been confirmed to meet the standard safety model. Be extra cautious when using these features", "../assets/offroad/icon_shell.png", }, - { - "UploadRaw", - "Upload Raw Logs", - "Upload full logs and full resolution video by default while on Wi-Fi. If not enabled, individual logs can be marked for upload at useradmin.comma.ai.", - "../assets/offroad/icon_network.png", - }, { "RecordFront", "Record and Upload Driver Camera", @@ -109,74 +104,51 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { } } -DevicePanel::DevicePanel(QWidget* parent) : ListWidget(parent) { +DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { setSpacing(50); - Params params = Params(); addItem(new LabelControl("Dongle ID", getDongleId().value_or("N/A"))); - - QString serial = QString::fromStdString(params.get("HardwareSerial", false)); - addItem(new LabelControl("Serial", serial)); + addItem(new LabelControl("Serial", params.get("HardwareSerial").c_str())); // offroad-only buttons auto dcamBtn = new ButtonControl("Driver Camera", "PREVIEW", - "Preview the driver facing camera to help optimize device mounting position for best driver monitoring experience. (vehicle must be off)"); + "Preview the driver facing camera to help optimize device mounting position for best driver monitoring experience. (vehicle must be off)"); connect(dcamBtn, &ButtonControl::clicked, [=]() { emit showDriverView(); }); + addItem(dcamBtn); - QString resetCalibDesc = "openpilot requires the device to be mounted within 4° left or right and within 5° up or down. openpilot is continuously calibrating, resetting is rarely required."; - auto resetCalibBtn = new ButtonControl("Reset Calibration", "RESET", resetCalibDesc); - connect(resetCalibBtn, &ButtonControl::clicked, [=]() { + auto resetCalibBtn = new ButtonControl("Reset Calibration", "RESET", " "); + connect(resetCalibBtn, &ButtonControl::showDescription, this, &DevicePanel::updateCalibDescription); + connect(resetCalibBtn, &ButtonControl::clicked, [&]() { if (ConfirmationDialog::confirm("Are you sure you want to reset calibration?", this)) { - Params().remove("CalibrationParams"); + params.remove("CalibrationParams"); } }); - connect(resetCalibBtn, &ButtonControl::showDescription, [=]() { - QString desc = resetCalibDesc; - std::string calib_bytes = Params().get("CalibrationParams"); - if (!calib_bytes.empty()) { - try { - AlignedBuffer aligned_buf; - capnp::FlatArrayMessageReader cmsg(aligned_buf.align(calib_bytes.data(), calib_bytes.size())); - auto calib = cmsg.getRoot().getLiveCalibration(); - if (calib.getCalStatus() != 0) { - double pitch = calib.getRpyCalib()[1] * (180 / M_PI); - double yaw = calib.getRpyCalib()[2] * (180 / M_PI); - desc += QString(" Your device is pointed %1° %2 and %3° %4.") - .arg(QString::number(std::abs(pitch), 'g', 1), pitch > 0 ? "up" : "down", - QString::number(std::abs(yaw), 'g', 1), yaw > 0 ? "right" : "left"); - } - } catch (kj::Exception) { - qInfo() << "invalid CalibrationParams"; - } - } - resetCalibBtn->setDescription(desc); - }); + addItem(resetCalibBtn); - ButtonControl *retrainingBtn = nullptr; if (!params.getBool("Passive")) { - retrainingBtn = new ButtonControl("Review Training Guide", "REVIEW", "Review the rules, features, and limitations of openpilot"); + auto retrainingBtn = new ButtonControl("Review Training Guide", "REVIEW", "Review the rules, features, and limitations of openpilot"); connect(retrainingBtn, &ButtonControl::clicked, [=]() { if (ConfirmationDialog::confirm("Are you sure you want to review the training guide?", this)) { emit reviewTrainingGuide(); } }); + addItem(retrainingBtn); } - ButtonControl *regulatoryBtn = nullptr; if (Hardware::TICI()) { - regulatoryBtn = new ButtonControl("Regulatory", "VIEW", ""); + auto regulatoryBtn = new ButtonControl("Regulatory", "VIEW", ""); connect(regulatoryBtn, &ButtonControl::clicked, [=]() { const std::string txt = util::read_file("../assets/offroad/fcc.html"); RichTextDialog::alert(QString::fromStdString(txt), this); }); + addItem(regulatoryBtn); } - for (auto btn : {dcamBtn, resetCalibBtn, retrainingBtn, regulatoryBtn}) { - if (btn) { - connect(parent, SIGNAL(offroadTransition(bool)), btn, SLOT(setEnabled(bool))); - addItem(btn); + QObject::connect(parent, &SettingsWindow::offroadTransition, [=](bool offroad) { + for (auto btn : findChildren()) { + btn->setEnabled(offroad); } - } + }); // power buttons QHBoxLayout *power_layout = new QHBoxLayout(); @@ -185,48 +157,72 @@ DevicePanel::DevicePanel(QWidget* parent) : ListWidget(parent) { QPushButton *reboot_btn = new QPushButton("Reboot"); reboot_btn->setObjectName("reboot_btn"); power_layout->addWidget(reboot_btn); - QObject::connect(reboot_btn, &QPushButton::clicked, [=]() { - if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) { - if (ConfirmationDialog::confirm("Are you sure you want to reboot?", this)) { - // Check engaged again in case it changed while the dialog was open - if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) { - Params().putBool("DoReboot", true); - } - } - } else { - ConfirmationDialog::alert("Disengage to Reboot", this); - } - }); + QObject::connect(reboot_btn, &QPushButton::clicked, this, &DevicePanel::reboot); QPushButton *poweroff_btn = new QPushButton("Power Off"); poweroff_btn->setObjectName("poweroff_btn"); power_layout->addWidget(poweroff_btn); - QObject::connect(poweroff_btn, &QPushButton::clicked, [=]() { - if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) { - if (ConfirmationDialog::confirm("Are you sure you want to power off?", this)) { - // Check engaged again in case it changed while the dialog was open - if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) { - Params().putBool("DoShutdown", true); - } - } - } else { - ConfirmationDialog::alert("Disengage to Power Off", this); - } - }); + QObject::connect(poweroff_btn, &QPushButton::clicked, this, &DevicePanel::poweroff); setStyleSheet(R"( - QPushButton { - height: 120px; - border-radius: 15px; - } - #reboot_btn { background-color: #393939; } + #reboot_btn { height: 120px; border-radius: 15px; background-color: #393939; } #reboot_btn:pressed { background-color: #4a4a4a; } - #poweroff_btn { background-color: #E22C2C; } + #poweroff_btn { height: 120px; border-radius: 15px; background-color: #E22C2C; } #poweroff_btn:pressed { background-color: #FF2424; } )"); addItem(power_layout); } +void DevicePanel::updateCalibDescription() { + QString desc = + "openpilot requires the device to be mounted within 4° left or right and " + "within 5° up or down. openpilot is continuously calibrating, resetting is rarely required."; + std::string calib_bytes = Params().get("CalibrationParams"); + if (!calib_bytes.empty()) { + try { + AlignedBuffer aligned_buf; + capnp::FlatArrayMessageReader cmsg(aligned_buf.align(calib_bytes.data(), calib_bytes.size())); + auto calib = cmsg.getRoot().getLiveCalibration(); + if (calib.getCalStatus() != 0) { + double pitch = calib.getRpyCalib()[1] * (180 / M_PI); + double yaw = calib.getRpyCalib()[2] * (180 / M_PI); + desc += QString(" Your device is pointed %1° %2 and %3° %4.") + .arg(QString::number(std::abs(pitch), 'g', 1), pitch > 0 ? "up" : "down", + QString::number(std::abs(yaw), 'g', 1), yaw > 0 ? "right" : "left"); + } + } catch (kj::Exception) { + qInfo() << "invalid CalibrationParams"; + } + } + qobject_cast(sender())->setDescription(desc); +} + +void DevicePanel::reboot() { + if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) { + if (ConfirmationDialog::confirm("Are you sure you want to reboot?", this)) { + // Check engaged again in case it changed while the dialog was open + if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) { + Params().putBool("DoReboot", true); + } + } + } else { + ConfirmationDialog::alert("Disengage to Reboot", this); + } +} + +void DevicePanel::poweroff() { + if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) { + if (ConfirmationDialog::confirm("Are you sure you want to power off?", this)) { + // Check engaged again in case it changed while the dialog was open + if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) { + Params().putBool("DoShutdown", true); + } + } + } else { + ConfirmationDialog::alert("Disengage to Power Off", this); + } +} + SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { gitBranchLbl = new LabelControl("Git Branch"); gitCommitLbl = new LabelControl("Git Commit"); @@ -246,9 +242,9 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { auto uninstallBtn = new ButtonControl("Uninstall " + getBrand(), "UNINSTALL"); - connect(uninstallBtn, &ButtonControl::clicked, [=]() { + connect(uninstallBtn, &ButtonControl::clicked, [&]() { if (ConfirmationDialog::confirm("Are you sure you want to uninstall?", this)) { - Params().putBool("DoUninstall", true); + params.putBool("DoUninstall", true); } }); connect(parent, SIGNAL(offroadTransition(bool)), uninstallBtn, SLOT(setEnabled(bool))); @@ -325,33 +321,23 @@ void SettingsWindow::showEvent(QShowEvent *event) { } SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { + QHBoxLayout *main_layout = new QHBoxLayout(this); // setup two main layouts sidebar_widget = new QWidget; + sidebar_widget->setFixedWidth(500); QVBoxLayout *sidebar_layout = new QVBoxLayout(sidebar_widget); - sidebar_layout->setMargin(0); + sidebar_layout->setContentsMargins(50, 50, 100, 50); + main_layout->addWidget(sidebar_widget); + panel_widget = new QStackedWidget(); - panel_widget->setStyleSheet(R"( - border-radius: 30px; - background-color: #292929; - )"); + panel_widget->setObjectName("panel_widget"); + panel_widget->setContentsMargins(25, 25, 25, 25); + main_layout->addWidget(panel_widget); // close button QPushButton *close_btn = new QPushButton("×"); - close_btn->setStyleSheet(R"( - QPushButton { - font-size: 140px; - padding-bottom: 20px; - font-weight: bold; - border 1px grey solid; - border-radius: 100px; - background-color: #292929; - font-weight: 400; - } - QPushButton:pressed { - background-color: #3B3B3B; - } - )"); + close_btn->setObjectName("close_btn"); close_btn->setFixedSize(200, 200); sidebar_layout->addSpacing(45); sidebar_layout->addWidget(close_btn, 0, Qt::AlignCenter); @@ -375,36 +361,17 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { QObject::connect(map_panel, &MapPanel::closeSettings, this, &SettingsWindow::closeSettings); #endif - const int padding = panels.size() > 3 ? 25 : 35; - - nav_btns = new QButtonGroup(); + nav_btns = new QButtonGroup(this); for (auto &[name, panel] : panels) { QPushButton *btn = new QPushButton(name); btn->setCheckable(true); btn->setChecked(nav_btns->buttons().size() == 0); - btn->setStyleSheet(QString(R"( - QPushButton { - color: grey; - border: none; - background: none; - font-size: 65px; - font-weight: 500; - padding-top: %1px; - padding-bottom: %1px; - } - QPushButton:checked { - color: white; - } - QPushButton:pressed { - color: #ADADAD; - } - )").arg(padding)); - + btn->setProperty("type", "menu"); nav_btns->addButton(btn); sidebar_layout->addWidget(btn, 0, Qt::AlignRight); - const int lr_margin = name != "Network" ? 50 : 0; // Network panel handles its own margins - panel->setContentsMargins(lr_margin, 25, lr_margin, 25); + const int lr_margin = name != "Network" ? 25 : 0; // Network panel handles its own margins + panel->setContentsMargins(lr_margin, 0, lr_margin, 0); ScrollView *panel_frame = new ScrollView(panel, this); panel_widget->addWidget(panel_frame); @@ -414,16 +381,9 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { panel_widget->setCurrentWidget(w); }); } - sidebar_layout->setContentsMargins(50, 50, 100, 50); - // main settings layout, sidebar + main panel - QHBoxLayout *main_layout = new QHBoxLayout(this); - - sidebar_widget->setFixedWidth(500); - main_layout->addWidget(sidebar_widget); - main_layout->addWidget(panel_widget); - - setStyleSheet(R"( + const int padding = panels.size() > 3 ? 25 : 35; + setStyleSheet(QString(R"( * { color: white; font-size: 50px; @@ -431,7 +391,38 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { SettingsWindow { background-color: black; } - )"); + #panel_widget{ + border-radius: 30px; + background-color: #292929; + } + QPushButton#close_btn { + font-size: 140px; + padding-bottom: 20px; + font-weight: bold; + border 1px grey solid; + border-radius: 100px; + background-color: #292929; + font-weight: 400; + } + QPushButton#close_btn:pressed { + background-color: #3B3B3B; + } + QPushButton[type="menu"] { + color: grey; + border: none; + background: none; + font-size: 65px; + font-weight: 500; + padding-top: %1px; + padding-bottom: %1px; + } + QPushButton[type="menu"]:checked { + color: white; + } + QPushButton[type="menu"]:pressed { + color: #ADADAD; + } + )").arg(padding)); } void SettingsWindow::hideEvent(QHideEvent *event) { diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index a2c7e01d8..7fc5a8581 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -38,10 +38,18 @@ private: class DevicePanel : public ListWidget { Q_OBJECT public: - explicit DevicePanel(QWidget* parent = nullptr); + explicit DevicePanel(SettingsWindow *parent); signals: void reviewTrainingGuide(); void showDriverView(); + +private slots: + void poweroff(); + void reboot(); + void updateCalibDescription(); + +private: + Params params; }; class TogglesPanel : public ListWidget { diff --git a/selfdrive/ui/qt/offroad/wifiManager.cc b/selfdrive/ui/qt/offroad/wifiManager.cc index e798949a8..097017392 100644 --- a/selfdrive/ui/qt/offroad/wifiManager.cc +++ b/selfdrive/ui/qt/offroad/wifiManager.cc @@ -9,7 +9,7 @@ #include "selfdrive/ui/qt/util.h" template -T get_response(QDBusMessage response) { +T get_response(const QDBusMessage &response) { QVariant first = response.arguments().at(0); QDBusVariant dbvFirst = first.value(); QVariant vFirst = dbvFirst.variant(); diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index e7a233000..53cc96367 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -1,13 +1,14 @@ #include "selfdrive/ui/qt/onroad.h" +#include + #include #include "selfdrive/common/timing.h" -#include "selfdrive/ui/paint.h" #include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/qt/api.h" #ifdef ENABLE_MAPS #include "selfdrive/ui/qt/maps/map.h" +#include "selfdrive/ui/qt/maps/map_helpers.h" #endif OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) { @@ -17,13 +18,18 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) { stacked_layout->setStackingMode(QStackedLayout::StackAll); main_layout->addLayout(stacked_layout); + QStackedLayout *road_view_layout = new QStackedLayout; + road_view_layout->setStackingMode(QStackedLayout::StackAll); nvg = new NvgWindow(VISION_STREAM_RGB_BACK, this); + road_view_layout->addWidget(nvg); + hud = new OnroadHud(this); + road_view_layout->addWidget(hud); QWidget * split_wrapper = new QWidget; split = new QHBoxLayout(split_wrapper); split->setContentsMargins(0, 0, 0, 0); split->setSpacing(0); - split->addWidget(nvg); + split->addLayout(road_view_layout); stacked_layout->addWidget(split_wrapper); @@ -48,6 +54,9 @@ void OnroadWindow::updateState(const UIState &s) { } alerts->updateAlert(alert, bgColor); } + + hud->updateState(s); + if (bg != bgColor) { // repaint border bg = bgColor; @@ -68,19 +77,7 @@ void OnroadWindow::offroadTransition(bool offroad) { #ifdef ENABLE_MAPS if (!offroad) { if (map == nullptr && (QUIState::ui_state.has_prime || !MAPBOX_TOKEN.isEmpty())) { - QMapboxGLSettings settings; - - // Valid for 4 weeks since we can't swap tokens on the fly - QString token = MAPBOX_TOKEN.isEmpty() ? CommaApi::create_jwt({}, 4 * 7 * 24 * 3600) : MAPBOX_TOKEN; - - if (!Hardware::PC()) { - settings.setCacheDatabasePath("/data/mbgl-cache.db"); - } - settings.setApiBaseUrl(MAPS_HOST); - settings.setCacheDatabaseMaximumSize(20 * 1024 * 1024); - settings.setAccessToken(token.trimmed()); - - MapWindow * m = new MapWindow(settings); + MapWindow * m = new MapWindow(get_mapbox_settings()); m->setFixedWidth(topWidget(this)->width() / 2); QObject::connect(this, &OnroadWindow::offroadTransitionSignal, m, &MapWindow::offroadTransition); split->addWidget(m, 0, Qt::AlignRight); @@ -103,6 +100,7 @@ void OnroadWindow::paintEvent(QPaintEvent *event) { // ***** onroad widgets ***** +// OnroadAlerts void OnroadAlerts::updateAlert(const Alert &a, const QColor &color) { if (!alert.equal(a) || color != bg) { alert = a; @@ -162,6 +160,106 @@ void OnroadAlerts::paintEvent(QPaintEvent *event) { } } +// OnroadHud +OnroadHud::OnroadHud(QWidget *parent) : QWidget(parent) { + engage_img = QPixmap("../assets/img_chffr_wheel.png").scaled(img_size, img_size, Qt::KeepAspectRatio, Qt::SmoothTransformation); + dm_img = QPixmap("../assets/img_driver_face.png").scaled(img_size, img_size, Qt::KeepAspectRatio, Qt::SmoothTransformation); + + connect(this, &OnroadHud::valueChanged, [=] { update(); }); +} + +void OnroadHud::updateState(const UIState &s) { + const int SET_SPEED_NA = 255; + const SubMaster &sm = *(s.sm); + const auto cs = sm["controlsState"].getControlsState(); + + float maxspeed = cs.getVCruise(); + bool cruise_set = maxspeed > 0 && (int)maxspeed != SET_SPEED_NA; + if (cruise_set && !s.scene.is_metric) { + maxspeed *= KM_TO_MILE; + } + QString maxspeed_str = cruise_set ? QString::number(std::nearbyint(maxspeed)) : "N/A"; + float cur_speed = std::max(0.0, sm["carState"].getCarState().getVEgo() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH)); + + setProperty("is_cruise_set", cruise_set); + setProperty("speed", QString::number(std::nearbyint(cur_speed))); + setProperty("maxSpeed", maxspeed_str); + setProperty("speedUnit", s.scene.is_metric ? "km/h" : "mph"); + setProperty("hideDM", cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE); + setProperty("status", s.status); + + // update engageability and DM icons at 2Hz + if (sm.frame % (UI_FREQ / 2) == 0) { + setProperty("engageable", cs.getEngageable() || cs.getEnabled()); + setProperty("dmActive", sm["driverMonitoringState"].getDriverMonitoringState().getIsActiveMode()); + } +} + +void OnroadHud::paintEvent(QPaintEvent *event) { + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); + + // Header gradient + QLinearGradient bg(0, header_h - (header_h / 2.5), 0, header_h); + bg.setColorAt(0, QColor::fromRgbF(0, 0, 0, 0.45)); + bg.setColorAt(1, QColor::fromRgbF(0, 0, 0, 0)); + p.fillRect(0, 0, width(), header_h, bg); + + // max speed + QRect rc(bdr_s * 2, bdr_s * 1.5, 184, 202); + p.setPen(QPen(QColor(0xff, 0xff, 0xff, 100), 10)); + p.setBrush(QColor(0, 0, 0, 100)); + p.drawRoundedRect(rc, 20, 20); + p.setPen(Qt::NoPen); + + configFont(p, "Open Sans", 48, "Regular"); + drawText(p, rc.center().x(), 118, "MAX", is_cruise_set ? 200 : 100); + if (is_cruise_set) { + configFont(p, "Open Sans", 88, is_cruise_set ? "Bold" : "SemiBold"); + drawText(p, rc.center().x(), 212, maxSpeed, 255); + } else { + configFont(p, "Open Sans", 80, "SemiBold"); + drawText(p, rc.center().x(), 212, maxSpeed, 100); + } + + // current speed + configFont(p, "Open Sans", 176, "Bold"); + drawText(p, rect().center().x(), 210, speed); + configFont(p, "Open Sans", 66, "Regular"); + drawText(p, rect().center().x(), 290, speedUnit, 200); + + // engage-ability icon + if (engageable) { + drawIcon(p, rect().right() - radius / 2 - bdr_s * 2, radius / 2 + int(bdr_s * 1.5), + engage_img, bg_colors[status], 1.0); + } + + // dm icon + if (!hideDM) { + drawIcon(p, radius / 2 + (bdr_s * 2), rect().bottom() - footer_h / 2, + dm_img, QColor(0, 0, 0, 70), dmActive ? 1.0 : 0.2); + } +} + +void OnroadHud::drawText(QPainter &p, int x, int y, const QString &text, int alpha) { + QFontMetrics fm(p.font()); + QRect init_rect = fm.boundingRect(text); + QRect real_rect = fm.boundingRect(init_rect, 0, text); + real_rect.moveCenter({x, y - real_rect.height() / 2}); + + p.setPen(QColor(0xff, 0xff, 0xff, alpha)); + p.drawText(real_rect.x(), real_rect.bottom(), text); +} + +void OnroadHud::drawIcon(QPainter &p, int x, int y, QPixmap &img, QBrush bg, float opacity) { + p.setPen(Qt::NoPen); + p.setBrush(bg); + p.drawEllipse(x - radius / 2, y - radius / 2, radius, radius); + p.setOpacity(opacity); + p.drawPixmap(x - img_size / 2, y - img_size / 2, img); +} + +// NvgWindow void NvgWindow::initializeGL() { CameraViewWidget::initializeGL(); qInfo() << "OpenGL version:" << QString((const char*)glGetString(GL_VERSION)); @@ -169,14 +267,105 @@ void NvgWindow::initializeGL() { qInfo() << "OpenGL renderer:" << QString((const char*)glGetString(GL_RENDERER)); qInfo() << "OpenGL language version:" << QString((const char*)glGetString(GL_SHADING_LANGUAGE_VERSION)); - ui_nvg_init(&QUIState::ui_state); prev_draw_t = millis_since_boot(); setBackgroundColor(bg_colors[STATUS_DISENGAGED]); } +void NvgWindow::updateFrameMat(int w, int h) { + CameraViewWidget::updateFrameMat(w, h); + + UIState *s = &QUIState::ui_state; + s->fb_w = w; + s->fb_h = h; + auto intrinsic_matrix = s->wide_camera ? ecam_intrinsic_matrix : fcam_intrinsic_matrix; + float zoom = ZOOM / intrinsic_matrix.v[0]; + if (s->wide_camera) { + zoom *= 0.5; + } + // Apply transformation such that video pixel coordinates match video + // 1) Put (0, 0) in the middle of the video + // 2) Apply same scaling as video + // 3) Put (0, 0) in top left corner of video + s->car_space_transform.reset(); + s->car_space_transform.translate(w / 2, h / 2 + y_offset) + .scale(zoom, zoom) + .translate(-intrinsic_matrix.v[2], -intrinsic_matrix.v[5]); +} + +void NvgWindow::drawLaneLines(QPainter &painter, const UIScene &scene) { + if (!scene.end_to_end) { + // lanelines + for (int i = 0; i < std::size(scene.lane_line_vertices); ++i) { + painter.setBrush(QColor::fromRgbF(1.0, 1.0, 1.0, scene.lane_line_probs[i])); + painter.drawPolygon(scene.lane_line_vertices[i].v, scene.lane_line_vertices[i].cnt); + } + // road edges + for (int i = 0; i < std::size(scene.road_edge_vertices); ++i) { + painter.setBrush(QColor::fromRgbF(1.0, 0, 0, std::clamp(1.0 - scene.road_edge_stds[i], 0.0, 1.0))); + painter.drawPolygon(scene.road_edge_vertices[i].v, scene.road_edge_vertices[i].cnt); + } + } + // paint path + QLinearGradient bg(0, height(), 0, height() / 4); + bg.setColorAt(0, scene.end_to_end ? redColor() : QColor(255, 255, 255)); + bg.setColorAt(1, scene.end_to_end ? redColor(0) : QColor(255, 255, 255, 0)); + painter.setBrush(bg); + painter.drawPolygon(scene.track_vertices.v, scene.track_vertices.cnt); +} + +void NvgWindow::drawLead(QPainter &painter, const cereal::ModelDataV2::LeadDataV3::Reader &lead_data, const QPointF &vd) { + const float speedBuff = 10.; + const float leadBuff = 40.; + const float d_rel = lead_data.getX()[0]; + const float v_rel = lead_data.getV()[0]; + + float fillAlpha = 0; + if (d_rel < leadBuff) { + fillAlpha = 255 * (1.0 - (d_rel / leadBuff)); + if (v_rel < 0) { + fillAlpha += 255 * (-1 * (v_rel / speedBuff)); + } + fillAlpha = (int)(fmin(fillAlpha, 255)); + } + + float sz = std::clamp((25 * 30) / (d_rel / 3 + 30), 15.0f, 30.0f) * 2.35; + float x = std::clamp((float)vd.x(), 0.f, width() - sz / 2); + float y = std::fmin(height() - sz * .6, (float)vd.y()); + + float g_xo = sz / 5; + float g_yo = sz / 10; + + QPointF glow[] = {{x + (sz * 1.35) + g_xo, y + sz + g_yo}, {x, y - g_xo}, {x - (sz * 1.35) - g_xo, y + sz + g_yo}}; + painter.setBrush(QColor(218, 202, 37, 255)); + painter.drawPolygon(glow, std::size(glow)); + + // chevron + QPointF chevron[] = {{x + (sz * 1.25), y + sz}, {x, y}, {x - (sz * 1.25), y + sz}}; + painter.setBrush(redColor(fillAlpha)); + painter.drawPolygon(chevron, std::size(chevron)); +} + void NvgWindow::paintGL() { CameraViewWidget::paintGL(); - ui_draw(&QUIState::ui_state, width(), height()); + + UIState *s = &QUIState::ui_state; + if (s->scene.world_objects_visible) { + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + painter.setPen(Qt::NoPen); + + drawLaneLines(painter, s->scene); + + if (s->scene.longitudinal_control) { + auto leads = (*s->sm)["modelV2"].getModelV2().getLeadsV3(); + if (leads[0].getProb() > .5) { + drawLead(painter, leads[0], s->scene.lead_vertices[0]); + } + if (leads[1].getProb() > .5 && (std::abs(leads[1].getX()[0] - leads[0].getX()[0]) > 3.0)) { + drawLead(painter, leads[1], s->scene.lead_vertices[1]); + } + } + } double cur_draw_t = millis_since_boot(); double dt = cur_draw_t - prev_draw_t; @@ -189,6 +378,7 @@ void NvgWindow::paintGL() { void NvgWindow::showEvent(QShowEvent *event) { CameraViewWidget::showEvent(event); + ui_update_params(&QUIState::ui_state); prev_draw_t = millis_since_boot(); } diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h index ee44973cf..37460c8ba 100644 --- a/selfdrive/ui/qt/onroad.h +++ b/selfdrive/ui/qt/onroad.h @@ -9,6 +9,43 @@ // ***** onroad widgets ***** +class OnroadHud : public QWidget { + Q_OBJECT + Q_PROPERTY(QString speed MEMBER speed NOTIFY valueChanged); + Q_PROPERTY(QString speedUnit MEMBER speedUnit NOTIFY valueChanged); + Q_PROPERTY(QString maxSpeed MEMBER maxSpeed NOTIFY valueChanged); + Q_PROPERTY(bool is_cruise_set MEMBER is_cruise_set NOTIFY valueChanged); + Q_PROPERTY(bool engageable MEMBER engageable NOTIFY valueChanged); + Q_PROPERTY(bool dmActive MEMBER dmActive NOTIFY valueChanged); + Q_PROPERTY(bool hideDM MEMBER hideDM NOTIFY valueChanged); + Q_PROPERTY(int status MEMBER status NOTIFY valueChanged); + +public: + explicit OnroadHud(QWidget *parent); + void updateState(const UIState &s); + +private: + void drawIcon(QPainter &p, int x, int y, QPixmap &img, QBrush bg, float opacity); + void drawText(QPainter &p, int x, int y, const QString &text, int alpha = 255); + void paintEvent(QPaintEvent *event) override; + + QPixmap engage_img; + QPixmap dm_img; + const int radius = 192; + const int img_size = (radius / 2) * 1.5; + QString speed; + QString speedUnit; + QString maxSpeed; + bool is_cruise_set = false; + bool engageable = false; + bool dmActive = false; + bool hideDM = false; + int status = STATUS_DISENGAGED; + +signals: + void valueChanged(); +}; + class OnroadAlerts : public QWidget { Q_OBJECT @@ -29,12 +66,16 @@ class NvgWindow : public CameraViewWidget { Q_OBJECT public: - explicit NvgWindow(VisionStreamType type, QWidget* parent = 0) : CameraViewWidget(type, true, parent) {} + explicit NvgWindow(VisionStreamType type, QWidget* parent = 0) : CameraViewWidget("camerad", type, true, parent) {} protected: void paintGL() override; void initializeGL() override; void showEvent(QShowEvent *event) override; + void updateFrameMat(int w, int h) override; + void drawLaneLines(QPainter &painter, const UIScene &scene); + void drawLead(QPainter &painter, const cereal::ModelDataV2::LeadDataV3::Reader &lead_data, const QPointF &vd); + inline QColor redColor(int alpha = 255) { return QColor(201, 34, 49, alpha); } double prev_draw_t = 0; }; @@ -49,6 +90,7 @@ public: private: void paintEvent(QPaintEvent *event); void mousePressEvent(QMouseEvent* e) override; + OnroadHud *hud; OnroadAlerts *alerts; NvgWindow *nvg; QColor bg = bg_colors[STATUS_DISENGAGED]; diff --git a/selfdrive/ui/qt/request_repeater.cc b/selfdrive/ui/qt/request_repeater.cc index c11698d6f..d2c0f9bc3 100644 --- a/selfdrive/ui/qt/request_repeater.cc +++ b/selfdrive/ui/qt/request_repeater.cc @@ -15,10 +15,10 @@ RequestRepeater::RequestRepeater(QObject *parent, const QString &requestURL, con if (!cacheKey.isEmpty()) { prevResp = QString::fromStdString(params.get(cacheKey.toStdString())); if (!prevResp.isEmpty()) { - QTimer::singleShot(500, [=]() { emit receivedResponse(prevResp); }); + QTimer::singleShot(500, [=]() { emit requestDone(prevResp, true); }); } - QObject::connect(this, &HttpRequest::receivedResponse, [=](const QString &resp) { - if (resp != prevResp) { + QObject::connect(this, &HttpRequest::requestDone, [=](const QString &resp, bool success) { + if (success && resp != prevResp) { params.put(cacheKey.toStdString(), resp.toStdString()); prevResp = resp; } diff --git a/selfdrive/ui/qt/util.cc b/selfdrive/ui/qt/util.cc index 1661c6c5d..b1defb008 100644 --- a/selfdrive/ui/qt/util.cc +++ b/selfdrive/ui/qt/util.cc @@ -85,6 +85,7 @@ void setQtSurfaceFormat() { #else fmt.setRenderableType(QSurfaceFormat::OpenGLES); #endif + fmt.setSamples(16); QSurfaceFormat::setDefaultFormat(fmt); } diff --git a/selfdrive/ui/qt/widgets/cameraview.cc b/selfdrive/ui/qt/widgets/cameraview.cc index 6cc26a0ca..a238f13c8 100644 --- a/selfdrive/ui/qt/widgets/cameraview.cc +++ b/selfdrive/ui/qt/widgets/cameraview.cc @@ -1,12 +1,18 @@ #include "selfdrive/ui/qt/widgets/cameraview.h" +#ifdef __APPLE__ +#include +#else +#include +#endif + #include #include namespace { const char frame_vertex_shader[] = -#ifdef NANOVG_GL3_IMPLEMENTATION +#ifdef __APPLE__ "#version 150 core\n" #else "#version 300 es\n" @@ -21,7 +27,7 @@ const char frame_vertex_shader[] = "}\n"; const char frame_fragment_shader[] = -#ifdef NANOVG_GL3_IMPLEMENTATION +#ifdef __APPLE__ "#version 150 core\n" #else "#version 300 es\n" @@ -45,28 +51,22 @@ const mat4 device_transform = {{ 0.0, 0.0, 0.0, 1.0, }}; -mat4 get_driver_view_transform() { +mat4 get_driver_view_transform(int screen_width, int screen_height, int stream_width, int stream_height) { const float driver_view_ratio = 1.333; mat4 transform; - if (Hardware::TICI()) { - // from dmonitoring.cc - const int full_width_tici = 1928; - const int full_height_tici = 1208; - const int adapt_width_tici = 954; - const int crop_x_offset = -72; - const int crop_y_offset = -144; - const float yscale = full_height_tici * driver_view_ratio / adapt_width_tici; - const float xscale = yscale*(1080)/(2160)*full_width_tici/full_height_tici; + if (stream_width == TICI_CAM_WIDTH) { + const float yscale = stream_height * driver_view_ratio / tici_dm_crop::width; + const float xscale = yscale*screen_height/screen_width*stream_width/stream_height; transform = (mat4){{ - xscale, 0.0, 0.0, xscale*crop_x_offset/full_width_tici*2, - 0.0, yscale, 0.0, yscale*crop_y_offset/full_height_tici*2, + xscale, 0.0, 0.0, xscale*tici_dm_crop::x_offset/stream_width*2, + 0.0, yscale, 0.0, yscale*tici_dm_crop::y_offset/stream_height*2, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, }}; } else { // frame from 4/3 to 16/9 display transform = (mat4){{ - driver_view_ratio*(1080)/(1920), 0.0, 0.0, 0.0, + driver_view_ratio * screen_height / screen_width, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, @@ -94,11 +94,10 @@ mat4 get_fit_view_transform(float widget_aspect_ratio, float frame_aspect_ratio) } // namespace -CameraViewWidget::CameraViewWidget(VisionStreamType type, bool zoom, QWidget* parent) : - stream_type(type), zoomed_view(zoom), QOpenGLWidget(parent) { +CameraViewWidget::CameraViewWidget(std::string stream_name, VisionStreamType type, bool zoom, QWidget* parent) : + stream_name(stream_name), stream_type(type), zoomed_view(zoom), QOpenGLWidget(parent) { setAttribute(Qt::WA_OpaquePaintEvent); connect(this, &CameraViewWidget::vipcThreadConnected, this, &CameraViewWidget::vipcConnected, Qt::BlockingQueuedConnection); - connect(this, &CameraViewWidget::vipcThreadFrameReceived, this, &CameraViewWidget::vipcFrameReceived); } CameraViewWidget::~CameraViewWidget() { @@ -114,7 +113,7 @@ CameraViewWidget::~CameraViewWidget() { void CameraViewWidget::initializeGL() { initializeOpenGLFunctions(); - program = new QOpenGLShaderProgram(context()); + program = std::make_unique(context()); bool ret = program->addShaderFromSourceCode(QOpenGLShader::Vertex, frame_vertex_shader); assert(ret); ret = program->addShaderFromSourceCode(QOpenGLShader::Fragment, frame_fragment_shader); @@ -152,7 +151,7 @@ void CameraViewWidget::initializeGL() { } void CameraViewWidget::showEvent(QShowEvent *event) { - latest_frame = nullptr; + latest_texture_id = -1; if (!vipc_thread) { vipc_thread = new QThread(); connect(vipc_thread, &QThread::started, [=]() { vipcThread(); }); @@ -173,7 +172,7 @@ void CameraViewWidget::hideEvent(QHideEvent *event) { void CameraViewWidget::updateFrameMat(int w, int h) { if (zoomed_view) { if (stream_type == VISION_STREAM_RGB_FRONT) { - frame_mat = matmul(device_transform, get_driver_view_transform()); + frame_mat = matmul(device_transform, get_driver_view_transform(w, h, stream_width, stream_height)); } else { auto intrinsic_matrix = stream_type == VISION_STREAM_RGB_WIDE ? ecam_intrinsic_matrix : fcam_intrinsic_matrix; float zoom = ZOOM / intrinsic_matrix.v[0]; @@ -200,18 +199,17 @@ void CameraViewWidget::updateFrameMat(int w, int h) { } void CameraViewWidget::paintGL() { - if (!latest_frame) { + if (latest_texture_id == -1) { glClearColor(bg.redF(), bg.greenF(), bg.blueF(), bg.alphaF()); glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT); return; } - std::unique_lock lk(texture_lock); glViewport(0, 0, width(), height()); glBindVertexArray(frame_vao); glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, texture[latest_frame->idx]->frame_tex); + glBindTexture(GL_TEXTURE_2D, texture[latest_texture_id]->frame_tex); glUseProgram(program->programId()); glUniform1i(program->uniformLocation("uTexture"), 0); @@ -239,17 +237,12 @@ void CameraViewWidget::vipcConnected(VisionIpcClient *vipc_client) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED); assert(glGetError() == GL_NO_ERROR); } - latest_frame = nullptr; + latest_texture_id = -1; stream_width = vipc_client->buffers[0].width; stream_height = vipc_client->buffers[0].height; updateFrameMat(width(), height()); } -void CameraViewWidget::vipcFrameReceived(VisionBuf *buf) { - latest_frame = buf; - update(); -} - void CameraViewWidget::vipcThread() { VisionStreamType cur_stream_type = stream_type; std::unique_ptr vipc_client; @@ -276,7 +269,7 @@ void CameraViewWidget::vipcThread() { while (!QThread::currentThread()->isInterruptionRequested()) { if (!vipc_client || cur_stream_type != stream_type) { cur_stream_type = stream_type; - vipc_client.reset(new VisionIpcClient("camerad", cur_stream_type, true)); + vipc_client.reset(new VisionIpcClient(stream_name, cur_stream_type, true)); } if (!vipc_client->connected) { @@ -298,8 +291,6 @@ void CameraViewWidget::vipcThread() { if (VisionBuf *buf = vipc_client->recv(nullptr, 1000)) { if (!Hardware::EON()) { - std::unique_lock lk(texture_lock); - void *texture_buffer = gl_buffer->map(QOpenGLBuffer::WriteOnly); memcpy(texture_buffer, buf->addr, buf->len); gl_buffer->unmap(); @@ -309,13 +300,15 @@ void CameraViewWidget::vipcThread() { glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, buf->width, buf->height, GL_RGB, GL_UNSIGNED_BYTE, 0); glBindTexture(GL_TEXTURE_2D, 0); assert(glGetError() == GL_NO_ERROR); - - emit vipcThreadFrameReceived(buf); - - glFlush(); - } else { - emit vipcThreadFrameReceived(buf); + // use glFinish to ensure that the texture has been uploaded. + glFinish(); } + latest_texture_id = buf->idx; + // Schedule update. update() will be invoked on the gui thread. + QMetaObject::invokeMethod(this, "update"); + + // TODO: remove later, it's only connected by DriverView. + emit vipcThreadFrameReceived(buf); } } } diff --git a/selfdrive/ui/qt/widgets/cameraview.h b/selfdrive/ui/qt/widgets/cameraview.h index 90622c6b7..4cfba3c6f 100644 --- a/selfdrive/ui/qt/widgets/cameraview.h +++ b/selfdrive/ui/qt/widgets/cameraview.h @@ -7,6 +7,7 @@ #include #include #include "cereal/visionipc/visionipc_client.h" +#include "selfdrive/camerad/cameras/camera_common.h" #include "selfdrive/common/visionimg.h" #include "selfdrive/ui/ui.h" @@ -15,7 +16,7 @@ class CameraViewWidget : public QOpenGLWidget, protected QOpenGLFunctions { public: using QOpenGLWidget::QOpenGLWidget; - explicit CameraViewWidget(VisionStreamType stream_type, bool zoom, QWidget* parent = nullptr); + explicit CameraViewWidget(std::string stream_name, VisionStreamType stream_type, bool zoom, QWidget* parent = nullptr); ~CameraViewWidget(); void setStreamType(VisionStreamType type) { stream_type = type; } void setBackgroundColor(const QColor &color) { bg = color; } @@ -32,25 +33,24 @@ protected: void showEvent(QShowEvent *event) override; void hideEvent(QHideEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override { emit clicked(); } - void updateFrameMat(int w, int h); + virtual void updateFrameMat(int w, int h); void vipcThread(); bool zoomed_view; - VisionBuf *latest_frame = nullptr; + std::atomic latest_texture_id = -1; GLuint frame_vao, frame_vbo, frame_ibo; mat4 frame_mat; std::unique_ptr texture[UI_BUF_COUNT]; - QOpenGLShaderProgram *program; + std::unique_ptr program; QColor bg = QColor("#000000"); + std::string stream_name; int stream_width = 0; int stream_height = 0; std::atomic stream_type; QThread *vipc_thread = nullptr; - std::mutex texture_lock; protected slots: void vipcConnected(VisionIpcClient *vipc_client); - void vipcFrameReceived(VisionBuf *buf); }; diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc index 8b09ba2bb..da4e4d795 100644 --- a/selfdrive/ui/qt/widgets/controls.cc +++ b/selfdrive/ui/qt/widgets/controls.cc @@ -39,7 +39,7 @@ AbstractControl::AbstractControl(const QString &title, const QString &desc, cons // title title_label = new QPushButton(title); title_label->setFixedHeight(120); - title_label->setStyleSheet("font-size: 50px; font-weight: 400; text-align: left"); + title_label->setStyleSheet("border:none; font-size: 50px; font-weight: 400; text-align: left"); hlayout->addWidget(title_label); main_layout->addLayout(hlayout); diff --git a/selfdrive/ui/qt/widgets/drive_stats.cc b/selfdrive/ui/qt/widgets/drive_stats.cc index bd1dc7176..f4c8f502a 100644 --- a/selfdrive/ui/qt/widgets/drive_stats.cc +++ b/selfdrive/ui/qt/widgets/drive_stats.cc @@ -48,7 +48,7 @@ DriveStats::DriveStats(QWidget* parent) : QFrame(parent) { if (auto dongleId = getDongleId()) { QString url = CommaApi::BASE_URL + "/v1.1/devices/" + *dongleId + "/stats"; RequestRepeater* repeater = new RequestRepeater(this, url, "ApiCache_DriveStats", 30); - QObject::connect(repeater, &RequestRepeater::receivedResponse, this, &DriveStats::parseResponse); + QObject::connect(repeater, &RequestRepeater::requestDone, this, &DriveStats::parseResponse); } setStyleSheet(R"( @@ -76,7 +76,9 @@ void DriveStats::updateStats() { update(json["week"].toObject(), week_); } -void DriveStats::parseResponse(const QString& response) { +void DriveStats::parseResponse(const QString& response, bool success) { + if (!success) return; + QJsonDocument doc = QJsonDocument::fromJson(response.trimmed().toUtf8()); if (doc.isNull()) { qDebug() << "JSON Parse failed on getting past drives statistics"; diff --git a/selfdrive/ui/qt/widgets/drive_stats.h b/selfdrive/ui/qt/widgets/drive_stats.h index 40ecbdeaf..944629451 100644 --- a/selfdrive/ui/qt/widgets/drive_stats.h +++ b/selfdrive/ui/qt/widgets/drive_stats.h @@ -21,5 +21,5 @@ private: } all_, week_; private slots: - void parseResponse(const QString &response); + void parseResponse(const QString &response, bool success); }; diff --git a/selfdrive/ui/qt/widgets/keyboard.cc b/selfdrive/ui/qt/widgets/keyboard.cc index e6e60e409..1c5968653 100644 --- a/selfdrive/ui/qt/widgets/keyboard.cc +++ b/selfdrive/ui/qt/widgets/keyboard.cc @@ -126,7 +126,7 @@ Keyboard::Keyboard(QWidget *parent) : QFrame(parent) { std::vector> specials = { {"[","]","{","}","#","%","^","*","+","="}, {"_","\\","|","~","<",">","€","£","Â¥","•"}, - {"123",".",",","?","!","`",BACKSPACE_KEY}, + {"123",".",",","?","!","'",BACKSPACE_KEY}, {"ABC"," ",".",ENTER_KEY}, }; main_layout->addWidget(new KeyboardLayout(this, specials)); diff --git a/selfdrive/ui/qt/widgets/prime.cc b/selfdrive/ui/qt/widgets/prime.cc index 665466df5..ab6dc67d3 100644 --- a/selfdrive/ui/qt/widgets/prime.cc +++ b/selfdrive/ui/qt/widgets/prime.cc @@ -161,7 +161,7 @@ PrimeUserWidget::PrimeUserWidget(QWidget* parent) : QWidget(parent) { if (auto dongleId = getDongleId()) { QString url = CommaApi::BASE_URL + "/v1/devices/" + *dongleId + "/owner"; RequestRepeater *repeater = new RequestRepeater(this, url, "ApiCache_Owner", 6); - QObject::connect(repeater, &RequestRepeater::receivedResponse, this, &PrimeUserWidget::replyFinished); + QObject::connect(repeater, &RequestRepeater::requestDone, this, &PrimeUserWidget::replyFinished); } } @@ -291,14 +291,14 @@ SetupWidget::SetupWidget(QWidget* parent) : QFrame(parent) { QString url = CommaApi::BASE_URL + "/v1.1/devices/" + *dongleId + "/"; RequestRepeater* repeater = new RequestRepeater(this, url, "ApiCache_Device", 5); - QObject::connect(repeater, &RequestRepeater::failedResponse, this, &SetupWidget::show); - QObject::connect(repeater, &RequestRepeater::receivedResponse, this, &SetupWidget::replyFinished); + QObject::connect(repeater, &RequestRepeater::requestDone, this, &SetupWidget::replyFinished); } hide(); // Only show when first request comes back } -void SetupWidget::replyFinished(const QString &response) { +void SetupWidget::replyFinished(const QString &response, bool success) { show(); + if (!success) return; QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8()); if (doc.isNull()) { diff --git a/selfdrive/ui/qt/widgets/prime.h b/selfdrive/ui/qt/widgets/prime.h index 6044f0610..f7470fe44 100644 --- a/selfdrive/ui/qt/widgets/prime.h +++ b/selfdrive/ui/qt/widgets/prime.h @@ -68,5 +68,5 @@ private: PrimeUserWidget *primeUser; private slots: - void replyFinished(const QString &response); + void replyFinished(const QString &response, bool success); }; diff --git a/selfdrive/ui/qt/widgets/scrollview.cc b/selfdrive/ui/qt/widgets/scrollview.cc index 1aa05b415..beb0b7d46 100644 --- a/selfdrive/ui/qt/widgets/scrollview.cc +++ b/selfdrive/ui/qt/widgets/scrollview.cc @@ -4,11 +4,16 @@ #include ScrollView::ScrollView(QWidget *w, QWidget *parent) : QScrollArea(parent) { + QPalette pal = palette(); + pal.setColor(QPalette::Background, QColor(0x29, 0x29, 0x29)); + w->setAutoFillBackground(true); + w->setPalette(pal); + setWidget(w); setWidgetResizable(true); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setStyleSheet("background-color: transparent;"); + setFrameStyle(QFrame::NoFrame); QString style = R"( QScrollBar:vertical { diff --git a/selfdrive/ui/qt/widgets/ssh_keys.cc b/selfdrive/ui/qt/widgets/ssh_keys.cc index c6af2f5cd..7630e6573 100644 --- a/selfdrive/ui/qt/widgets/ssh_keys.cc +++ b/selfdrive/ui/qt/widgets/ssh_keys.cc @@ -41,23 +41,22 @@ void SshControl::refresh() { void SshControl::getUserKeys(const QString &username) { HttpRequest *request = new HttpRequest(this, false); - QObject::connect(request, &HttpRequest::receivedResponse, [=](const QString &resp) { - if (!resp.isEmpty()) { - params.put("GithubUsername", username.toStdString()); - params.put("GithubSshKeys", resp.toStdString()); + QObject::connect(request, &HttpRequest::requestDone, [=](const QString &resp, bool success) { + if (success) { + if (!resp.isEmpty()) { + params.put("GithubUsername", username.toStdString()); + params.put("GithubSshKeys", resp.toStdString()); + } else { + ConfirmationDialog::alert(QString("Username '%1' has no keys on GitHub").arg(username), this); + } } else { - ConfirmationDialog::alert("Username '" + username + "' has no keys on GitHub", this); + if (request->timeout()) { + ConfirmationDialog::alert("Request timed out", this); + } else { + ConfirmationDialog::alert(QString("Username '%1' doesn't exist on GitHub").arg(username), this); + } } - refresh(); - request->deleteLater(); - }); - QObject::connect(request, &HttpRequest::failedResponse, [=] { - ConfirmationDialog::alert("Username '" + username + "' doesn't exist on GitHub", this); - refresh(); - request->deleteLater(); - }); - QObject::connect(request, &HttpRequest::timeoutResponse, [=] { - ConfirmationDialog::alert("Request timed out", this); + refresh(); request->deleteLater(); }); diff --git a/selfdrive/ui/replay/camera.h b/selfdrive/ui/replay/camera.h index 8b1fa7620..21e02292d 100644 --- a/selfdrive/ui/replay/camera.h +++ b/selfdrive/ui/replay/camera.h @@ -32,9 +32,9 @@ protected: void cameraThread(Camera &cam); Camera cameras_[MAX_CAMERAS] = { - {.type = RoadCam, .rgb_type = VISION_STREAM_RGB_BACK, .yuv_type = VISION_STREAM_YUV_BACK}, - {.type = DriverCam, .rgb_type = VISION_STREAM_RGB_FRONT, .yuv_type = VISION_STREAM_YUV_FRONT}, - {.type = WideRoadCam, .rgb_type = VISION_STREAM_RGB_WIDE, .yuv_type = VISION_STREAM_YUV_WIDE}, + {.type = RoadCam, .rgb_type = VISION_STREAM_RGB_BACK, .yuv_type = VISION_STREAM_ROAD}, + {.type = DriverCam, .rgb_type = VISION_STREAM_RGB_FRONT, .yuv_type = VISION_STREAM_DRIVER}, + {.type = WideRoadCam, .rgb_type = VISION_STREAM_RGB_WIDE, .yuv_type = VISION_STREAM_WIDE_ROAD}, }; std::atomic publishing_ = 0; std::unique_ptr vipc_server_; diff --git a/selfdrive/ui/replay/filereader.cc b/selfdrive/ui/replay/filereader.cc index d11025549..84dc76694 100644 --- a/selfdrive/ui/replay/filereader.cc +++ b/selfdrive/ui/replay/filereader.cc @@ -1,12 +1,7 @@ #include "selfdrive/ui/replay/filereader.h" -#include - -#include -#include #include #include -#include #include "selfdrive/common/util.h" #include "selfdrive/ui/replay/util.h" @@ -31,7 +26,7 @@ std::string FileReader::read(const std::string &file, std::atomic *abort) } else if (is_remote) { result = download(file, abort); if (cache_to_local_ && !result.empty()) { - std::ofstream fs(local_file, fs.binary | fs.out); + std::ofstream fs(local_file, std::ios::binary | std::ios::out); fs.write(result.data(), result.size()); } } @@ -39,23 +34,13 @@ std::string FileReader::read(const std::string &file, std::atomic *abort) } std::string FileReader::download(const std::string &url, std::atomic *abort) { - std::string result; - size_t remote_file_size = 0; for (int i = 0; i <= max_retries_ && !(abort && *abort); ++i) { - if (i > 0) { - std::cout << "download failed, retrying" << i << std::endl; + std::string result = httpGet(url, chunk_size_, abort); + if (!result.empty()) { + return result; } - if (remote_file_size <= 0) { - remote_file_size = getRemoteFileSize(url); - } - if (remote_file_size > 0 && !(abort && *abort)) { - std::ostringstream oss; - result.resize(remote_file_size); - oss.rdbuf()->pubsetbuf(result.data(), result.size()); - int chunks = chunk_size_ > 0 ? std::max(1, (int)std::nearbyint(remote_file_size / (float)chunk_size_)) : 1; - if (httpMultiPartDownload(url, oss, chunks, remote_file_size, abort)) { - return result; - } + if (i != max_retries_) { + std::cout << "download failed, retrying " << i + 1 << std::endl; } } return {}; diff --git a/selfdrive/ui/replay/filereader.h b/selfdrive/ui/replay/filereader.h index 06ce14e9f..34aa91e85 100644 --- a/selfdrive/ui/replay/filereader.h +++ b/selfdrive/ui/replay/filereader.h @@ -5,14 +5,14 @@ class FileReader { public: - FileReader(bool cache_to_local, int chunk_size = -1, int max_retries = 3) - : cache_to_local_(cache_to_local), chunk_size_(chunk_size), max_retries_(max_retries) {} + FileReader(bool cache_to_local, size_t chunk_size = 0, int retries = 3) + : cache_to_local_(cache_to_local), chunk_size_(chunk_size), max_retries_(retries) {} virtual ~FileReader() {} std::string read(const std::string &file, std::atomic *abort = nullptr); private: std::string download(const std::string &url, std::atomic *abort); - int chunk_size_; + size_t chunk_size_; int max_retries_; bool cache_to_local_; }; diff --git a/selfdrive/ui/replay/framereader.cc b/selfdrive/ui/replay/framereader.cc index fdbfc0672..32af922f1 100644 --- a/selfdrive/ui/replay/framereader.cc +++ b/selfdrive/ui/replay/framereader.cc @@ -27,43 +27,46 @@ enum AVPixelFormat get_hw_format(AVCodecContext *ctx, const enum AVPixelFormat * if (*p == *hw_pix_fmt) return *p; } printf("Please run replay with the --no-cuda flag!\n"); - assert(0); - return AV_PIX_FMT_NONE; + // fallback to YUV420p + *hw_pix_fmt = AV_PIX_FMT_NONE; + return AV_PIX_FMT_YUV420P; } } // namespace -FrameReader::FrameReader(bool local_cache, int chunk_size, int retries) : FileReader(local_cache, chunk_size, retries) { - input_ctx = avformat_alloc_context(); - sws_frame.reset(av_frame_alloc()); -} +FrameReader::FrameReader() {} FrameReader::~FrameReader() { - for (auto &f : frames_) { - av_packet_unref(&f.pkt); + for (AVPacket *pkt : packets) { + av_packet_free(&pkt); } if (decoder_ctx) avcodec_free_context(&decoder_ctx); if (input_ctx) avformat_close_input(&input_ctx); if (hw_device_ctx) av_buffer_unref(&hw_device_ctx); - if (rgb_sws_ctx_) sws_freeContext(rgb_sws_ctx_); - if (yuv_sws_ctx_) sws_freeContext(yuv_sws_ctx_); - if (avio_ctx_) { av_freep(&avio_ctx_->buffer); avio_context_free(&avio_ctx_); } } -bool FrameReader::load(const std::string &url, bool no_cuda, std::atomic *abort) { - std::string content = read(url, abort); - if (content.empty()) return false; +bool FrameReader::load(const std::string &url, bool no_cuda, std::atomic *abort, bool local_cache, int chunk_size, int retries) { + FileReader f(local_cache, chunk_size, retries); + std::string data = f.read(url, abort); + if (data.empty()) return false; + + return load((std::byte *)data.data(), data.size(), no_cuda, abort); +} + +bool FrameReader::load(const std::byte *data, size_t size, bool no_cuda, std::atomic *abort) { + input_ctx = avformat_alloc_context(); + if (!input_ctx) return false; struct buffer_data bd = { - .data = (uint8_t *)content.data(), + .data = (const uint8_t*)data, .offset = 0, - .size = content.size(), + .size = size, }; const int avio_ctx_buffer_size = 64 * 1024; unsigned char *avio_ctx_buffer = (unsigned char *)av_malloc(avio_ctx_buffer_size); @@ -71,11 +74,11 @@ bool FrameReader::load(const std::string &url, bool no_cuda, std::atomic * input_ctx->pb = avio_ctx_; input_ctx->probesize = 10 * 1024 * 1024; // 10MB - int ret = avformat_open_input(&input_ctx, url.c_str(), NULL, NULL); + int ret = avformat_open_input(&input_ctx, nullptr, nullptr, nullptr); if (ret != 0) { char err_str[1024] = {0}; av_strerror(ret, err_str, std::size(err_str)); - printf("Error loading video - %s - %s\n", err_str, url.c_str()); + printf("Error loading video - %s\n", err_str); return false; } @@ -96,36 +99,31 @@ bool FrameReader::load(const std::string &url, bool no_cuda, std::atomic * width = (decoder_ctx->width + 3) & ~3; height = decoder_ctx->height; - if (!no_cuda) { + if (has_cuda_device && !no_cuda) { if (!initHardwareDecoder(AV_HWDEVICE_TYPE_CUDA)) { printf("No CUDA capable device was found. fallback to CPU decoding.\n"); + } else { + nv12toyuv_buffer.resize(getYUVSize()); } } - rgb_sws_ctx_ = sws_getContext(decoder_ctx->width, decoder_ctx->height, sws_src_format, - width, height, AV_PIX_FMT_BGR24, - SWS_BILINEAR, NULL, NULL, NULL); - if (!rgb_sws_ctx_) return false; - yuv_sws_ctx_ = sws_getContext(decoder_ctx->width, decoder_ctx->height, sws_src_format, - width, height, AV_PIX_FMT_YUV420P, - SWS_BILINEAR, NULL, NULL, NULL); - if (!yuv_sws_ctx_) return false; - - ret = avcodec_open2(decoder_ctx, decoder, NULL); + ret = avcodec_open2(decoder_ctx, decoder, nullptr); if (ret < 0) return false; - frames_.reserve(60 * 20); // 20fps, one minute + packets.reserve(60 * 20); // 20fps, one minute while (!(abort && *abort)) { - Frame &frame = frames_.emplace_back(); - ret = av_read_frame(input_ctx, &frame.pkt); + AVPacket *pkt = av_packet_alloc(); + ret = av_read_frame(input_ctx, pkt); if (ret < 0) { - frames_.pop_back(); + av_packet_free(&pkt); valid_ = (ret == AVERROR_EOF); break; } + packets.push_back(pkt); // some stream seems to contian no keyframes - key_frames_count_ += frame.pkt.flags & AV_PKT_FLAG_KEY; + key_frames_count_ += pkt->flags & AV_PKT_FLAG_KEY; } + valid_ = valid_ && !packets.empty(); return valid_; } @@ -145,21 +143,12 @@ bool FrameReader::initHardwareDecoder(AVHWDeviceType hw_device_type) { int ret = av_hwdevice_ctx_create(&hw_device_ctx, hw_device_type, nullptr, nullptr, 0); if (ret < 0) { + hw_pix_fmt = AV_PIX_FMT_NONE; + has_cuda_device = false; printf("Failed to create specified HW device %d.\n", ret); return false; } - // get sws source format - AVHWFramesConstraints *hw_frames_const = av_hwdevice_get_hwframe_constraints(hw_device_ctx, nullptr); - assert(hw_frames_const != 0); - for (AVPixelFormat *p = hw_frames_const->valid_sw_formats; *p != AV_PIX_FMT_NONE; p++) { - if (sws_isSupportedInput(*p)) { - sws_src_format = *p; - break; - } - } - av_hwframe_constraints_free(&hw_frames_const); - decoder_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx); decoder_ctx->opaque = &hw_pix_fmt; decoder_ctx->get_format = get_hw_format; @@ -168,35 +157,29 @@ bool FrameReader::initHardwareDecoder(AVHWDeviceType hw_device_type) { bool FrameReader::get(int idx, uint8_t *rgb, uint8_t *yuv) { assert(rgb || yuv); - if (!valid_ || idx < 0 || idx >= frames_.size()) { + if (!valid_ || idx < 0 || idx >= packets.size()) { return false; } return decode(idx, rgb, yuv); } bool FrameReader::decode(int idx, uint8_t *rgb, uint8_t *yuv) { - auto get_keyframe = [=](int idx) { - for (int i = idx; i >= 0 && key_frames_count_ > 1; --i) { - if (frames_[i].pkt.flags & AV_PKT_FLAG_KEY) return i; - } - return idx; - }; - int from_idx = idx; - if (idx > 0 && !frames_[idx].decoded && !frames_[idx - 1].decoded) { - // find the previous keyframe - from_idx = get_keyframe(idx); + if (idx != prev_idx + 1 && key_frames_count_ > 1) { + // seeking to the nearest key frame + for (int i = idx; i >= 0; --i) { + if (packets[i]->flags & AV_PKT_FLAG_KEY) { + from_idx = i; + break; + } + } } + prev_idx = idx; for (int i = from_idx; i <= idx; ++i) { - Frame &frame = frames_[i]; - if ((!frame.decoded || i == idx) && !frame.failed) { - AVFrame *f = decodeFrame(&frame.pkt); - frame.decoded = f != nullptr; - frame.failed = !frame.decoded; - if (frame.decoded && i == idx) { - return copyBuffers(f, rgb, yuv); - } + AVFrame *f = decodeFrame(packets[i]); + if (f && i == idx) { + return copyBuffers(f, rgb, yuv); } } return false; @@ -228,27 +211,26 @@ AVFrame *FrameReader::decodeFrame(AVPacket *pkt) { } bool FrameReader::copyBuffers(AVFrame *f, uint8_t *rgb, uint8_t *yuv) { - if (yuv) { - if (sws_src_format == AV_PIX_FMT_NV12) { - // libswscale crash if height is not 16 bytes aligned for NV12->YUV420 conversion - assert(sws_src_format == AV_PIX_FMT_NV12); + if (hw_pix_fmt == AV_PIX_FMT_CUDA) { + uint8_t *y = yuv ? yuv : nv12toyuv_buffer.data(); + uint8_t *u = y + width * height; + uint8_t *v = u + (width / 2) * (height / 2); + libyuv::NV12ToI420(f->data[0], f->linesize[0], f->data[1], f->linesize[1], + y, width, u, width / 2, v, width / 2, width, height); + libyuv::I420ToRGB24(y, width, u, width / 2, v, width / 2, + rgb, width * 3, width, height); + } else { + if (yuv) { uint8_t *u = yuv + width * height; uint8_t *v = u + (width / 2) * (height / 2); - libyuv::NV12ToI420(f->data[0], f->linesize[0], - f->data[1], f->linesize[1], - yuv, width, - u, width / 2, - v, width / 2, - width, height); - } else { - av_image_fill_arrays(sws_frame->data, sws_frame->linesize, yuv, AV_PIX_FMT_YUV420P, width, height, 1); - int ret = sws_scale(yuv_sws_ctx_, (const uint8_t **)f->data, f->linesize, 0, f->height, sws_frame->data, sws_frame->linesize); - if (ret < 0) return false; + memcpy(yuv, f->data[0], width * height); + memcpy(u, f->data[1], width / 2 * height / 2); + memcpy(v, f->data[2], width / 2 * height / 2); } + libyuv::I420ToRGB24(f->data[0], f->linesize[0], + f->data[1], f->linesize[1], + f->data[2], f->linesize[2], + rgb, width * 3, width, height); } - - // images is going to be written to output buffers, no alignment (align = 1) - av_image_fill_arrays(sws_frame->data, sws_frame->linesize, rgb, AV_PIX_FMT_BGR24, width, height, 1); - int ret = sws_scale(rgb_sws_ctx_, (const uint8_t **)f->data, f->linesize, 0, f->height, sws_frame->data, sws_frame->linesize); - return ret >= 0; + return true; } diff --git a/selfdrive/ui/replay/framereader.h b/selfdrive/ui/replay/framereader.h index c7b04138c..d572b727e 100644 --- a/selfdrive/ui/replay/framereader.h +++ b/selfdrive/ui/replay/framereader.h @@ -9,23 +9,22 @@ extern "C" { #include #include -#include -#include } struct AVFrameDeleter { void operator()(AVFrame* frame) const { av_frame_free(&frame); } }; -class FrameReader : protected FileReader { +class FrameReader { public: - FrameReader(bool local_cache = false, int chunk_size = -1, int retries = 0); + FrameReader(); ~FrameReader(); - bool load(const std::string &url, bool no_cuda = false, std::atomic *abort = nullptr); + bool load(const std::string &url, bool no_cuda = false, std::atomic *abort = nullptr, bool local_cache = false, int chunk_size = -1, int retries = 0); + bool load(const std::byte *data, size_t size, bool no_cuda = false, std::atomic *abort = nullptr); bool get(int idx, uint8_t *rgb, uint8_t *yuv); int getRGBSize() const { return width * height * 3; } int getYUVSize() const { return width * height * 3 / 2; } - size_t getFrameCount() const { return frames_.size(); } + size_t getFrameCount() const { return packets.size(); } bool valid() const { return valid_; } int width = 0, height = 0; @@ -36,15 +35,8 @@ private: AVFrame * decodeFrame(AVPacket *pkt); bool copyBuffers(AVFrame *f, uint8_t *rgb, uint8_t *yuv); - struct Frame { - AVPacket pkt = {}; - int decoded = false; - bool failed = false; - }; - std::vector frames_; - AVPixelFormat sws_src_format = AV_PIX_FMT_YUV420P; - SwsContext *rgb_sws_ctx_ = nullptr, *yuv_sws_ctx_ = nullptr; - std::unique_ptrav_frame_, sws_frame, hw_frame; + std::vector packets; + std::unique_ptrav_frame_, hw_frame; AVFormatContext *input_ctx = nullptr; AVCodecContext *decoder_ctx = nullptr; int key_frames_count_ = 0; @@ -53,4 +45,7 @@ private: AVPixelFormat hw_pix_fmt = AV_PIX_FMT_NONE; AVBufferRef *hw_device_ctx = nullptr; + std::vector nv12toyuv_buffer; + int prev_idx = -1; + inline static std::atomic has_cuda_device = true; }; diff --git a/selfdrive/ui/replay/logreader.cc b/selfdrive/ui/replay/logreader.cc index 10be8a19c..8e2836a4f 100644 --- a/selfdrive/ui/replay/logreader.cc +++ b/selfdrive/ui/replay/logreader.cc @@ -1,6 +1,7 @@ #include "selfdrive/ui/replay/logreader.h" #include +#include #include "selfdrive/ui/replay/util.h" Event::Event(const kj::ArrayPtr &amsg, bool frame) : reader(amsg), frame(frame) { @@ -26,7 +27,7 @@ Event::Event(const kj::ArrayPtr &amsg, bool frame) : reader(a // class LogReader -LogReader::LogReader(bool local_cache, int chunk_size, int retries, size_t memory_pool_block_size) : FileReader(local_cache, chunk_size, retries) { +LogReader::LogReader(size_t memory_pool_block_size) { #ifdef HAS_MEMORY_RESOURCE const size_t buf_size = sizeof(Event) * memory_pool_block_size; pool_buffer_ = ::operator new(buf_size); @@ -36,23 +37,35 @@ LogReader::LogReader(bool local_cache, int chunk_size, int retries, size_t memor } LogReader::~LogReader() { -#ifdef HAS_MEMORY_RESOURCE - delete mbr_; - ::operator delete(pool_buffer_); -#else for (Event *e : events) { delete e; } + +#ifdef HAS_MEMORY_RESOURCE + delete mbr_; + ::operator delete(pool_buffer_); #endif } -bool LogReader::load(const std::string &file, std::atomic *abort) { - raw_ = decompressBZ2(read(file, abort)); - if (raw_.empty()) return false; +bool LogReader::load(const std::string &url, std::atomic *abort, bool local_cache, int chunk_size, int retries) { + FileReader f(local_cache, chunk_size, retries); + std::string data = f.read(url, abort); + if (data.empty()) return false; + + return load((std::byte*)data.data(), data.size(), abort); +} + +bool LogReader::load(const std::byte *data, size_t size, std::atomic *abort) { + raw_ = decompressBZ2(data, size); + if (raw_.empty()) { + std::cout << "failed to decompress log" << std::endl; + return false; + } + + try { + kj::ArrayPtr words((const capnp::word *)raw_.data(), raw_.size() / sizeof(capnp::word)); + while (words.size() > 0) { - kj::ArrayPtr words((const capnp::word *)raw_.data(), raw_.size() / sizeof(capnp::word)); - while (words.size() > 0) { - try { #ifdef HAS_MEMORY_RESOURCE Event *evt = new (mbr_) Event(words); #else @@ -63,20 +76,26 @@ bool LogReader::load(const std::string &file, std::atomic *abort) { if (evt->which == cereal::Event::ROAD_ENCODE_IDX || evt->which == cereal::Event::DRIVER_ENCODE_IDX || evt->which == cereal::Event::WIDE_ROAD_ENCODE_IDX) { + #ifdef HAS_MEMORY_RESOURCE Event *frame_evt = new (mbr_) Event(words, true); #else Event *frame_evt = new Event(words, true); #endif + events.push_back(frame_evt); } words = kj::arrayPtr(evt->reader.getEnd(), words.end()); events.push_back(evt); - } catch (const kj::Exception &e) { - return false; } + } catch (const kj::Exception &e) { + std::cout << "failed to parse log : " << e.getDescription().cStr() << std::endl; + if (events.empty()) return false; + + std::cout << "read " << events.size() << " events from corrupt log" << std::endl; } + std::sort(events.begin(), events.end(), Event::lessThan()); return true; } diff --git a/selfdrive/ui/replay/logreader.h b/selfdrive/ui/replay/logreader.h index 5bb613d9d..33d7ea82f 100644 --- a/selfdrive/ui/replay/logreader.h +++ b/selfdrive/ui/replay/logreader.h @@ -46,11 +46,12 @@ public: bool frame; }; -class LogReader : protected FileReader { +class LogReader { public: - LogReader(bool local_cache = false, int chunk_size = -1, int retries = 0, size_t memory_pool_block_size = DEFAULT_EVENT_MEMORY_POOL_BLOCK_SIZE); + LogReader(size_t memory_pool_block_size = DEFAULT_EVENT_MEMORY_POOL_BLOCK_SIZE); ~LogReader(); - bool load(const std::string &file, std::atomic *abort = nullptr); + bool load(const std::string &url, std::atomic *abort = nullptr, bool local_cache = false, int chunk_size = -1, int retries = 0); + bool load(const std::byte *data, size_t size, std::atomic *abort = nullptr); std::vector events; diff --git a/selfdrive/ui/replay/replay.cc b/selfdrive/ui/replay/replay.cc index e3753ccf2..8155eb345 100644 --- a/selfdrive/ui/replay/replay.cc +++ b/selfdrive/ui/replay/replay.cc @@ -25,10 +25,11 @@ Replay::Replay(QString route, QStringList allow, QStringList block, SubMaster *s qDebug() << "services " << s; if (sm == nullptr) { - pm = new PubMaster(s); + pm = std::make_unique(s); } route_ = std::make_unique(route, data_dir); - events_ = new std::vector(); + events_ = std::make_unique>(); + new_events_ = std::make_unique>(); connect(this, &Replay::seekTo, this, &Replay::doSeek); connect(this, &Replay::segmentChanged, this, &Replay::queueSegment); @@ -36,8 +37,6 @@ Replay::Replay(QString route, QStringList allow, QStringList block, SubMaster *s Replay::~Replay() { stop(); - delete pm; - delete events_; } void Replay::stop() { @@ -181,28 +180,27 @@ void Replay::queueSegment() { void Replay::mergeSegments(const SegmentMap::iterator &begin, const SegmentMap::iterator &end) { // merge 3 segments in sequence. std::vector segments_need_merge; + size_t new_events_size = 0; for (auto it = begin; it != end && it->second->isLoaded() && segments_need_merge.size() < 3; ++it) { segments_need_merge.push_back(it->first); + new_events_size += it->second->log->events.size(); } if (segments_need_merge != segments_merged_) { qDebug() << "merge segments" << segments_need_merge; - std::vector *new_events = new std::vector(); - new_events->reserve(std::accumulate(segments_need_merge.begin(), segments_need_merge.end(), 0, - [=](int v, int n) { return v + segments_[n]->log->events.size(); })); + new_events_->clear(); + new_events_->reserve(new_events_size); for (int n : segments_need_merge) { - auto &e = segments_[n]->log->events; - auto middle = new_events->insert(new_events->end(), e.begin(), e.end()); - std::inplace_merge(new_events->begin(), middle, new_events->end(), Event::lessThan()); + const auto &e = segments_[n]->log->events; + auto middle = new_events_->insert(new_events_->end(), e.begin(), e.end()); + std::inplace_merge(new_events_->begin(), middle, new_events_->end(), Event::lessThan()); } - auto prev_events = events_; updateEvents([&]() { - events_ = new_events; + events_.swap(new_events_); segments_merged_ = segments_need_merge; return true; }); - delete prev_events; } } diff --git a/selfdrive/ui/replay/replay.h b/selfdrive/ui/replay/replay.h index 2d30d90e4..a3e6efaad 100644 --- a/selfdrive/ui/replay/replay.h +++ b/selfdrive/ui/replay/replay.h @@ -69,12 +69,13 @@ protected: bool events_updated_ = false; uint64_t route_start_ts_ = 0; uint64_t cur_mono_time_ = 0; - std::vector *events_ = nullptr; + std::unique_ptr> events_; + std::unique_ptr> new_events_; std::vector segments_merged_; // messaging SubMaster *sm = nullptr; - PubMaster *pm = nullptr; + std::unique_ptr pm; std::vector sockets_; std::unique_ptr route_; std::unique_ptr camera_server_; diff --git a/selfdrive/ui/replay/route.cc b/selfdrive/ui/replay/route.cc index 9ee1d387c..23c27073c 100644 --- a/selfdrive/ui/replay/route.cc +++ b/selfdrive/ui/replay/route.cc @@ -35,10 +35,8 @@ bool Route::load() { bool Route::loadFromServer() { QEventLoop loop; HttpRequest http(nullptr, !Hardware::PC()); - QObject::connect(&http, &HttpRequest::failedResponse, [&] { loop.exit(0); }); - QObject::connect(&http, &HttpRequest::timeoutResponse, [&] { loop.exit(0); }); - QObject::connect(&http, &HttpRequest::receivedResponse, [&](const QString &json) { - loop.exit(loadFromJson(json)); + QObject::connect(&http, &HttpRequest::requestDone, [&](const QString &json, bool success) { + loop.exit(success ? loadFromJson(json) : 0); }); http.sendRequest("https://api.commadotai.com/v1/route/" + route_.str + "/files"); return loop.exec(); @@ -118,11 +116,11 @@ void Segment::loadFile(int id, const std::string file) { const bool local_cache = !(flags & REPLAY_FLAG_NO_FILE_CACHE); bool success = false; if (id < MAX_CAMERAS) { - frames[id] = std::make_unique(local_cache, 20 * 1024 * 1024, 3); - success = frames[id]->load(file, flags & REPLAY_FLAG_NO_CUDA, &abort_); + frames[id] = std::make_unique(); + success = frames[id]->load(file, flags & REPLAY_FLAG_NO_CUDA, &abort_, local_cache, 20 * 1024 * 1024, 3); } else { - log = std::make_unique(local_cache, -1, 3); - success = log->load(file, &abort_); + log = std::make_unique(); + success = log->load(file, &abort_, local_cache, 0, 3); } if (!success) { diff --git a/selfdrive/ui/replay/util.cc b/selfdrive/ui/replay/util.cc index b4fcbf7e6..d6791465f 100644 --- a/selfdrive/ui/replay/util.cc +++ b/selfdrive/ui/replay/util.cc @@ -4,8 +4,10 @@ #include #include +#include #include -#include +#include +#include #include #include #include @@ -22,21 +24,34 @@ struct CURLGlobalInitializer { ~CURLGlobalInitializer() { curl_global_cleanup(); } }; +template struct MultiPartWriter { + T *buf; + size_t *total_written; size_t offset; size_t end; - size_t written; - std::ostream *os; + + size_t write(char *data, size_t size, size_t count) { + size_t bytes = size * count; + if ((offset + bytes) > end) return 0; + + if constexpr (std::is_same::value) { + memcpy(buf->data() + offset, data, bytes); + } else if constexpr (std::is_same::value) { + buf->seekp(offset); + buf->write(data, bytes); + } + + offset += bytes; + *total_written += bytes; + return bytes; + } }; +template size_t write_cb(char *data, size_t size, size_t count, void *userp) { - MultiPartWriter *w = (MultiPartWriter *)userp; - w->os->seekp(w->offset); - size_t bytes = size * count; - w->os->write(data, bytes); - w->offset += bytes; - w->written += bytes; - return bytes; + auto w = (MultiPartWriter *)userp; + return w->write(data, size, count); } size_t dumy_write_cb(char *data, size_t size, size_t count, void *userp) { return size * count; } @@ -64,12 +79,12 @@ size_t getRemoteFileSize(const std::string &url) { CURLcode res = curl_easy_perform(curl); double content_length = -1; if (res == CURLE_OK) { - res = curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &content_length); + curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &content_length); } else { std::cout << "Download failed: error code: " << res << std::endl; } curl_easy_cleanup(curl); - return content_length > 0 ? content_length : 0; + return content_length > 0 ? (size_t)content_length : 0; } std::string getUrlWithoutQuery(const std::string &url) { @@ -81,30 +96,32 @@ void enableHttpLogging(bool enable) { enable_http_logging = enable; } -bool httpMultiPartDownload(const std::string &url, std::ostream &os, int parts, size_t content_length, std::atomic *abort) { +template +bool httpDownload(const std::string &url, T &buf, size_t chunk_size, size_t content_length, std::atomic *abort) { static CURLGlobalInitializer curl_initializer; - static std::mutex lock; - static uint64_t total_written = 0, prev_total_written = 0; - static double last_print_ts = 0; - os.seekp(content_length - 1); - os.write("\0", 1); + int parts = 1; + if (chunk_size > 0 && content_length > 10 * 1024 * 1024) { + parts = std::nearbyint(content_length / (float)chunk_size); + parts = std::clamp(parts, 1, 5); + } CURLM *cm = curl_multi_init(); - - std::map writers; + size_t written = 0; + std::map> writers; const int part_size = content_length / parts; for (int i = 0; i < parts; ++i) { CURL *eh = curl_easy_init(); writers[eh] = { - .os = &os, + .buf = &buf, + .total_written = &written, .offset = (size_t)(i * part_size), - .end = i == parts - 1 ? content_length - 1 : (i + 1) * part_size - 1, + .end = i == parts - 1 ? content_length : (i + 1) * part_size, }; - curl_easy_setopt(eh, CURLOPT_WRITEFUNCTION, write_cb); + curl_easy_setopt(eh, CURLOPT_WRITEFUNCTION, write_cb); curl_easy_setopt(eh, CURLOPT_WRITEDATA, (void *)(&writers[eh])); curl_easy_setopt(eh, CURLOPT_URL, url.c_str()); - curl_easy_setopt(eh, CURLOPT_RANGE, util::string_format("%d-%d", writers[eh].offset, writers[eh].end).c_str()); + curl_easy_setopt(eh, CURLOPT_RANGE, util::string_format("%d-%d", writers[eh].offset, writers[eh].end - 1).c_str()); curl_easy_setopt(eh, CURLOPT_HTTPGET, 1); curl_easy_setopt(eh, CURLOPT_NOSIGNAL, 1); curl_easy_setopt(eh, CURLOPT_FOLLOWLOCATION, 1); @@ -112,27 +129,22 @@ bool httpMultiPartDownload(const std::string &url, std::ostream &os, int parts, curl_multi_add_handle(cm, eh); } - int still_running = 1; size_t prev_written = 0; + double last_print = millis_since_boot(); + int still_running = 1; while (still_running > 0 && !(abort && *abort)) { curl_multi_wait(cm, nullptr, 0, 1000, nullptr); curl_multi_perform(cm, &still_running); - size_t written = std::accumulate(writers.begin(), writers.end(), 0, [=](int v, auto &w) { return v + w.second.written; }); - int cur_written = written - prev_written; - prev_written = written; - - std::lock_guard lk(lock); - double ts = millis_since_boot(); - total_written += cur_written; - if ((ts - last_print_ts) > 2 * 1000) { - if (enable_http_logging && last_print_ts > 0) { - size_t average = (total_written - prev_total_written) / ((ts - last_print_ts) / 1000.); + if (enable_http_logging) { + if (double ts = millis_since_boot(); (ts - last_print) > 2 * 1000) { + size_t average = (written - prev_written) / ((ts - last_print) / 1000.); int progress = std::min(100, 100.0 * (double)written / (double)content_length); - std::cout << "downloading " << getUrlWithoutQuery(url) << " - " << progress << "% (" << formattedDataSize(average) << "/s)" << std::endl; + std::cout << "downloading " << getUrlWithoutQuery(url) << " - " << progress + << "% (" << formattedDataSize(average) << "/s)" << std::endl; + last_print = ts; + prev_written = written; } - prev_total_written = total_written; - last_print_ts = ts; } } @@ -155,29 +167,59 @@ bool httpMultiPartDownload(const std::string &url, std::ostream &os, int parts, } } - for (auto &[e, w] : writers) { + for (const auto &[e, w] : writers) { curl_multi_remove_handle(cm, e); curl_easy_cleanup(e); } - curl_multi_cleanup(cm); + return complete == parts; } +std::string httpGet(const std::string &url, size_t chunk_size, std::atomic *abort) { + size_t size = getRemoteFileSize(url); + if (size == 0) return {}; + + std::string result(size, '\0'); + return httpDownload(url, result, chunk_size, size, abort) ? result : ""; +} + +bool httpDownload(const std::string &url, const std::string &file, size_t chunk_size, std::atomic *abort) { + size_t size = getRemoteFileSize(url); + if (size == 0) return false; + + std::ofstream of(file, std::ios::binary | std::ios::out); + of.seekp(size - 1).write("\0", 1); + return httpDownload(url, of, chunk_size, size, abort); +} + std::string decompressBZ2(const std::string &in) { - if (in.empty()) return {}; + return decompressBZ2((std::byte *)in.data(), in.size()); +} + +std::string decompressBZ2(const std::byte *in, size_t in_size) { + if (in_size == 0) return {}; bz_stream strm = {}; int bzerror = BZ2_bzDecompressInit(&strm, 0, 0); assert(bzerror == BZ_OK); - strm.next_in = (char *)in.data(); - strm.avail_in = in.size(); - std::string out(in.size() * 5, '\0'); + strm.next_in = (char *)in; + strm.avail_in = in_size; + std::string out(in_size * 5, '\0'); do { strm.next_out = (char *)(&out[strm.total_out_lo32]); strm.avail_out = out.size() - strm.total_out_lo32; + + const char *prev_write_pos = strm.next_out; bzerror = BZ2_bzDecompress(&strm); + if (bzerror == BZ_OK && prev_write_pos == strm.next_out) { + // content is corrupt + bzerror = BZ_STREAM_END; + std::cout << "decompressBZ2 error : content is corrupt" << std::endl; + break; + } + if (bzerror == BZ_OK && strm.avail_in > 0 && strm.avail_out == 0) { out.resize(out.size() * 2); } diff --git a/selfdrive/ui/replay/util.h b/selfdrive/ui/replay/util.h index 30a26c431..726e65cb9 100644 --- a/selfdrive/ui/replay/util.h +++ b/selfdrive/ui/replay/util.h @@ -1,13 +1,14 @@ #pragma once #include -#include #include std::string sha256(const std::string &str); void precise_nano_sleep(long sleep_ns); std::string decompressBZ2(const std::string &in); +std::string decompressBZ2(const std::byte *in, size_t in_size); void enableHttpLogging(bool enable); std::string getUrlWithoutQuery(const std::string &url); size_t getRemoteFileSize(const std::string &url); -bool httpMultiPartDownload(const std::string &url, std::ostream &os, int parts, size_t content_length, std::atomic *abort = nullptr); +std::string httpGet(const std::string &url, size_t chunk_size = 0, std::atomic *abort = nullptr); +bool httpDownload(const std::string &url, const std::string &file, size_t chunk_size = 0, std::atomic *abort = nullptr); diff --git a/selfdrive/ui/soundd/sound.cc b/selfdrive/ui/soundd/sound.cc index df8b4fa6a..f303aa92b 100644 --- a/selfdrive/ui/soundd/sound.cc +++ b/selfdrive/ui/soundd/sound.cc @@ -1,5 +1,11 @@ #include "selfdrive/ui/soundd/sound.h" +#include + +#include +#include +#include + #include "cereal/messaging/messaging.h" #include "selfdrive/common/util.h" @@ -7,14 +13,15 @@ // TODO: detect when we can't display the UI Sound::Sound(QObject *parent) : sm({"carState", "controlsState", "deviceState"}) { - const QString sound_asset_path = Hardware::TICI() ? "../../assets/sounds_tici/" : "../../assets/sounds/"; + qInfo() << "default audio device: " << QAudioDeviceInfo::defaultOutputDevice().deviceName(); + for (auto &[alert, fn, loops] : sound_list) { QSoundEffect *s = new QSoundEffect(this); QObject::connect(s, &QSoundEffect::statusChanged, [=]() { assert(s->status() != QSoundEffect::Error); }); s->setVolume(Hardware::MIN_VOLUME); - s->setSource(QUrl::fromLocalFile(sound_asset_path + fn)); + s->setSource(QUrl::fromLocalFile("../../assets/sounds/" + fn)); sounds[alert] = {s, loops}; } @@ -42,8 +49,9 @@ void Sound::update() { // scale volume with speed if (sm.updated("carState")) { - float volume = util::map_val(sm["carState"].getCarState().getVEgo(), 0.f, 20.f, - Hardware::MIN_VOLUME, Hardware::MAX_VOLUME); + float volume = util::map_val(sm["carState"].getCarState().getVEgo(), 11.f, 20.f, 0.f, 1.0f); + volume = QAudio::convertVolume(volume, QAudio::LogarithmicVolumeScale, QAudio::LinearVolumeScale); + volume = util::map_val(volume, 0.f, 1.f, Hardware::MIN_VOLUME, Hardware::MAX_VOLUME); for (auto &[s, loops] : sounds) { s->setVolume(std::round(100 * volume) / 100); } diff --git a/selfdrive/ui/soundd/sound.h b/selfdrive/ui/soundd/sound.h index cbc7b8707..b493c3a3d 100644 --- a/selfdrive/ui/soundd/sound.h +++ b/selfdrive/ui/soundd/sound.h @@ -7,14 +7,16 @@ const std::tuple sound_list[] = { // AudibleAlert, file name, loop count - {AudibleAlert::CHIME_DISENGAGE, "disengaged.wav", 0}, - {AudibleAlert::CHIME_ENGAGE, "engaged.wav", 0}, - {AudibleAlert::CHIME_WARNING1, "warning_1.wav", 0}, - {AudibleAlert::CHIME_WARNING_REPEAT, "warning_repeat.wav", 10}, - {AudibleAlert::CHIME_WARNING_REPEAT_INFINITE, "warning_repeat.wav", QSoundEffect::Infinite}, - {AudibleAlert::CHIME_WARNING2_REPEAT_INFINITE, "warning_2.wav", QSoundEffect::Infinite}, - {AudibleAlert::CHIME_ERROR, "error.wav", 0}, - {AudibleAlert::CHIME_PROMPT, "error.wav", 0}, + {AudibleAlert::ENGAGE, "engage.wav", 0}, + {AudibleAlert::DISENGAGE, "disengage.wav", 0}, + {AudibleAlert::REFUSE, "refuse.wav", 0}, + + {AudibleAlert::PROMPT, "prompt.wav", 0}, + {AudibleAlert::PROMPT_REPEAT, "prompt.wav", QSoundEffect::Infinite}, + {AudibleAlert::PROMPT_DISTRACTED, "prompt_distracted.wav", QSoundEffect::Infinite}, + + {AudibleAlert::WARNING_SOFT, "warning_soft.wav", QSoundEffect::Infinite}, + {AudibleAlert::WARNING_IMMEDIATE, "warning_immediate.wav", 10}, }; class Sound : public QObject { diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 7a4de0658..38c08eb48 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -1,11 +1,11 @@ #include "selfdrive/ui/ui.h" -#include - #include #include -#include +#include "common/transformations/orientation.hpp" +#include "selfdrive/common/params.h" +#include "selfdrive/common/swaglog.h" #include "selfdrive/common/util.h" #include "selfdrive/common/watchdog.h" #include "selfdrive/hardware/hw.h" @@ -14,10 +14,9 @@ #define BACKLIGHT_TS 10.00 #define BACKLIGHT_OFFROAD 50 - // Projects a point in car to space to the corresponding point in full frame // image space. -static bool calib_frame_to_full_frame(const UIState *s, float in_x, float in_y, float in_z, vertex_data *out) { +static bool calib_frame_to_full_frame(const UIState *s, float in_x, float in_y, float in_z, QPointF *out) { const float margin = 500.0f; const QRectF clip_region{-margin, -margin, s->fb_w + 2 * margin, s->fb_h + 2 * margin}; @@ -28,8 +27,7 @@ static bool calib_frame_to_full_frame(const UIState *s, float in_x, float in_y, // Project. QPointF point = s->car_space_transform.map(QPointF{KEp.v[0] / KEp.v[2], KEp.v[1] / KEp.v[2]}); if (clip_region.contains(point)) { - out->x = point.x(); - out->y = point.y(); + *out = point; return true; } return false; @@ -57,7 +55,7 @@ static void update_leads(UIState *s, const cereal::RadarState::Reader &radar_sta static void update_line_data(const UIState *s, const cereal::ModelDataV2::XYZTData::Reader &line, float y_off, float z_off, line_vertices_data *pvd, int max_idx) { const auto line_x = line.getX(), line_y = line.getY(), line_z = line.getZ(); - vertex_data *v = &pvd->v[0]; + QPointF *v = &pvd->v[0]; for (int i = 0; i <= max_idx; i++) { v += calib_frame_to_full_frame(s, line_x[i], line_y[i] - y_off, line_z[i] + z_off, v); } @@ -108,18 +106,11 @@ static void update_sockets(UIState *s) { static void update_state(UIState *s) { SubMaster &sm = *(s->sm); UIScene &scene = s->scene; - s->running_time = 1e-9 * (nanos_since_boot() - sm["deviceState"].getDeviceState().getStartedMonoTime()); - // update engageability and DM icons at 2Hz - if (sm.frame % (UI_FREQ / 2) == 0) { - auto cs = sm["controlsState"].getControlsState(); - scene.engageable = cs.getEngageable() || cs.getEnabled(); - scene.dm_active = sm["driverMonitoringState"].getDriverMonitoringState().getIsActiveMode(); - } - if (sm.updated("modelV2") && s->vg) { + if (sm.updated("modelV2")) { update_model(s, sm["modelV2"].getModelV2()); } - if (sm.updated("radarState") && s->vg) { + if (sm.updated("radarState")) { std::optional line; if (sm.rcv_frame("modelV2") > 0) { line = sm["modelV2"].getModelV2().getPosition(); @@ -298,16 +289,6 @@ void Device::updateBrightness(const UIState &s) { // Scale back to 10% to 100% clipped_brightness = std::clamp(100.0f * clipped_brightness, 10.0f, 100.0f); - - // Limit brightness if running for too long - if (Hardware::TICI()) { - const float MAX_BRIGHTNESS_HOURS = 4; - const float HOURLY_BRIGHTNESS_DECREASE = 5; - float ui_running_hours = s.running_time / (60*60); - float anti_burnin_max_percent = std::clamp(100.0f - HOURLY_BRIGHTNESS_DECREASE * (ui_running_hours - MAX_BRIGHTNESS_HOURS), - 30.0f, 100.0f); - clipped_brightness = std::min(clipped_brightness, anti_burnin_max_percent); - } } int brightness = brightness_filter.update(clipped_brightness); diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index 46cbae63f..4b4ef0bb2 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -9,23 +8,11 @@ #include #include #include -#include "nanovg.h" #include "cereal/messaging/messaging.h" -#include "common/transformations/orientation.hpp" -#include "selfdrive/camerad/cameras/camera_common.h" -#include "selfdrive/common/mat.h" #include "selfdrive/common/modeldata.h" #include "selfdrive/common/params.h" -#include "selfdrive/common/util.h" - -#define COLOR_BLACK nvgRGBA(0, 0, 0, 255) -#define COLOR_BLACK_ALPHA(x) nvgRGBA(0, 0, 0, x) -#define COLOR_WHITE nvgRGBA(255, 255, 255, 255) -#define COLOR_WHITE_ALPHA(x) nvgRGBA(255, 255, 255, x) -#define COLOR_RED_ALPHA(x) nvgRGBA(201, 34, 49, x) -#define COLOR_YELLOW nvgRGBA(218, 202, 37, 255) -#define COLOR_RED nvgRGBA(201, 34, 49, 255) +#include "selfdrive/common/timing.h" const int bdr_s = 30; const int header_h = 420; @@ -39,17 +26,6 @@ typedef cereal::CarControl::HUDControl::AudibleAlert AudibleAlert; const float y_offset = Hardware::EON() ? 0.0 : 150.0; const float ZOOM = Hardware::EON() ? 2138.5 : 2912.8; -typedef struct Rect { - int x, y, w, h; - int centerX() const { return x + w / 2; } - int centerY() const { return y + h / 2; } - int right() const { return x + w; } - int bottom() const { return y + h; } - bool ptInRect(int px, int py) const { - return px >= x && px < (x + w) && py >= y && py < (y + h); - } -} Rect; - struct Alert { QString text1; QString text2; @@ -78,7 +54,7 @@ struct Alert { // car is started, but controls is lagging or died return {"TAKE CONTROL IMMEDIATELY", "Controls Unresponsive", "controlsUnresponsive", cereal::ControlsState::AlertSize::FULL, - AudibleAlert::CHIME_WARNING_REPEAT}; + AudibleAlert::WARNING_IMMEDIATE}; } } return {}; @@ -100,16 +76,11 @@ const QColor bg_colors [] = { }; typedef struct { - float x, y; -} vertex_data; - -typedef struct { - vertex_data v[TRAJECTORY_SIZE * 2]; + QPointF v[TRAJECTORY_SIZE * 2]; int cnt; } line_vertices_data; typedef struct UIScene { - mat3 view_from_calib; bool world_objects_visible; @@ -122,10 +93,8 @@ typedef struct UIScene { line_vertices_data lane_line_vertices[4]; line_vertices_data road_edge_vertices[2]; - bool dm_active, engageable; - // lead - vertex_data lead_vertices[2]; + QPointF lead_vertices[2]; float light_sensor, accel_sensor, gyro_sensor; bool started, ignition, is_metric, longitudinal_control, end_to_end; @@ -134,10 +103,6 @@ typedef struct UIScene { typedef struct UIState { int fb_w = 0, fb_h = 0; - NVGcontext *vg; - - // images - std::map images; std::unique_ptr sm; @@ -149,8 +114,6 @@ typedef struct UIState { QTransform car_space_transform; bool wide_camera; - - float running_time; } UIState; @@ -188,11 +151,11 @@ private: // auto brightness const float accel_samples = 5*UI_FREQ; - bool awake; + bool awake = false; int awake_timeout = 0; float accel_prev = 0; float gyro_prev = 0; - float last_brightness = 0; + int last_brightness = 0; FirstOrderFilter brightness_filter; QTimer *timer; @@ -207,3 +170,5 @@ public slots: void setAwake(bool on, bool reset); void update(const UIState &s); }; + +void ui_update_params(UIState *s); diff --git a/selfdrive/ui/watch3.cc b/selfdrive/ui/watch3.cc index e5e2864cc..73c8e1131 100644 --- a/selfdrive/ui/watch3.cc +++ b/selfdrive/ui/watch3.cc @@ -17,12 +17,20 @@ int main(int argc, char *argv[]) { QVBoxLayout *layout = new QVBoxLayout(&w); layout->setMargin(0); layout->setSpacing(0); - layout->addWidget(new CameraViewWidget(VISION_STREAM_RGB_BACK, false)); - QHBoxLayout *hlayout = new QHBoxLayout(); - layout->addLayout(hlayout); - hlayout->addWidget(new CameraViewWidget(VISION_STREAM_RGB_FRONT, false)); - hlayout->addWidget(new CameraViewWidget(VISION_STREAM_RGB_WIDE, false)); + { + QHBoxLayout *hlayout = new QHBoxLayout(); + layout->addLayout(hlayout); + hlayout->addWidget(new CameraViewWidget("navd", VISION_STREAM_RGB_MAP, false)); + hlayout->addWidget(new CameraViewWidget("camerad", VISION_STREAM_RGB_BACK, false)); + } + + { + QHBoxLayout *hlayout = new QHBoxLayout(); + layout->addLayout(hlayout); + hlayout->addWidget(new CameraViewWidget("camerad", VISION_STREAM_RGB_FRONT, false)); + hlayout->addWidget(new CameraViewWidget("camerad", VISION_STREAM_RGB_WIDE, false)); + } return a.exec(); } diff --git a/selfdrive/updated.py b/selfdrive/updated.py index 78760bea4..2acebf2a4 100755 --- a/selfdrive/updated.py +++ b/selfdrive/updated.py @@ -40,6 +40,7 @@ from common.params import Params from selfdrive.hardware import EON, TICI, HARDWARE from selfdrive.swaglog import cloudlog from selfdrive.controls.lib.alertmanager import set_offroad_alert +from selfdrive.version import get_tested_branch LOCK_FILE = os.getenv("UPDATER_LOCK_FILE", "/tmp/safe_staging_overlay.lock") STAGING_ROOT = os.getenv("UPDATER_STAGING_ROOT", "/data/safe_staging") @@ -51,6 +52,8 @@ OVERLAY_METADATA = os.path.join(STAGING_ROOT, "metadata") OVERLAY_MERGED = os.path.join(STAGING_ROOT, "merged") FINALIZED = os.path.join(STAGING_ROOT, "finalized") +DAYS_NO_CONNECTIVITY_MAX = 14 # do not allow to engage after this many days +DAYS_NO_CONNECTIVITY_PROMPT = 10 # send an offroad prompt after this many days class WaitTimeHelper: def __init__(self, proc): @@ -102,15 +105,24 @@ def set_params(new_version: bool, failed_count: int, exception: Optional[str]) - params = Params() params.put("UpdateFailedCount", str(failed_count)) + + last_update = datetime.datetime.utcnow() if failed_count == 0: - t = datetime.datetime.utcnow().isoformat() + t = last_update.isoformat() params.put("LastUpdateTime", t.encode('utf8')) + else: + try: + t = params.get("LastUpdateTime", encoding='utf8') + last_update = datetime.datetime.fromisoformat(t) + except (TypeError, ValueError): + pass if exception is None: params.delete("LastUpdateException") else: params.put("LastUpdateException", exception) + # Write out release notes for new versions if new_version: try: with open(os.path.join(FINALIZED, "RELEASES.md"), "rb") as f: @@ -123,6 +135,24 @@ def set_params(new_version: bool, failed_count: int, exception: Optional[str]) - params.put("ReleaseNotes", "") params.put_bool("UpdateAvailable", True) + # Handle user prompt + for alert in ("Offroad_UpdateFailed", "Offroad_ConnectivityNeeded", "Offroad_ConnectivityNeededPrompt"): + set_offroad_alert(alert, False) + + now = datetime.datetime.utcnow() + dt = now - last_update + if failed_count > 15 and exception is not None: + if get_tested_branch(): + extra_text = "Ensure the software is correctly installed" + else: + extra_text = exception + set_offroad_alert("Offroad_UpdateFailed", True, extra_text=extra_text) + elif dt.days > DAYS_NO_CONNECTIVITY_MAX and failed_count > 1: + set_offroad_alert("Offroad_ConnectivityNeeded", True) + elif dt.days > DAYS_NO_CONNECTIVITY_PROMPT: + remaining = max(DAYS_NO_CONNECTIVITY_MAX - dt.days, 1) + set_offroad_alert("Offroad_ConnectivityNeededPrompt", True, extra_text=f"{remaining} day{'' if remaining == 1 else 's'}.") + def setup_git_options(cwd: str) -> None: # We sync FS object atimes (which NEOS doesn't use) and mtimes, but ctimes @@ -344,16 +374,14 @@ def main(): params = Params() if params.get_bool("DisableUpdates"): - raise RuntimeError("updates are disabled by the DisableUpdates param") - - if EON and os.geteuid() != 0: - raise RuntimeError("updated must be launched as root!") + cloudlog.warning("updates are disabled by the DisableUpdates param") + exit(0) ov_lock_fd = open(LOCK_FILE, 'w') try: fcntl.flock(ov_lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) except IOError as e: - raise RuntimeError("couldn't get overlay lock; is another updated running?") from e + raise RuntimeError("couldn't get overlay lock; is another instance running?") from e # Set low io priority proc = psutil.Process() @@ -368,10 +396,6 @@ def main(): t = datetime.datetime.utcnow().isoformat() params.put("InstallDate", t.encode('utf8')) - # Wait for IsOffroad to be set before our first update attempt - wait_helper = WaitTimeHelper(proc) - wait_helper.sleep(30) - overlay_init = Path(os.path.join(BASEDIR, ".overlay_init")) overlay_init.unlink(missing_ok=True) @@ -379,6 +403,13 @@ def main(): last_fetch_time = 0 update_failed_count = 0 + # Set initial params for offroad alerts + set_params(False, 0, None) + + # Wait for IsOffroad to be set before our first update attempt + wait_helper = WaitTimeHelper(proc) + wait_helper.sleep(30) + # Run the update loop # * every 1m, do a lightweight internet/update check # * every 10m, do a full git fetch @@ -428,7 +459,11 @@ def main(): exception = str(e) overlay_init.unlink(missing_ok=True) - set_params(new_version, update_failed_count, exception) + try: + set_params(new_version, update_failed_count, exception) + except Exception: + cloudlog.exception("uncaught updated exception while setting params, shouldn't happen") + wait_helper.sleep(60) dismount_overlay() diff --git a/selfdrive/version.py b/selfdrive/version.py index 7a2234e3e..2ddfc93c6 100644 --- a/selfdrive/version.py +++ b/selfdrive/version.py @@ -2,6 +2,7 @@ import os import subprocess from typing import List, Optional +from functools import lru_cache from common.basedir import BASEDIR from selfdrive.swaglog import cloudlog @@ -9,9 +10,16 @@ from selfdrive.swaglog import cloudlog TESTED_BRANCHES = ['devel', 'release2-staging', 'release3-staging', 'dashcam-staging', 'release2', 'release3', 'dashcam'] +training_version: bytes = b"0.2.0" +terms_version: bytes = b"2" + + +def cache(user_function, /): + return lru_cache(maxsize=None)(user_function) + def run_cmd(cmd: List[str]) -> str: - return subprocess.check_output(cmd, encoding='utf8').strip() + return subprocess.check_output(cmd, encoding='utf8').strip() def run_cmd_default(cmd: List[str], default: Optional[str] = None) -> Optional[str]: @@ -21,19 +29,23 @@ def run_cmd_default(cmd: List[str], default: Optional[str] = None) -> Optional[s return default -def get_git_commit(branch: str = "HEAD", default: Optional[str] = None) -> Optional[str]: +@cache +def get_commit(branch: str = "HEAD", default: Optional[str] = None) -> Optional[str]: return run_cmd_default(["git", "rev-parse", branch], default=default) -def get_git_branch(default: Optional[str] = None) -> Optional[str]: +@cache +def get_short_branch(default: Optional[str] = None) -> Optional[str]: return run_cmd_default(["git", "rev-parse", "--abbrev-ref", "HEAD"], default=default) -def get_git_full_branchname(default: Optional[str] = None) -> Optional[str]: +@cache +def get_branch(default: Optional[str] = None) -> Optional[str]: return run_cmd_default(["git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], default=default) -def get_git_remote(default: Optional[str] = None) -> Optional[str]: +@cache +def get_origin(default: Optional[str] = None) -> Optional[str]: try: local_branch = run_cmd(["git", "name-rev", "--name-only", "HEAD"]) tracking_remote = run_cmd(["git", "config", "branch." + local_branch + ".remote"]) @@ -42,55 +54,68 @@ def get_git_remote(default: Optional[str] = None) -> Optional[str]: return run_cmd_default(["git", "config", "--get", "remote.origin.url"], default=default) -def get_version(): +@cache +def get_version() -> str: with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "common", "version.h")) as _versionf: version = _versionf.read().split('"')[1] return version -version = get_version() -prebuilt = os.path.exists(os.path.join(BASEDIR, 'prebuilt')) -training_version: bytes = b"0.2.0" -terms_version: bytes = b"2" +@cache +def get_prebuilt() -> bool: + return os.path.exists(os.path.join(BASEDIR, 'prebuilt')) -dirty: bool = True -comma_remote: bool = False -tested_branch: bool = False -origin = get_git_remote() -branch = get_git_full_branchname() -commit = get_git_commit() -if (origin is not None) and (branch is not None): +@cache +def get_comma_remote() -> bool: + origin = get_origin() + if origin is None: + return False + + return origin.startswith('git@github.com:commaai') or origin.startswith('https://github.com/commaai') + + +@cache +def get_tested_branch() -> bool: + return get_short_branch() in TESTED_BRANCHES + + +@cache +def get_dirty() -> bool: + origin = get_origin() + branch = get_branch() + if (origin is None) or (branch is None): + return True + + dirty = False try: - comma_remote = origin.startswith('git@github.com:commaai') or origin.startswith('https://github.com/commaai') - tested_branch = get_git_branch() in TESTED_BRANCHES - - dirty = False - # Actually check dirty files - if not prebuilt: + if not get_prebuilt(): # This is needed otherwise touched files might show up as modified try: subprocess.check_call(["git", "update-index", "--refresh"]) except subprocess.CalledProcessError: pass + dirty = (subprocess.call(["git", "diff-index", "--quiet", branch, "--"]) != 0) # Log dirty files - if dirty and comma_remote: + if dirty and get_comma_remote(): try: dirty_files = run_cmd(["git", "diff-index", branch, "--"]) - cloudlog.event("dirty comma branch", version=version, dirty=dirty, origin=origin, branch=branch, - dirty_files=dirty_files, commit=commit, origin_commit=get_git_commit(branch)) + cloudlog.event("dirty comma branch", version=get_version(), dirty=dirty, origin=origin, branch=branch, + dirty_files=dirty_files, commit=get_commit(), origin_commit=get_commit(branch)) except subprocess.CalledProcessError: pass - dirty = dirty or (not comma_remote) + dirty = dirty or (not get_comma_remote()) dirty = dirty or ('master' in branch) except subprocess.CalledProcessError: - dirty = True cloudlog.exception("git subprocess failed while checking dirty") + dirty = True + + return dirty if __name__ == "__main__": @@ -100,8 +125,9 @@ if __name__ == "__main__": params.put("TermsVersion", terms_version) params.put("TrainingVersion", training_version) - print("Dirty: %s" % dirty) - print("Version: %s" % version) - print("Remote: %s" % origin) - print("Branch: %s" % branch) - print("Prebuilt: %s" % prebuilt) + print("Dirty: %s" % get_dirty()) + print("Version: %s" % get_version()) + print("Origin: %s" % get_origin()) + print("Branch: %s" % get_branch()) + print("Short branch: %s" % get_short_branch()) + print("Prebuilt: %s" % get_prebuilt()) diff --git a/third_party/nanovg/fontstash.h b/third_party/nanovg/fontstash.h deleted file mode 100644 index 35dfb0f65..000000000 --- a/third_party/nanovg/fontstash.h +++ /dev/null @@ -1,1718 +0,0 @@ -// -// Copyright (c) 2009-2013 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#ifndef FONS_H -#define FONS_H - -#define FONS_INVALID -1 - -enum FONSflags { - FONS_ZERO_TOPLEFT = 1, - FONS_ZERO_BOTTOMLEFT = 2, -}; - -enum FONSalign { - // Horizontal align - FONS_ALIGN_LEFT = 1<<0, // Default - FONS_ALIGN_CENTER = 1<<1, - FONS_ALIGN_RIGHT = 1<<2, - // Vertical align - FONS_ALIGN_TOP = 1<<3, - FONS_ALIGN_MIDDLE = 1<<4, - FONS_ALIGN_BOTTOM = 1<<5, - FONS_ALIGN_BASELINE = 1<<6, // Default -}; - -enum FONSerrorCode { - // Font atlas is full. - FONS_ATLAS_FULL = 1, - // Scratch memory used to render glyphs is full, requested size reported in 'val', you may need to bump up FONS_SCRATCH_BUF_SIZE. - FONS_SCRATCH_FULL = 2, - // Calls to fonsPushState has created too large stack, if you need deep state stack bump up FONS_MAX_STATES. - FONS_STATES_OVERFLOW = 3, - // Trying to pop too many states fonsPopState(). - FONS_STATES_UNDERFLOW = 4, -}; - -struct FONSparams { - int width, height; - unsigned char flags; - void* userPtr; - int (*renderCreate)(void* uptr, int width, int height); - int (*renderResize)(void* uptr, int width, int height); - void (*renderUpdate)(void* uptr, int* rect, const unsigned char* data); - void (*renderDraw)(void* uptr, const float* verts, const float* tcoords, const unsigned int* colors, int nverts); - void (*renderDelete)(void* uptr); -}; -typedef struct FONSparams FONSparams; - -struct FONSquad -{ - float x0,y0,s0,t0; - float x1,y1,s1,t1; -}; -typedef struct FONSquad FONSquad; - -struct FONStextIter { - float x, y, nextx, nexty, scale, spacing; - unsigned int codepoint; - short isize, iblur; - struct FONSfont* font; - int prevGlyphIndex; - const char* str; - const char* next; - const char* end; - unsigned int utf8state; -}; -typedef struct FONStextIter FONStextIter; - -typedef struct FONScontext FONScontext; - -// Constructor and destructor. -FONScontext* fonsCreateInternal(FONSparams* params); -void fonsDeleteInternal(FONScontext* s); - -void fonsSetErrorCallback(FONScontext* s, void (*callback)(void* uptr, int error, int val), void* uptr); -// Returns current atlas size. -void fonsGetAtlasSize(FONScontext* s, int* width, int* height); -// Expands the atlas size. -int fonsExpandAtlas(FONScontext* s, int width, int height); -// Resets the whole stash. -int fonsResetAtlas(FONScontext* stash, int width, int height); - -// Add fonts -int fonsAddFont(FONScontext* s, const char* name, const char* path); -int fonsAddFontMem(FONScontext* s, const char* name, unsigned char* data, int ndata, int freeData); -int fonsGetFontByName(FONScontext* s, const char* name); - -// State handling -void fonsPushState(FONScontext* s); -void fonsPopState(FONScontext* s); -void fonsClearState(FONScontext* s); - -// State setting -void fonsSetSize(FONScontext* s, float size); -void fonsSetColor(FONScontext* s, unsigned int color); -void fonsSetSpacing(FONScontext* s, float spacing); -void fonsSetBlur(FONScontext* s, float blur); -void fonsSetAlign(FONScontext* s, int align); -void fonsSetFont(FONScontext* s, int font); - -// Draw text -float fonsDrawText(FONScontext* s, float x, float y, const char* string, const char* end); - -// Measure text -float fonsTextBounds(FONScontext* s, float x, float y, const char* string, const char* end, float* bounds); -void fonsLineBounds(FONScontext* s, float y, float* miny, float* maxy); -void fonsVertMetrics(FONScontext* s, float* ascender, float* descender, float* lineh); - -// Text iterator -int fonsTextIterInit(FONScontext* stash, FONStextIter* iter, float x, float y, const char* str, const char* end); -int fonsTextIterNext(FONScontext* stash, FONStextIter* iter, struct FONSquad* quad); - -// Pull texture changes -const unsigned char* fonsGetTextureData(FONScontext* stash, int* width, int* height); -int fonsValidateTexture(FONScontext* s, int* dirty); - -// Draws the stash texture for debugging -void fonsDrawDebug(FONScontext* s, float x, float y); - -#endif // FONTSTASH_H - - -#ifdef FONTSTASH_IMPLEMENTATION - -#define FONS_NOTUSED(v) (void)sizeof(v) - -#ifdef FONS_USE_FREETYPE - -#include -#include FT_FREETYPE_H -#include FT_ADVANCES_H -#include - -struct FONSttFontImpl { - FT_Face font; -}; -typedef struct FONSttFontImpl FONSttFontImpl; - -static FT_Library ftLibrary; - -int fons__tt_init(FONScontext *context) -{ - FT_Error ftError; - FONS_NOTUSED(context); - ftError = FT_Init_FreeType(&ftLibrary); - return ftError == 0; -} - -int fons__tt_loadFont(FONScontext *context, FONSttFontImpl *font, unsigned char *data, int dataSize) -{ - FT_Error ftError; - FONS_NOTUSED(context); - - //font->font.userdata = stash; - ftError = FT_New_Memory_Face(ftLibrary, (const FT_Byte*)data, dataSize, 0, &font->font); - return ftError == 0; -} - -void fons__tt_getFontVMetrics(FONSttFontImpl *font, int *ascent, int *descent, int *lineGap) -{ - *ascent = font->font->ascender; - *descent = font->font->descender; - *lineGap = font->font->height - (*ascent - *descent); -} - -float fons__tt_getPixelHeightScale(FONSttFontImpl *font, float size) -{ - return size / (font->font->ascender - font->font->descender); -} - -int fons__tt_getGlyphIndex(FONSttFontImpl *font, int codepoint) -{ - return FT_Get_Char_Index(font->font, codepoint); -} - -int fons__tt_buildGlyphBitmap(FONSttFontImpl *font, int glyph, float size, float scale, - int *advance, int *lsb, int *x0, int *y0, int *x1, int *y1) -{ - FT_Error ftError; - FT_GlyphSlot ftGlyph; - FT_Fixed advFixed; - FONS_NOTUSED(scale); - - ftError = FT_Set_Pixel_Sizes(font->font, 0, (FT_UInt)(size * (float)font->font->units_per_EM / (float)(font->font->ascender - font->font->descender))); - if (ftError) return 0; - ftError = FT_Load_Glyph(font->font, glyph, FT_LOAD_RENDER); - if (ftError) return 0; - ftError = FT_Get_Advance(font->font, glyph, FT_LOAD_NO_SCALE, &advFixed); - if (ftError) return 0; - ftGlyph = font->font->glyph; - *advance = (int)advFixed; - *lsb = (int)ftGlyph->metrics.horiBearingX; - *x0 = ftGlyph->bitmap_left; - *x1 = *x0 + ftGlyph->bitmap.width; - *y0 = -ftGlyph->bitmap_top; - *y1 = *y0 + ftGlyph->bitmap.rows; - return 1; -} - -void fons__tt_renderGlyphBitmap(FONSttFontImpl *font, unsigned char *output, int outWidth, int outHeight, int outStride, - float scaleX, float scaleY, int glyph) -{ - FT_GlyphSlot ftGlyph = font->font->glyph; - int ftGlyphOffset = 0; - int x, y; - FONS_NOTUSED(outWidth); - FONS_NOTUSED(outHeight); - FONS_NOTUSED(scaleX); - FONS_NOTUSED(scaleY); - FONS_NOTUSED(glyph); // glyph has already been loaded by fons__tt_buildGlyphBitmap - - for ( y = 0; y < ftGlyph->bitmap.rows; y++ ) { - for ( x = 0; x < ftGlyph->bitmap.width; x++ ) { - output[(y * outStride) + x] = ftGlyph->bitmap.buffer[ftGlyphOffset++]; - } - } -} - -int fons__tt_getGlyphKernAdvance(FONSttFontImpl *font, int glyph1, int glyph2) -{ - FT_Vector ftKerning; - FT_Get_Kerning(font->font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning); - return (int)((ftKerning.x + 32) >> 6); // Round up and convert to integer -} - -#else - -#define STB_TRUETYPE_IMPLEMENTATION -static void* fons__tmpalloc(size_t size, void* up); -static void fons__tmpfree(void* ptr, void* up); -#define STBTT_malloc(x,u) fons__tmpalloc(x,u) -#define STBTT_free(x,u) fons__tmpfree(x,u) -#include "stb_truetype.h" - -struct FONSttFontImpl { - stbtt_fontinfo font; -}; -typedef struct FONSttFontImpl FONSttFontImpl; - -int fons__tt_init(FONScontext *context) -{ - FONS_NOTUSED(context); - return 1; -} - -int fons__tt_loadFont(FONScontext *context, FONSttFontImpl *font, unsigned char *data, int dataSize) -{ - int stbError; - FONS_NOTUSED(dataSize); - - font->font.userdata = context; - stbError = stbtt_InitFont(&font->font, data, 0); - return stbError; -} - -void fons__tt_getFontVMetrics(FONSttFontImpl *font, int *ascent, int *descent, int *lineGap) -{ - stbtt_GetFontVMetrics(&font->font, ascent, descent, lineGap); -} - -float fons__tt_getPixelHeightScale(FONSttFontImpl *font, float size) -{ - return stbtt_ScaleForPixelHeight(&font->font, size); -} - -int fons__tt_getGlyphIndex(FONSttFontImpl *font, int codepoint) -{ - return stbtt_FindGlyphIndex(&font->font, codepoint); -} - -int fons__tt_buildGlyphBitmap(FONSttFontImpl *font, int glyph, float size, float scale, - int *advance, int *lsb, int *x0, int *y0, int *x1, int *y1) -{ - FONS_NOTUSED(size); - stbtt_GetGlyphHMetrics(&font->font, glyph, advance, lsb); - stbtt_GetGlyphBitmapBox(&font->font, glyph, scale, scale, x0, y0, x1, y1); - return 1; -} - -void fons__tt_renderGlyphBitmap(FONSttFontImpl *font, unsigned char *output, int outWidth, int outHeight, int outStride, - float scaleX, float scaleY, int glyph) -{ - stbtt_MakeGlyphBitmap(&font->font, output, outWidth, outHeight, outStride, scaleX, scaleY, glyph); -} - -int fons__tt_getGlyphKernAdvance(FONSttFontImpl *font, int glyph1, int glyph2) -{ - return stbtt_GetGlyphKernAdvance(&font->font, glyph1, glyph2); -} - -#endif - -#ifndef FONS_SCRATCH_BUF_SIZE -# define FONS_SCRATCH_BUF_SIZE 64000 -#endif -#ifndef FONS_HASH_LUT_SIZE -# define FONS_HASH_LUT_SIZE 256 -#endif -#ifndef FONS_INIT_FONTS -# define FONS_INIT_FONTS 4 -#endif -#ifndef FONS_INIT_GLYPHS -# define FONS_INIT_GLYPHS 256 -#endif -#ifndef FONS_INIT_ATLAS_NODES -# define FONS_INIT_ATLAS_NODES 256 -#endif -#ifndef FONS_VERTEX_COUNT -# define FONS_VERTEX_COUNT 1024 -#endif -#ifndef FONS_MAX_STATES -# define FONS_MAX_STATES 20 -#endif -#ifndef FONS_MAX_FALLBACKS -# define FONS_MAX_FALLBACKS 20 -#endif - -static unsigned int fons__hashint(unsigned int a) -{ - a += ~(a<<15); - a ^= (a>>10); - a += (a<<3); - a ^= (a>>6); - a += ~(a<<11); - a ^= (a>>16); - return a; -} - -static int fons__mini(int a, int b) -{ - return a < b ? a : b; -} - -static int fons__maxi(int a, int b) -{ - return a > b ? a : b; -} - -struct FONSglyph -{ - unsigned int codepoint; - int index; - int next; - short size, blur; - short x0,y0,x1,y1; - short xadv,xoff,yoff; -}; -typedef struct FONSglyph FONSglyph; - -struct FONSfont -{ - FONSttFontImpl font; - char name[64]; - unsigned char* data; - int dataSize; - unsigned char freeData; - float ascender; - float descender; - float lineh; - FONSglyph* glyphs; - int cglyphs; - int nglyphs; - int lut[FONS_HASH_LUT_SIZE]; - int fallbacks[FONS_MAX_FALLBACKS]; - int nfallbacks; -}; -typedef struct FONSfont FONSfont; - -struct FONSstate -{ - int font; - int align; - float size; - unsigned int color; - float blur; - float spacing; -}; -typedef struct FONSstate FONSstate; - -struct FONSatlasNode { - short x, y, width; -}; -typedef struct FONSatlasNode FONSatlasNode; - -struct FONSatlas -{ - int width, height; - FONSatlasNode* nodes; - int nnodes; - int cnodes; -}; -typedef struct FONSatlas FONSatlas; - -struct FONScontext -{ - FONSparams params; - float itw,ith; - unsigned char* texData; - int dirtyRect[4]; - FONSfont** fonts; - FONSatlas* atlas; - int cfonts; - int nfonts; - float verts[FONS_VERTEX_COUNT*2]; - float tcoords[FONS_VERTEX_COUNT*2]; - unsigned int colors[FONS_VERTEX_COUNT]; - int nverts; - unsigned char* scratch; - int nscratch; - FONSstate states[FONS_MAX_STATES]; - int nstates; - void (*handleError)(void* uptr, int error, int val); - void* errorUptr; -}; - -#ifdef STB_TRUETYPE_IMPLEMENTATION - -static void* fons__tmpalloc(size_t size, void* up) -{ - unsigned char* ptr; - FONScontext* stash = (FONScontext*)up; - - // 16-byte align the returned pointer - size = (size + 0xf) & ~0xf; - - if (stash->nscratch+(int)size > FONS_SCRATCH_BUF_SIZE) { - if (stash->handleError) - stash->handleError(stash->errorUptr, FONS_SCRATCH_FULL, stash->nscratch+(int)size); - return NULL; - } - ptr = stash->scratch + stash->nscratch; - stash->nscratch += (int)size; - return ptr; -} - -static void fons__tmpfree(void* ptr, void* up) -{ - (void)ptr; - (void)up; - // empty -} - -#endif // STB_TRUETYPE_IMPLEMENTATION - -// Copyright (c) 2008-2010 Bjoern Hoehrmann -// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. - -#define FONS_UTF8_ACCEPT 0 -#define FONS_UTF8_REJECT 12 - -static unsigned int fons__decutf8(unsigned int* state, unsigned int* codep, unsigned int byte) -{ - static const unsigned char utf8d[] = { - // The first part of the table maps bytes to character classes that - // to reduce the size of the transition table and create bitmasks. - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, - 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, - 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, - - // The second part is a transition table that maps a combination - // of a state of the automaton and a character class to a state. - 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, - 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, - 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, - 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, - 12,36,12,12,12,12,12,12,12,12,12,12, - }; - - unsigned int type = utf8d[byte]; - - *codep = (*state != FONS_UTF8_ACCEPT) ? - (byte & 0x3fu) | (*codep << 6) : - (0xff >> type) & (byte); - - *state = utf8d[256 + *state + type]; - return *state; -} - -// Atlas based on Skyline Bin Packer by Jukka Jylänki - -static void fons__deleteAtlas(FONSatlas* atlas) -{ - if (atlas == NULL) return; - if (atlas->nodes != NULL) free(atlas->nodes); - free(atlas); -} - -static FONSatlas* fons__allocAtlas(int w, int h, int nnodes) -{ - FONSatlas* atlas = NULL; - - // Allocate memory for the font stash. - atlas = (FONSatlas*)malloc(sizeof(FONSatlas)); - if (atlas == NULL) goto error; - memset(atlas, 0, sizeof(FONSatlas)); - - atlas->width = w; - atlas->height = h; - - // Allocate space for skyline nodes - atlas->nodes = (FONSatlasNode*)malloc(sizeof(FONSatlasNode) * nnodes); - if (atlas->nodes == NULL) goto error; - memset(atlas->nodes, 0, sizeof(FONSatlasNode) * nnodes); - atlas->nnodes = 0; - atlas->cnodes = nnodes; - - // Init root node. - atlas->nodes[0].x = 0; - atlas->nodes[0].y = 0; - atlas->nodes[0].width = (short)w; - atlas->nnodes++; - - return atlas; - -error: - if (atlas) fons__deleteAtlas(atlas); - return NULL; -} - -static int fons__atlasInsertNode(FONSatlas* atlas, int idx, int x, int y, int w) -{ - int i; - // Insert node - if (atlas->nnodes+1 > atlas->cnodes) { - atlas->cnodes = atlas->cnodes == 0 ? 8 : atlas->cnodes * 2; - atlas->nodes = (FONSatlasNode*)realloc(atlas->nodes, sizeof(FONSatlasNode) * atlas->cnodes); - if (atlas->nodes == NULL) - return 0; - } - for (i = atlas->nnodes; i > idx; i--) - atlas->nodes[i] = atlas->nodes[i-1]; - atlas->nodes[idx].x = (short)x; - atlas->nodes[idx].y = (short)y; - atlas->nodes[idx].width = (short)w; - atlas->nnodes++; - - return 1; -} - -static void fons__atlasRemoveNode(FONSatlas* atlas, int idx) -{ - int i; - if (atlas->nnodes == 0) return; - for (i = idx; i < atlas->nnodes-1; i++) - atlas->nodes[i] = atlas->nodes[i+1]; - atlas->nnodes--; -} - -static void fons__atlasExpand(FONSatlas* atlas, int w, int h) -{ - // Insert node for empty space - if (w > atlas->width) - fons__atlasInsertNode(atlas, atlas->nnodes, atlas->width, 0, w - atlas->width); - atlas->width = w; - atlas->height = h; -} - -static void fons__atlasReset(FONSatlas* atlas, int w, int h) -{ - atlas->width = w; - atlas->height = h; - atlas->nnodes = 0; - - // Init root node. - atlas->nodes[0].x = 0; - atlas->nodes[0].y = 0; - atlas->nodes[0].width = (short)w; - atlas->nnodes++; -} - -static int fons__atlasAddSkylineLevel(FONSatlas* atlas, int idx, int x, int y, int w, int h) -{ - int i; - - // Insert new node - if (fons__atlasInsertNode(atlas, idx, x, y+h, w) == 0) - return 0; - - // Delete skyline segments that fall under the shadow of the new segment. - for (i = idx+1; i < atlas->nnodes; i++) { - if (atlas->nodes[i].x < atlas->nodes[i-1].x + atlas->nodes[i-1].width) { - int shrink = atlas->nodes[i-1].x + atlas->nodes[i-1].width - atlas->nodes[i].x; - atlas->nodes[i].x += (short)shrink; - atlas->nodes[i].width -= (short)shrink; - if (atlas->nodes[i].width <= 0) { - fons__atlasRemoveNode(atlas, i); - i--; - } else { - break; - } - } else { - break; - } - } - - // Merge same height skyline segments that are next to each other. - for (i = 0; i < atlas->nnodes-1; i++) { - if (atlas->nodes[i].y == atlas->nodes[i+1].y) { - atlas->nodes[i].width += atlas->nodes[i+1].width; - fons__atlasRemoveNode(atlas, i+1); - i--; - } - } - - return 1; -} - -static int fons__atlasRectFits(FONSatlas* atlas, int i, int w, int h) -{ - // Checks if there is enough space at the location of skyline span 'i', - // and return the max height of all skyline spans under that at that location, - // (think tetris block being dropped at that position). Or -1 if no space found. - int x = atlas->nodes[i].x; - int y = atlas->nodes[i].y; - int spaceLeft; - if (x + w > atlas->width) - return -1; - spaceLeft = w; - while (spaceLeft > 0) { - if (i == atlas->nnodes) return -1; - y = fons__maxi(y, atlas->nodes[i].y); - if (y + h > atlas->height) return -1; - spaceLeft -= atlas->nodes[i].width; - ++i; - } - return y; -} - -static int fons__atlasAddRect(FONSatlas* atlas, int rw, int rh, int* rx, int* ry) -{ - int besth = atlas->height, bestw = atlas->width, besti = -1; - int bestx = -1, besty = -1, i; - - // Bottom left fit heuristic. - for (i = 0; i < atlas->nnodes; i++) { - int y = fons__atlasRectFits(atlas, i, rw, rh); - if (y != -1) { - if (y + rh < besth || (y + rh == besth && atlas->nodes[i].width < bestw)) { - besti = i; - bestw = atlas->nodes[i].width; - besth = y + rh; - bestx = atlas->nodes[i].x; - besty = y; - } - } - } - - if (besti == -1) - return 0; - - // Perform the actual packing. - if (fons__atlasAddSkylineLevel(atlas, besti, bestx, besty, rw, rh) == 0) - return 0; - - *rx = bestx; - *ry = besty; - - return 1; -} - -static void fons__addWhiteRect(FONScontext* stash, int w, int h) -{ - int x, y, gx, gy; - unsigned char* dst; - if (fons__atlasAddRect(stash->atlas, w, h, &gx, &gy) == 0) - return; - - // Rasterize - dst = &stash->texData[gx + gy * stash->params.width]; - for (y = 0; y < h; y++) { - for (x = 0; x < w; x++) - dst[x] = 0xff; - dst += stash->params.width; - } - - stash->dirtyRect[0] = fons__mini(stash->dirtyRect[0], gx); - stash->dirtyRect[1] = fons__mini(stash->dirtyRect[1], gy); - stash->dirtyRect[2] = fons__maxi(stash->dirtyRect[2], gx+w); - stash->dirtyRect[3] = fons__maxi(stash->dirtyRect[3], gy+h); -} - -FONScontext* fonsCreateInternal(FONSparams* params) -{ - FONScontext* stash = NULL; - - // Allocate memory for the font stash. - stash = (FONScontext*)malloc(sizeof(FONScontext)); - if (stash == NULL) goto error; - memset(stash, 0, sizeof(FONScontext)); - - stash->params = *params; - - // Allocate scratch buffer. - stash->scratch = (unsigned char*)malloc(FONS_SCRATCH_BUF_SIZE); - if (stash->scratch == NULL) goto error; - - // Initialize implementation library - if (!fons__tt_init(stash)) goto error; - - if (stash->params.renderCreate != NULL) { - if (stash->params.renderCreate(stash->params.userPtr, stash->params.width, stash->params.height) == 0) - goto error; - } - - stash->atlas = fons__allocAtlas(stash->params.width, stash->params.height, FONS_INIT_ATLAS_NODES); - if (stash->atlas == NULL) goto error; - - // Allocate space for fonts. - stash->fonts = (FONSfont**)malloc(sizeof(FONSfont*) * FONS_INIT_FONTS); - if (stash->fonts == NULL) goto error; - memset(stash->fonts, 0, sizeof(FONSfont*) * FONS_INIT_FONTS); - stash->cfonts = FONS_INIT_FONTS; - stash->nfonts = 0; - - // Create texture for the cache. - stash->itw = 1.0f/stash->params.width; - stash->ith = 1.0f/stash->params.height; - stash->texData = (unsigned char*)malloc(stash->params.width * stash->params.height); - if (stash->texData == NULL) goto error; - memset(stash->texData, 0, stash->params.width * stash->params.height); - - stash->dirtyRect[0] = stash->params.width; - stash->dirtyRect[1] = stash->params.height; - stash->dirtyRect[2] = 0; - stash->dirtyRect[3] = 0; - - // Add white rect at 0,0 for debug drawing. - fons__addWhiteRect(stash, 2,2); - - fonsPushState(stash); - fonsClearState(stash); - - return stash; - -error: - fonsDeleteInternal(stash); - return NULL; -} - -static FONSstate* fons__getState(FONScontext* stash) -{ - return &stash->states[stash->nstates-1]; -} - -int fonsAddFallbackFont(FONScontext* stash, int base, int fallback) -{ - FONSfont* baseFont = stash->fonts[base]; - if (baseFont->nfallbacks < FONS_MAX_FALLBACKS) { - baseFont->fallbacks[baseFont->nfallbacks++] = fallback; - return 1; - } - return 0; -} - -void fonsSetSize(FONScontext* stash, float size) -{ - fons__getState(stash)->size = size; -} - -void fonsSetColor(FONScontext* stash, unsigned int color) -{ - fons__getState(stash)->color = color; -} - -void fonsSetSpacing(FONScontext* stash, float spacing) -{ - fons__getState(stash)->spacing = spacing; -} - -void fonsSetBlur(FONScontext* stash, float blur) -{ - fons__getState(stash)->blur = blur; -} - -void fonsSetAlign(FONScontext* stash, int align) -{ - fons__getState(stash)->align = align; -} - -void fonsSetFont(FONScontext* stash, int font) -{ - fons__getState(stash)->font = font; -} - -void fonsPushState(FONScontext* stash) -{ - if (stash->nstates >= FONS_MAX_STATES) { - if (stash->handleError) - stash->handleError(stash->errorUptr, FONS_STATES_OVERFLOW, 0); - return; - } - if (stash->nstates > 0) - memcpy(&stash->states[stash->nstates], &stash->states[stash->nstates-1], sizeof(FONSstate)); - stash->nstates++; -} - -void fonsPopState(FONScontext* stash) -{ - if (stash->nstates <= 1) { - if (stash->handleError) - stash->handleError(stash->errorUptr, FONS_STATES_UNDERFLOW, 0); - return; - } - stash->nstates--; -} - -void fonsClearState(FONScontext* stash) -{ - FONSstate* state = fons__getState(stash); - state->size = 12.0f; - state->color = 0xffffffff; - state->font = 0; - state->blur = 0; - state->spacing = 0; - state->align = FONS_ALIGN_LEFT | FONS_ALIGN_BASELINE; -} - -static void fons__freeFont(FONSfont* font) -{ - if (font == NULL) return; - if (font->glyphs) free(font->glyphs); - if (font->freeData && font->data) free(font->data); - free(font); -} - -static int fons__allocFont(FONScontext* stash) -{ - FONSfont* font = NULL; - if (stash->nfonts+1 > stash->cfonts) { - stash->cfonts = stash->cfonts == 0 ? 8 : stash->cfonts * 2; - stash->fonts = (FONSfont**)realloc(stash->fonts, sizeof(FONSfont*) * stash->cfonts); - if (stash->fonts == NULL) - return -1; - } - font = (FONSfont*)malloc(sizeof(FONSfont)); - if (font == NULL) goto error; - memset(font, 0, sizeof(FONSfont)); - - font->glyphs = (FONSglyph*)malloc(sizeof(FONSglyph) * FONS_INIT_GLYPHS); - if (font->glyphs == NULL) goto error; - font->cglyphs = FONS_INIT_GLYPHS; - font->nglyphs = 0; - - stash->fonts[stash->nfonts++] = font; - return stash->nfonts-1; - -error: - fons__freeFont(font); - - return FONS_INVALID; -} - -int fonsAddFont(FONScontext* stash, const char* name, const char* path) -{ - FILE* fp = 0; - int dataSize = 0; - unsigned char* data = NULL; - - // Read in the font data. - fp = fopen(path, "rb"); - if (fp == NULL) goto error; - fseek(fp,0,SEEK_END); - dataSize = (int)ftell(fp); - fseek(fp,0,SEEK_SET); - data = (unsigned char*)malloc(dataSize); - if (data == NULL) goto error; - fread(data, 1, dataSize, fp); - fclose(fp); - fp = 0; - - return fonsAddFontMem(stash, name, data, dataSize, 1); - -error: - if (data) free(data); - if (fp) fclose(fp); - return FONS_INVALID; -} - -int fonsAddFontMem(FONScontext* stash, const char* name, unsigned char* data, int dataSize, int freeData) -{ - int i, ascent, descent, fh, lineGap; - FONSfont* font; - - int idx = fons__allocFont(stash); - if (idx == FONS_INVALID) - return FONS_INVALID; - - font = stash->fonts[idx]; - - strncpy(font->name, name, sizeof(font->name)); - font->name[sizeof(font->name)-1] = '\0'; - - // Init hash lookup. - for (i = 0; i < FONS_HASH_LUT_SIZE; ++i) - font->lut[i] = -1; - - // Read in the font data. - font->dataSize = dataSize; - font->data = data; - font->freeData = (unsigned char)freeData; - - // Init font - stash->nscratch = 0; - if (!fons__tt_loadFont(stash, &font->font, data, dataSize)) goto error; - - // Store normalized line height. The real line height is got - // by multiplying the lineh by font size. - fons__tt_getFontVMetrics( &font->font, &ascent, &descent, &lineGap); - fh = ascent - descent; - font->ascender = (float)ascent / (float)fh; - font->descender = (float)descent / (float)fh; - font->lineh = (float)(fh + lineGap) / (float)fh; - - return idx; - -error: - fons__freeFont(font); - stash->nfonts--; - return FONS_INVALID; -} - -int fonsGetFontByName(FONScontext* s, const char* name) -{ - int i; - for (i = 0; i < s->nfonts; i++) { - if (strcmp(s->fonts[i]->name, name) == 0) - return i; - } - return FONS_INVALID; -} - - -static FONSglyph* fons__allocGlyph(FONSfont* font) -{ - if (font->nglyphs+1 > font->cglyphs) { - font->cglyphs = font->cglyphs == 0 ? 8 : font->cglyphs * 2; - font->glyphs = (FONSglyph*)realloc(font->glyphs, sizeof(FONSglyph) * font->cglyphs); - if (font->glyphs == NULL) return NULL; - } - font->nglyphs++; - return &font->glyphs[font->nglyphs-1]; -} - - -// Based on Exponential blur, Jani Huhtanen, 2006 - -#define APREC 16 -#define ZPREC 7 - -static void fons__blurCols(unsigned char* dst, int w, int h, int dstStride, int alpha) -{ - int x, y; - for (y = 0; y < h; y++) { - int z = 0; // force zero border - for (x = 1; x < w; x++) { - z += (alpha * (((int)(dst[x]) << ZPREC) - z)) >> APREC; - dst[x] = (unsigned char)(z >> ZPREC); - } - dst[w-1] = 0; // force zero border - z = 0; - for (x = w-2; x >= 0; x--) { - z += (alpha * (((int)(dst[x]) << ZPREC) - z)) >> APREC; - dst[x] = (unsigned char)(z >> ZPREC); - } - dst[0] = 0; // force zero border - dst += dstStride; - } -} - -static void fons__blurRows(unsigned char* dst, int w, int h, int dstStride, int alpha) -{ - int x, y; - for (x = 0; x < w; x++) { - int z = 0; // force zero border - for (y = dstStride; y < h*dstStride; y += dstStride) { - z += (alpha * (((int)(dst[y]) << ZPREC) - z)) >> APREC; - dst[y] = (unsigned char)(z >> ZPREC); - } - dst[(h-1)*dstStride] = 0; // force zero border - z = 0; - for (y = (h-2)*dstStride; y >= 0; y -= dstStride) { - z += (alpha * (((int)(dst[y]) << ZPREC) - z)) >> APREC; - dst[y] = (unsigned char)(z >> ZPREC); - } - dst[0] = 0; // force zero border - dst++; - } -} - - -static void fons__blur(FONScontext* stash, unsigned char* dst, int w, int h, int dstStride, int blur) -{ - int alpha; - float sigma; - (void)stash; - - if (blur < 1) - return; - // Calculate the alpha such that 90% of the kernel is within the radius. (Kernel extends to infinity) - sigma = (float)blur * 0.57735f; // 1 / sqrt(3) - alpha = (int)((1< 20) iblur = 20; - pad = iblur+2; - - // Reset allocator. - stash->nscratch = 0; - - // Find code point and size. - h = fons__hashint(codepoint) & (FONS_HASH_LUT_SIZE-1); - i = font->lut[h]; - while (i != -1) { - if (font->glyphs[i].codepoint == codepoint && font->glyphs[i].size == isize && font->glyphs[i].blur == iblur) - return &font->glyphs[i]; - i = font->glyphs[i].next; - } - - // Could not find glyph, create it. - scale = fons__tt_getPixelHeightScale(&font->font, size); - g = fons__tt_getGlyphIndex(&font->font, codepoint); - // Try to find the glyph in fallback fonts. - if (g == 0) { - for (i = 0; i < font->nfallbacks; ++i) { - FONSglyph* fallbackGlyph = fons__getGlyph(stash, stash->fonts[font->fallbacks[i]], codepoint, isize, iblur); - if (fallbackGlyph != NULL && fallbackGlyph->index != 0) { - return fallbackGlyph; - } - } - } - fons__tt_buildGlyphBitmap(&font->font, g, size, scale, &advance, &lsb, &x0, &y0, &x1, &y1); - gw = x1-x0 + pad*2; - gh = y1-y0 + pad*2; - - // Find free spot for the rect in the atlas - added = fons__atlasAddRect(stash->atlas, gw, gh, &gx, &gy); - if (added == 0 && stash->handleError != NULL) { - // Atlas is full, let the user to resize the atlas (or not), and try again. - stash->handleError(stash->errorUptr, FONS_ATLAS_FULL, 0); - added = fons__atlasAddRect(stash->atlas, gw, gh, &gx, &gy); - } - if (added == 0) return NULL; - - // Init glyph. - glyph = fons__allocGlyph(font); - glyph->codepoint = codepoint; - glyph->size = isize; - glyph->blur = iblur; - glyph->index = g; - glyph->x0 = (short)gx; - glyph->y0 = (short)gy; - glyph->x1 = (short)(glyph->x0+gw); - glyph->y1 = (short)(glyph->y0+gh); - glyph->xadv = (short)(scale * advance * 10.0f); - glyph->xoff = (short)(x0 - pad); - glyph->yoff = (short)(y0 - pad); - glyph->next = 0; - - // Insert char to hash lookup. - glyph->next = font->lut[h]; - font->lut[h] = font->nglyphs-1; - - // Rasterize - dst = &stash->texData[(glyph->x0+pad) + (glyph->y0+pad) * stash->params.width]; - fons__tt_renderGlyphBitmap(&font->font, dst, gw-pad*2,gh-pad*2, stash->params.width, scale,scale, g); - - // Make sure there is one pixel empty border. - dst = &stash->texData[glyph->x0 + glyph->y0 * stash->params.width]; - for (y = 0; y < gh; y++) { - dst[y*stash->params.width] = 0; - dst[gw-1 + y*stash->params.width] = 0; - } - for (x = 0; x < gw; x++) { - dst[x] = 0; - dst[x + (gh-1)*stash->params.width] = 0; - } - - // Debug code to color the glyph background -/* unsigned char* fdst = &stash->texData[glyph->x0 + glyph->y0 * stash->params.width]; - for (y = 0; y < gh; y++) { - for (x = 0; x < gw; x++) { - int a = (int)fdst[x+y*stash->params.width] + 20; - if (a > 255) a = 255; - fdst[x+y*stash->params.width] = a; - } - }*/ - - // Blur - if (iblur > 0) { - stash->nscratch = 0; - bdst = &stash->texData[glyph->x0 + glyph->y0 * stash->params.width]; - fons__blur(stash, bdst, gw,gh, stash->params.width, iblur); - } - - stash->dirtyRect[0] = fons__mini(stash->dirtyRect[0], glyph->x0); - stash->dirtyRect[1] = fons__mini(stash->dirtyRect[1], glyph->y0); - stash->dirtyRect[2] = fons__maxi(stash->dirtyRect[2], glyph->x1); - stash->dirtyRect[3] = fons__maxi(stash->dirtyRect[3], glyph->y1); - - return glyph; -} - -static void fons__getQuad(FONScontext* stash, FONSfont* font, - int prevGlyphIndex, FONSglyph* glyph, - float scale, float spacing, float* x, float* y, FONSquad* q) -{ - float rx,ry,xoff,yoff,x0,y0,x1,y1; - - if (prevGlyphIndex != -1) { - float adv = fons__tt_getGlyphKernAdvance(&font->font, prevGlyphIndex, glyph->index) * scale; - *x += (int)(adv + spacing + 0.5f); - } - - // Each glyph has 2px border to allow good interpolation, - // one pixel to prevent leaking, and one to allow good interpolation for rendering. - // Inset the texture region by one pixel for correct interpolation. - xoff = (short)(glyph->xoff+1); - yoff = (short)(glyph->yoff+1); - x0 = (float)(glyph->x0+1); - y0 = (float)(glyph->y0+1); - x1 = (float)(glyph->x1-1); - y1 = (float)(glyph->y1-1); - - if (stash->params.flags & FONS_ZERO_TOPLEFT) { - rx = (float)(int)(*x + xoff); - ry = (float)(int)(*y + yoff); - - q->x0 = rx; - q->y0 = ry; - q->x1 = rx + x1 - x0; - q->y1 = ry + y1 - y0; - - q->s0 = x0 * stash->itw; - q->t0 = y0 * stash->ith; - q->s1 = x1 * stash->itw; - q->t1 = y1 * stash->ith; - } else { - rx = (float)(int)(*x + xoff); - ry = (float)(int)(*y - yoff); - - q->x0 = rx; - q->y0 = ry; - q->x1 = rx + x1 - x0; - q->y1 = ry - y1 + y0; - - q->s0 = x0 * stash->itw; - q->t0 = y0 * stash->ith; - q->s1 = x1 * stash->itw; - q->t1 = y1 * stash->ith; - } - - *x += (int)(glyph->xadv / 10.0f + 0.5f); -} - -static void fons__flush(FONScontext* stash) -{ - // Flush texture - if (stash->dirtyRect[0] < stash->dirtyRect[2] && stash->dirtyRect[1] < stash->dirtyRect[3]) { - if (stash->params.renderUpdate != NULL) - stash->params.renderUpdate(stash->params.userPtr, stash->dirtyRect, stash->texData); - // Reset dirty rect - stash->dirtyRect[0] = stash->params.width; - stash->dirtyRect[1] = stash->params.height; - stash->dirtyRect[2] = 0; - stash->dirtyRect[3] = 0; - } - - // Flush triangles - if (stash->nverts > 0) { - if (stash->params.renderDraw != NULL) - stash->params.renderDraw(stash->params.userPtr, stash->verts, stash->tcoords, stash->colors, stash->nverts); - stash->nverts = 0; - } -} - -static __inline void fons__vertex(FONScontext* stash, float x, float y, float s, float t, unsigned int c) -{ - stash->verts[stash->nverts*2+0] = x; - stash->verts[stash->nverts*2+1] = y; - stash->tcoords[stash->nverts*2+0] = s; - stash->tcoords[stash->nverts*2+1] = t; - stash->colors[stash->nverts] = c; - stash->nverts++; -} - -static float fons__getVertAlign(FONScontext* stash, FONSfont* font, int align, short isize) -{ - if (stash->params.flags & FONS_ZERO_TOPLEFT) { - if (align & FONS_ALIGN_TOP) { - return font->ascender * (float)isize/10.0f; - } else if (align & FONS_ALIGN_MIDDLE) { - return (font->ascender + font->descender) / 2.0f * (float)isize/10.0f; - } else if (align & FONS_ALIGN_BASELINE) { - return 0.0f; - } else if (align & FONS_ALIGN_BOTTOM) { - return font->descender * (float)isize/10.0f; - } - } else { - if (align & FONS_ALIGN_TOP) { - return -font->ascender * (float)isize/10.0f; - } else if (align & FONS_ALIGN_MIDDLE) { - return -(font->ascender + font->descender) / 2.0f * (float)isize/10.0f; - } else if (align & FONS_ALIGN_BASELINE) { - return 0.0f; - } else if (align & FONS_ALIGN_BOTTOM) { - return -font->descender * (float)isize/10.0f; - } - } - return 0.0; -} - -float fonsDrawText(FONScontext* stash, - float x, float y, - const char* str, const char* end) -{ - FONSstate* state = fons__getState(stash); - unsigned int codepoint; - unsigned int utf8state = 0; - FONSglyph* glyph = NULL; - FONSquad q; - int prevGlyphIndex = -1; - short isize = (short)(state->size*10.0f); - short iblur = (short)state->blur; - float scale; - FONSfont* font; - float width; - - if (stash == NULL) return x; - if (state->font < 0 || state->font >= stash->nfonts) return x; - font = stash->fonts[state->font]; - if (font->data == NULL) return x; - - scale = fons__tt_getPixelHeightScale(&font->font, (float)isize/10.0f); - - if (end == NULL) - end = str + strlen(str); - - // Align horizontally - if (state->align & FONS_ALIGN_LEFT) { - // empty - } else if (state->align & FONS_ALIGN_RIGHT) { - width = fonsTextBounds(stash, x,y, str, end, NULL); - x -= width; - } else if (state->align & FONS_ALIGN_CENTER) { - width = fonsTextBounds(stash, x,y, str, end, NULL); - x -= width * 0.5f; - } - // Align vertically. - y += fons__getVertAlign(stash, font, state->align, isize); - - for (; str != end; ++str) { - if (fons__decutf8(&utf8state, &codepoint, *(const unsigned char*)str)) - continue; - glyph = fons__getGlyph(stash, font, codepoint, isize, iblur); - if (glyph != NULL) { - fons__getQuad(stash, font, prevGlyphIndex, glyph, scale, state->spacing, &x, &y, &q); - - if (stash->nverts+6 > FONS_VERTEX_COUNT) - fons__flush(stash); - - fons__vertex(stash, q.x0, q.y0, q.s0, q.t0, state->color); - fons__vertex(stash, q.x1, q.y1, q.s1, q.t1, state->color); - fons__vertex(stash, q.x1, q.y0, q.s1, q.t0, state->color); - - fons__vertex(stash, q.x0, q.y0, q.s0, q.t0, state->color); - fons__vertex(stash, q.x0, q.y1, q.s0, q.t1, state->color); - fons__vertex(stash, q.x1, q.y1, q.s1, q.t1, state->color); - } - prevGlyphIndex = glyph != NULL ? glyph->index : -1; - } - fons__flush(stash); - - return x; -} - -int fonsTextIterInit(FONScontext* stash, FONStextIter* iter, - float x, float y, const char* str, const char* end) -{ - FONSstate* state = fons__getState(stash); - float width; - - memset(iter, 0, sizeof(*iter)); - - if (stash == NULL) return 0; - if (state->font < 0 || state->font >= stash->nfonts) return 0; - iter->font = stash->fonts[state->font]; - if (iter->font->data == NULL) return 0; - - iter->isize = (short)(state->size*10.0f); - iter->iblur = (short)state->blur; - iter->scale = fons__tt_getPixelHeightScale(&iter->font->font, (float)iter->isize/10.0f); - - // Align horizontally - if (state->align & FONS_ALIGN_LEFT) { - // empty - } else if (state->align & FONS_ALIGN_RIGHT) { - width = fonsTextBounds(stash, x,y, str, end, NULL); - x -= width; - } else if (state->align & FONS_ALIGN_CENTER) { - width = fonsTextBounds(stash, x,y, str, end, NULL); - x -= width * 0.5f; - } - // Align vertically. - y += fons__getVertAlign(stash, iter->font, state->align, iter->isize); - - if (end == NULL) - end = str + strlen(str); - - iter->x = iter->nextx = x; - iter->y = iter->nexty = y; - iter->spacing = state->spacing; - iter->str = str; - iter->next = str; - iter->end = end; - iter->codepoint = 0; - iter->prevGlyphIndex = -1; - - return 1; -} - -int fonsTextIterNext(FONScontext* stash, FONStextIter* iter, FONSquad* quad) -{ - FONSglyph* glyph = NULL; - const char* str = iter->next; - iter->str = iter->next; - - if (str == iter->end) - return 0; - - for (; str != iter->end; str++) { - if (fons__decutf8(&iter->utf8state, &iter->codepoint, *(const unsigned char*)str)) - continue; - str++; - // Get glyph and quad - iter->x = iter->nextx; - iter->y = iter->nexty; - glyph = fons__getGlyph(stash, iter->font, iter->codepoint, iter->isize, iter->iblur); - if (glyph != NULL) - fons__getQuad(stash, iter->font, iter->prevGlyphIndex, glyph, iter->scale, iter->spacing, &iter->nextx, &iter->nexty, quad); - iter->prevGlyphIndex = glyph != NULL ? glyph->index : -1; - break; - } - iter->next = str; - - return 1; -} - -void fonsDrawDebug(FONScontext* stash, float x, float y) -{ - int i; - int w = stash->params.width; - int h = stash->params.height; - float u = w == 0 ? 0 : (1.0f / w); - float v = h == 0 ? 0 : (1.0f / h); - - if (stash->nverts+6+6 > FONS_VERTEX_COUNT) - fons__flush(stash); - - // Draw background - fons__vertex(stash, x+0, y+0, u, v, 0x0fffffff); - fons__vertex(stash, x+w, y+h, u, v, 0x0fffffff); - fons__vertex(stash, x+w, y+0, u, v, 0x0fffffff); - - fons__vertex(stash, x+0, y+0, u, v, 0x0fffffff); - fons__vertex(stash, x+0, y+h, u, v, 0x0fffffff); - fons__vertex(stash, x+w, y+h, u, v, 0x0fffffff); - - // Draw texture - fons__vertex(stash, x+0, y+0, 0, 0, 0xffffffff); - fons__vertex(stash, x+w, y+h, 1, 1, 0xffffffff); - fons__vertex(stash, x+w, y+0, 1, 0, 0xffffffff); - - fons__vertex(stash, x+0, y+0, 0, 0, 0xffffffff); - fons__vertex(stash, x+0, y+h, 0, 1, 0xffffffff); - fons__vertex(stash, x+w, y+h, 1, 1, 0xffffffff); - - // Drawbug draw atlas - for (i = 0; i < stash->atlas->nnodes; i++) { - FONSatlasNode* n = &stash->atlas->nodes[i]; - - if (stash->nverts+6 > FONS_VERTEX_COUNT) - fons__flush(stash); - - fons__vertex(stash, x+n->x+0, y+n->y+0, u, v, 0xc00000ff); - fons__vertex(stash, x+n->x+n->width, y+n->y+1, u, v, 0xc00000ff); - fons__vertex(stash, x+n->x+n->width, y+n->y+0, u, v, 0xc00000ff); - - fons__vertex(stash, x+n->x+0, y+n->y+0, u, v, 0xc00000ff); - fons__vertex(stash, x+n->x+0, y+n->y+1, u, v, 0xc00000ff); - fons__vertex(stash, x+n->x+n->width, y+n->y+1, u, v, 0xc00000ff); - } - - fons__flush(stash); -} - -float fonsTextBounds(FONScontext* stash, - float x, float y, - const char* str, const char* end, - float* bounds) -{ - FONSstate* state = fons__getState(stash); - unsigned int codepoint; - unsigned int utf8state = 0; - FONSquad q; - FONSglyph* glyph = NULL; - int prevGlyphIndex = -1; - short isize = (short)(state->size*10.0f); - short iblur = (short)state->blur; - float scale; - FONSfont* font; - float startx, advance; - float minx, miny, maxx, maxy; - - if (stash == NULL) return 0; - if (state->font < 0 || state->font >= stash->nfonts) return 0; - font = stash->fonts[state->font]; - if (font->data == NULL) return 0; - - scale = fons__tt_getPixelHeightScale(&font->font, (float)isize/10.0f); - - // Align vertically. - y += fons__getVertAlign(stash, font, state->align, isize); - - minx = maxx = x; - miny = maxy = y; - startx = x; - - if (end == NULL) - end = str + strlen(str); - - for (; str != end; ++str) { - if (fons__decutf8(&utf8state, &codepoint, *(const unsigned char*)str)) - continue; - glyph = fons__getGlyph(stash, font, codepoint, isize, iblur); - if (glyph != NULL) { - fons__getQuad(stash, font, prevGlyphIndex, glyph, scale, state->spacing, &x, &y, &q); - if (q.x0 < minx) minx = q.x0; - if (q.x1 > maxx) maxx = q.x1; - if (stash->params.flags & FONS_ZERO_TOPLEFT) { - if (q.y0 < miny) miny = q.y0; - if (q.y1 > maxy) maxy = q.y1; - } else { - if (q.y1 < miny) miny = q.y1; - if (q.y0 > maxy) maxy = q.y0; - } - } - prevGlyphIndex = glyph != NULL ? glyph->index : -1; - } - - advance = x - startx; - - // Align horizontally - if (state->align & FONS_ALIGN_LEFT) { - // empty - } else if (state->align & FONS_ALIGN_RIGHT) { - minx -= advance; - maxx -= advance; - } else if (state->align & FONS_ALIGN_CENTER) { - minx -= advance * 0.5f; - maxx -= advance * 0.5f; - } - - if (bounds) { - bounds[0] = minx; - bounds[1] = miny; - bounds[2] = maxx; - bounds[3] = maxy; - } - - return advance; -} - -void fonsVertMetrics(FONScontext* stash, - float* ascender, float* descender, float* lineh) -{ - FONSfont* font; - FONSstate* state = fons__getState(stash); - short isize; - - if (stash == NULL) return; - if (state->font < 0 || state->font >= stash->nfonts) return; - font = stash->fonts[state->font]; - isize = (short)(state->size*10.0f); - if (font->data == NULL) return; - - if (ascender) - *ascender = font->ascender*isize/10.0f; - if (descender) - *descender = font->descender*isize/10.0f; - if (lineh) - *lineh = font->lineh*isize/10.0f; -} - -void fonsLineBounds(FONScontext* stash, float y, float* miny, float* maxy) -{ - FONSfont* font; - FONSstate* state = fons__getState(stash); - short isize; - - if (stash == NULL) return; - if (state->font < 0 || state->font >= stash->nfonts) return; - font = stash->fonts[state->font]; - isize = (short)(state->size*10.0f); - if (font->data == NULL) return; - - y += fons__getVertAlign(stash, font, state->align, isize); - - if (stash->params.flags & FONS_ZERO_TOPLEFT) { - *miny = y - font->ascender * (float)isize/10.0f; - *maxy = *miny + font->lineh*isize/10.0f; - } else { - *maxy = y + font->descender * (float)isize/10.0f; - *miny = *maxy - font->lineh*isize/10.0f; - } -} - -const unsigned char* fonsGetTextureData(FONScontext* stash, int* width, int* height) -{ - if (width != NULL) - *width = stash->params.width; - if (height != NULL) - *height = stash->params.height; - return stash->texData; -} - -int fonsValidateTexture(FONScontext* stash, int* dirty) -{ - if (stash->dirtyRect[0] < stash->dirtyRect[2] && stash->dirtyRect[1] < stash->dirtyRect[3]) { - dirty[0] = stash->dirtyRect[0]; - dirty[1] = stash->dirtyRect[1]; - dirty[2] = stash->dirtyRect[2]; - dirty[3] = stash->dirtyRect[3]; - // Reset dirty rect - stash->dirtyRect[0] = stash->params.width; - stash->dirtyRect[1] = stash->params.height; - stash->dirtyRect[2] = 0; - stash->dirtyRect[3] = 0; - return 1; - } - return 0; -} - -void fonsDeleteInternal(FONScontext* stash) -{ - int i; - if (stash == NULL) return; - - if (stash->params.renderDelete) - stash->params.renderDelete(stash->params.userPtr); - - for (i = 0; i < stash->nfonts; ++i) - fons__freeFont(stash->fonts[i]); - - if (stash->atlas) fons__deleteAtlas(stash->atlas); - if (stash->fonts) free(stash->fonts); - if (stash->texData) free(stash->texData); - if (stash->scratch) free(stash->scratch); - free(stash); -} - -void fonsSetErrorCallback(FONScontext* stash, void (*callback)(void* uptr, int error, int val), void* uptr) -{ - if (stash == NULL) return; - stash->handleError = callback; - stash->errorUptr = uptr; -} - -void fonsGetAtlasSize(FONScontext* stash, int* width, int* height) -{ - if (stash == NULL) return; - *width = stash->params.width; - *height = stash->params.height; -} - -int fonsExpandAtlas(FONScontext* stash, int width, int height) -{ - int i, maxy = 0; - unsigned char* data = NULL; - if (stash == NULL) return 0; - - width = fons__maxi(width, stash->params.width); - height = fons__maxi(height, stash->params.height); - - if (width == stash->params.width && height == stash->params.height) - return 1; - - // Flush pending glyphs. - fons__flush(stash); - - // Create new texture - if (stash->params.renderResize != NULL) { - if (stash->params.renderResize(stash->params.userPtr, width, height) == 0) - return 0; - } - // Copy old texture data over. - data = (unsigned char*)malloc(width * height); - if (data == NULL) - return 0; - for (i = 0; i < stash->params.height; i++) { - unsigned char* dst = &data[i*width]; - unsigned char* src = &stash->texData[i*stash->params.width]; - memcpy(dst, src, stash->params.width); - if (width > stash->params.width) - memset(dst+stash->params.width, 0, width - stash->params.width); - } - if (height > stash->params.height) - memset(&data[stash->params.height * width], 0, (height - stash->params.height) * width); - - free(stash->texData); - stash->texData = data; - - // Increase atlas size - fons__atlasExpand(stash->atlas, width, height); - - // Add existing data as dirty. - for (i = 0; i < stash->atlas->nnodes; i++) - maxy = fons__maxi(maxy, stash->atlas->nodes[i].y); - stash->dirtyRect[0] = 0; - stash->dirtyRect[1] = 0; - stash->dirtyRect[2] = stash->params.width; - stash->dirtyRect[3] = maxy; - - stash->params.width = width; - stash->params.height = height; - stash->itw = 1.0f/stash->params.width; - stash->ith = 1.0f/stash->params.height; - - return 1; -} - -int fonsResetAtlas(FONScontext* stash, int width, int height) -{ - int i, j; - if (stash == NULL) return 0; - - // Flush pending glyphs. - fons__flush(stash); - - // Create new texture - if (stash->params.renderResize != NULL) { - if (stash->params.renderResize(stash->params.userPtr, width, height) == 0) - return 0; - } - - // Reset atlas - fons__atlasReset(stash->atlas, width, height); - - // Clear texture data. - stash->texData = (unsigned char*)realloc(stash->texData, width * height); - if (stash->texData == NULL) return 0; - memset(stash->texData, 0, width * height); - - // Reset dirty rect - stash->dirtyRect[0] = width; - stash->dirtyRect[1] = height; - stash->dirtyRect[2] = 0; - stash->dirtyRect[3] = 0; - - // Reset cached glyphs - for (i = 0; i < stash->nfonts; i++) { - FONSfont* font = stash->fonts[i]; - font->nglyphs = 0; - for (j = 0; j < FONS_HASH_LUT_SIZE; j++) - font->lut[j] = -1; - } - - stash->params.width = width; - stash->params.height = height; - stash->itw = 1.0f/stash->params.width; - stash->ith = 1.0f/stash->params.height; - - // Add white rect at 0,0 for debug drawing. - fons__addWhiteRect(stash, 2,2); - - return 1; -} - - -#endif diff --git a/third_party/nanovg/nanovg.c b/third_party/nanovg/nanovg.c deleted file mode 100644 index 51f972ca5..000000000 --- a/third_party/nanovg/nanovg.c +++ /dev/null @@ -1,2870 +0,0 @@ -// -// Copyright (c) 2013 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#include -#include -#include -#include - -#include "nanovg.h" -#define FONTSTASH_IMPLEMENTATION -#include "fontstash.h" -#define STB_IMAGE_IMPLEMENTATION -#include "stb_image.h" - -#ifdef _MSC_VER -#pragma warning(disable: 4100) // unreferenced formal parameter -#pragma warning(disable: 4127) // conditional expression is constant -#pragma warning(disable: 4204) // nonstandard extension used : non-constant aggregate initializer -#pragma warning(disable: 4706) // assignment within conditional expression -#endif - -#define NVG_INIT_FONTIMAGE_SIZE 512 -#define NVG_MAX_FONTIMAGE_SIZE 2048 -#define NVG_MAX_FONTIMAGES 4 - -#define NVG_INIT_COMMANDS_SIZE 256 -#define NVG_INIT_POINTS_SIZE 128 -#define NVG_INIT_PATHS_SIZE 16 -#define NVG_INIT_VERTS_SIZE 256 -#define NVG_MAX_STATES 32 - -#define NVG_KAPPA90 0.5522847493f // Length proportional to radius of a cubic bezier handle for 90deg arcs. - -#define NVG_COUNTOF(arr) (sizeof(arr) / sizeof(0[arr])) - - -enum NVGcommands { - NVG_MOVETO = 0, - NVG_LINETO = 1, - NVG_BEZIERTO = 2, - NVG_CLOSE = 3, - NVG_WINDING = 4, -}; - -enum NVGpointFlags -{ - NVG_PT_CORNER = 0x01, - NVG_PT_LEFT = 0x02, - NVG_PT_BEVEL = 0x04, - NVG_PR_INNERBEVEL = 0x08, -}; - -struct NVGstate { - NVGcompositeOperationState compositeOperation; - NVGpaint fill; - NVGpaint stroke; - float strokeWidth; - float miterLimit; - int lineJoin; - int lineCap; - float alpha; - float xform[6]; - NVGscissor scissor; - float fontSize; - float letterSpacing; - float lineHeight; - float fontBlur; - int textAlign; - int fontId; -}; -typedef struct NVGstate NVGstate; - -struct NVGpoint { - float x,y; - float dx, dy; - float len; - float dmx, dmy; - unsigned char flags; -}; -typedef struct NVGpoint NVGpoint; - -struct NVGpathCache { - NVGpoint* points; - int npoints; - int cpoints; - NVGpath* paths; - int npaths; - int cpaths; - NVGvertex* verts; - int nverts; - int cverts; - float bounds[4]; -}; -typedef struct NVGpathCache NVGpathCache; - -struct NVGcontext { - NVGparams params; - float* commands; - int ccommands; - int ncommands; - float commandx, commandy; - NVGstate states[NVG_MAX_STATES]; - int nstates; - NVGpathCache* cache; - float tessTol; - float distTol; - float fringeWidth; - float devicePxRatio; - struct FONScontext* fs; - int fontImages[NVG_MAX_FONTIMAGES]; - int fontImageIdx; - int drawCallCount; - int fillTriCount; - int strokeTriCount; - int textTriCount; -}; - -static float nvg__sqrtf(float a) { return sqrtf(a); } -static float nvg__modf(float a, float b) { return fmodf(a, b); } -static float nvg__sinf(float a) { return sinf(a); } -static float nvg__cosf(float a) { return cosf(a); } -static float nvg__tanf(float a) { return tanf(a); } -static float nvg__atan2f(float a,float b) { return atan2f(a, b); } -static float nvg__acosf(float a) { return acosf(a); } - -static int nvg__mini(int a, int b) { return a < b ? a : b; } -static int nvg__maxi(int a, int b) { return a > b ? a : b; } -static int nvg__clampi(int a, int mn, int mx) { return a < mn ? mn : (a > mx ? mx : a); } -static float nvg__minf(float a, float b) { return a < b ? a : b; } -static float nvg__maxf(float a, float b) { return a > b ? a : b; } -static float nvg__absf(float a) { return a >= 0.0f ? a : -a; } -static float nvg__signf(float a) { return a >= 0.0f ? 1.0f : -1.0f; } -static float nvg__clampf(float a, float mn, float mx) { return a < mn ? mn : (a > mx ? mx : a); } -static float nvg__cross(float dx0, float dy0, float dx1, float dy1) { return dx1*dy0 - dx0*dy1; } - -static float nvg__normalize(float *x, float* y) -{ - float d = nvg__sqrtf((*x)*(*x) + (*y)*(*y)); - if (d > 1e-6f) { - float id = 1.0f / d; - *x *= id; - *y *= id; - } - return d; -} - - -static void nvg__deletePathCache(NVGpathCache* c) -{ - if (c == NULL) return; - if (c->points != NULL) free(c->points); - if (c->paths != NULL) free(c->paths); - if (c->verts != NULL) free(c->verts); - free(c); -} - -static NVGpathCache* nvg__allocPathCache(void) -{ - NVGpathCache* c = (NVGpathCache*)malloc(sizeof(NVGpathCache)); - if (c == NULL) goto error; - memset(c, 0, sizeof(NVGpathCache)); - - c->points = (NVGpoint*)malloc(sizeof(NVGpoint)*NVG_INIT_POINTS_SIZE); - if (!c->points) goto error; - c->npoints = 0; - c->cpoints = NVG_INIT_POINTS_SIZE; - - c->paths = (NVGpath*)malloc(sizeof(NVGpath)*NVG_INIT_PATHS_SIZE); - if (!c->paths) goto error; - c->npaths = 0; - c->cpaths = NVG_INIT_PATHS_SIZE; - - c->verts = (NVGvertex*)malloc(sizeof(NVGvertex)*NVG_INIT_VERTS_SIZE); - if (!c->verts) goto error; - c->nverts = 0; - c->cverts = NVG_INIT_VERTS_SIZE; - - return c; -error: - nvg__deletePathCache(c); - return NULL; -} - -static void nvg__setDevicePixelRatio(NVGcontext* ctx, float ratio) -{ - ctx->tessTol = 0.25f / ratio; - ctx->distTol = 0.01f / ratio; - ctx->fringeWidth = 1.0f / ratio; - ctx->devicePxRatio = ratio; -} - -static NVGcompositeOperationState nvg__compositeOperationState(int op) -{ - int sfactor, dfactor; - - if (op == NVG_SOURCE_OVER) - { - sfactor = NVG_ONE; - dfactor = NVG_ONE_MINUS_SRC_ALPHA; - } - else if (op == NVG_SOURCE_IN) - { - sfactor = NVG_DST_ALPHA; - dfactor = NVG_ZERO; - } - else if (op == NVG_SOURCE_OUT) - { - sfactor = NVG_ONE_MINUS_DST_ALPHA; - dfactor = NVG_ZERO; - } - else if (op == NVG_ATOP) - { - sfactor = NVG_DST_ALPHA; - dfactor = NVG_ONE_MINUS_SRC_ALPHA; - } - else if (op == NVG_DESTINATION_OVER) - { - sfactor = NVG_ONE_MINUS_DST_ALPHA; - dfactor = NVG_ONE; - } - else if (op == NVG_DESTINATION_IN) - { - sfactor = NVG_ZERO; - dfactor = NVG_SRC_ALPHA; - } - else if (op == NVG_DESTINATION_OUT) - { - sfactor = NVG_ZERO; - dfactor = NVG_ONE_MINUS_SRC_ALPHA; - } - else if (op == NVG_DESTINATION_ATOP) - { - sfactor = NVG_ONE_MINUS_DST_ALPHA; - dfactor = NVG_SRC_ALPHA; - } - else if (op == NVG_LIGHTER) - { - sfactor = NVG_ONE; - dfactor = NVG_ONE; - } - else if (op == NVG_COPY) - { - sfactor = NVG_ONE; - dfactor = NVG_ZERO; - } - else if (op == NVG_XOR) - { - sfactor = NVG_ONE_MINUS_DST_ALPHA; - dfactor = NVG_ONE_MINUS_SRC_ALPHA; - } - - NVGcompositeOperationState state; - state.srcRGB = sfactor; - state.dstRGB = dfactor; - state.srcAlpha = sfactor; - state.dstAlpha = dfactor; - return state; -} - -static NVGstate* nvg__getState(NVGcontext* ctx) -{ - return &ctx->states[ctx->nstates-1]; -} - -NVGcontext* nvgCreateInternal(NVGparams* params) -{ - FONSparams fontParams; - NVGcontext* ctx = (NVGcontext*)malloc(sizeof(NVGcontext)); - int i; - if (ctx == NULL) goto error; - memset(ctx, 0, sizeof(NVGcontext)); - - ctx->params = *params; - for (i = 0; i < NVG_MAX_FONTIMAGES; i++) - ctx->fontImages[i] = 0; - - ctx->commands = (float*)malloc(sizeof(float)*NVG_INIT_COMMANDS_SIZE); - if (!ctx->commands) goto error; - ctx->ncommands = 0; - ctx->ccommands = NVG_INIT_COMMANDS_SIZE; - - ctx->cache = nvg__allocPathCache(); - if (ctx->cache == NULL) goto error; - - nvgSave(ctx); - nvgReset(ctx); - - nvg__setDevicePixelRatio(ctx, 1.0f); - - if (ctx->params.renderCreate(ctx->params.userPtr) == 0) goto error; - - // Init font rendering - memset(&fontParams, 0, sizeof(fontParams)); - fontParams.width = NVG_INIT_FONTIMAGE_SIZE; - fontParams.height = NVG_INIT_FONTIMAGE_SIZE; - fontParams.flags = FONS_ZERO_TOPLEFT; - fontParams.renderCreate = NULL; - fontParams.renderUpdate = NULL; - fontParams.renderDraw = NULL; - fontParams.renderDelete = NULL; - fontParams.userPtr = NULL; - ctx->fs = fonsCreateInternal(&fontParams); - if (ctx->fs == NULL) goto error; - - // Create font texture - ctx->fontImages[0] = ctx->params.renderCreateTexture(ctx->params.userPtr, NVG_TEXTURE_ALPHA, fontParams.width, fontParams.height, 0, NULL); - if (ctx->fontImages[0] == 0) goto error; - ctx->fontImageIdx = 0; - - return ctx; - -error: - nvgDeleteInternal(ctx); - return 0; -} - -NVGparams* nvgInternalParams(NVGcontext* ctx) -{ - return &ctx->params; -} - -void nvgDeleteInternal(NVGcontext* ctx) -{ - int i; - if (ctx == NULL) return; - if (ctx->commands != NULL) free(ctx->commands); - if (ctx->cache != NULL) nvg__deletePathCache(ctx->cache); - - if (ctx->fs) - fonsDeleteInternal(ctx->fs); - - for (i = 0; i < NVG_MAX_FONTIMAGES; i++) { - if (ctx->fontImages[i] != 0) { - nvgDeleteImage(ctx, ctx->fontImages[i]); - ctx->fontImages[i] = 0; - } - } - - if (ctx->params.renderDelete != NULL) - ctx->params.renderDelete(ctx->params.userPtr); - - free(ctx); -} - -void nvgBeginFrame(NVGcontext* ctx, int windowWidth, int windowHeight, float devicePixelRatio) -{ -/* printf("Tris: draws:%d fill:%d stroke:%d text:%d TOT:%d\n", - ctx->drawCallCount, ctx->fillTriCount, ctx->strokeTriCount, ctx->textTriCount, - ctx->fillTriCount+ctx->strokeTriCount+ctx->textTriCount);*/ - - ctx->nstates = 0; - nvgSave(ctx); - nvgReset(ctx); - - nvg__setDevicePixelRatio(ctx, devicePixelRatio); - - ctx->params.renderViewport(ctx->params.userPtr, windowWidth, windowHeight, devicePixelRatio); - - ctx->drawCallCount = 0; - ctx->fillTriCount = 0; - ctx->strokeTriCount = 0; - ctx->textTriCount = 0; -} - -void nvgCancelFrame(NVGcontext* ctx) -{ - ctx->params.renderCancel(ctx->params.userPtr); -} - -void nvgEndFrame(NVGcontext* ctx) -{ - NVGstate* state = nvg__getState(ctx); - ctx->params.renderFlush(ctx->params.userPtr, state->compositeOperation); - if (ctx->fontImageIdx != 0) { - int fontImage = ctx->fontImages[ctx->fontImageIdx]; - int i, j, iw, ih; - // delete images that smaller than current one - if (fontImage == 0) - return; - nvgImageSize(ctx, fontImage, &iw, &ih); - for (i = j = 0; i < ctx->fontImageIdx; i++) { - if (ctx->fontImages[i] != 0) { - int nw, nh; - nvgImageSize(ctx, ctx->fontImages[i], &nw, &nh); - if (nw < iw || nh < ih) - nvgDeleteImage(ctx, ctx->fontImages[i]); - else - ctx->fontImages[j++] = ctx->fontImages[i]; - } - } - // make current font image to first - ctx->fontImages[j++] = ctx->fontImages[0]; - ctx->fontImages[0] = fontImage; - ctx->fontImageIdx = 0; - // clear all images after j - for (i = j; i < NVG_MAX_FONTIMAGES; i++) - ctx->fontImages[i] = 0; - } -} - -NVGcolor nvgRGB(unsigned char r, unsigned char g, unsigned char b) -{ - return nvgRGBA(r,g,b,255); -} - -NVGcolor nvgRGBf(float r, float g, float b) -{ - return nvgRGBAf(r,g,b,1.0f); -} - -NVGcolor nvgRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) -{ - NVGcolor color; - // Use longer initialization to suppress warning. - color.r = r / 255.0f; - color.g = g / 255.0f; - color.b = b / 255.0f; - color.a = a / 255.0f; - return color; -} - -NVGcolor nvgRGBAf(float r, float g, float b, float a) -{ - NVGcolor color; - // Use longer initialization to suppress warning. - color.r = r; - color.g = g; - color.b = b; - color.a = a; - return color; -} - -NVGcolor nvgTransRGBA(NVGcolor c, unsigned char a) -{ - c.a = a / 255.0f; - return c; -} - -NVGcolor nvgTransRGBAf(NVGcolor c, float a) -{ - c.a = a; - return c; -} - -NVGcolor nvgLerpRGBA(NVGcolor c0, NVGcolor c1, float u) -{ - int i; - float oneminu; - NVGcolor cint = {0}; - - u = nvg__clampf(u, 0.0f, 1.0f); - oneminu = 1.0f - u; - for( i = 0; i <4; i++ ) - { - cint.rgba[i] = c0.rgba[i] * oneminu + c1.rgba[i] * u; - } - - return cint; -} - -NVGcolor nvgHSL(float h, float s, float l) -{ - return nvgHSLA(h,s,l,255); -} - -static float nvg__hue(float h, float m1, float m2) -{ - if (h < 0) h += 1; - if (h > 1) h -= 1; - if (h < 1.0f/6.0f) - return m1 + (m2 - m1) * h * 6.0f; - else if (h < 3.0f/6.0f) - return m2; - else if (h < 4.0f/6.0f) - return m1 + (m2 - m1) * (2.0f/3.0f - h) * 6.0f; - return m1; -} - -NVGcolor nvgHSLA(float h, float s, float l, unsigned char a) -{ - float m1, m2; - NVGcolor col; - h = nvg__modf(h, 1.0f); - if (h < 0.0f) h += 1.0f; - s = nvg__clampf(s, 0.0f, 1.0f); - l = nvg__clampf(l, 0.0f, 1.0f); - m2 = l <= 0.5f ? (l * (1 + s)) : (l + s - l * s); - m1 = 2 * l - m2; - col.r = nvg__clampf(nvg__hue(h + 1.0f/3.0f, m1, m2), 0.0f, 1.0f); - col.g = nvg__clampf(nvg__hue(h, m1, m2), 0.0f, 1.0f); - col.b = nvg__clampf(nvg__hue(h - 1.0f/3.0f, m1, m2), 0.0f, 1.0f); - col.a = a/255.0f; - return col; -} - -void nvgTransformIdentity(float* t) -{ - t[0] = 1.0f; t[1] = 0.0f; - t[2] = 0.0f; t[3] = 1.0f; - t[4] = 0.0f; t[5] = 0.0f; -} - -void nvgTransformTranslate(float* t, float tx, float ty) -{ - t[0] = 1.0f; t[1] = 0.0f; - t[2] = 0.0f; t[3] = 1.0f; - t[4] = tx; t[5] = ty; -} - -void nvgTransformScale(float* t, float sx, float sy) -{ - t[0] = sx; t[1] = 0.0f; - t[2] = 0.0f; t[3] = sy; - t[4] = 0.0f; t[5] = 0.0f; -} - -void nvgTransformRotate(float* t, float a) -{ - float cs = nvg__cosf(a), sn = nvg__sinf(a); - t[0] = cs; t[1] = sn; - t[2] = -sn; t[3] = cs; - t[4] = 0.0f; t[5] = 0.0f; -} - -void nvgTransformSkewX(float* t, float a) -{ - t[0] = 1.0f; t[1] = 0.0f; - t[2] = nvg__tanf(a); t[3] = 1.0f; - t[4] = 0.0f; t[5] = 0.0f; -} - -void nvgTransformSkewY(float* t, float a) -{ - t[0] = 1.0f; t[1] = nvg__tanf(a); - t[2] = 0.0f; t[3] = 1.0f; - t[4] = 0.0f; t[5] = 0.0f; -} - -void nvgTransformMultiply(float* t, const float* s) -{ - float t0 = t[0] * s[0] + t[1] * s[2]; - float t2 = t[2] * s[0] + t[3] * s[2]; - float t4 = t[4] * s[0] + t[5] * s[2] + s[4]; - t[1] = t[0] * s[1] + t[1] * s[3]; - t[3] = t[2] * s[1] + t[3] * s[3]; - t[5] = t[4] * s[1] + t[5] * s[3] + s[5]; - t[0] = t0; - t[2] = t2; - t[4] = t4; -} - -void nvgTransformPremultiply(float* t, const float* s) -{ - float s2[6]; - memcpy(s2, s, sizeof(float)*6); - nvgTransformMultiply(s2, t); - memcpy(t, s2, sizeof(float)*6); -} - -int nvgTransformInverse(float* inv, const float* t) -{ - double invdet, det = (double)t[0] * t[3] - (double)t[2] * t[1]; - if (det > -1e-6 && det < 1e-6) { - nvgTransformIdentity(inv); - return 0; - } - invdet = 1.0 / det; - inv[0] = (float)(t[3] * invdet); - inv[2] = (float)(-t[2] * invdet); - inv[4] = (float)(((double)t[2] * t[5] - (double)t[3] * t[4]) * invdet); - inv[1] = (float)(-t[1] * invdet); - inv[3] = (float)(t[0] * invdet); - inv[5] = (float)(((double)t[1] * t[4] - (double)t[0] * t[5]) * invdet); - return 1; -} - -void nvgTransformPoint(float* dx, float* dy, const float* t, float sx, float sy) -{ - *dx = sx*t[0] + sy*t[2] + t[4]; - *dy = sx*t[1] + sy*t[3] + t[5]; -} - -float nvgDegToRad(float deg) -{ - return deg / 180.0f * NVG_PI; -} - -float nvgRadToDeg(float rad) -{ - return rad / NVG_PI * 180.0f; -} - -static void nvg__setPaintColor(NVGpaint* p, NVGcolor color) -{ - memset(p, 0, sizeof(*p)); - nvgTransformIdentity(p->xform); - p->radius = 0.0f; - p->feather = 1.0f; - p->innerColor = color; - p->outerColor = color; -} - - -// State handling -void nvgSave(NVGcontext* ctx) -{ - if (ctx->nstates >= NVG_MAX_STATES) - return; - if (ctx->nstates > 0) - memcpy(&ctx->states[ctx->nstates], &ctx->states[ctx->nstates-1], sizeof(NVGstate)); - ctx->nstates++; -} - -void nvgRestore(NVGcontext* ctx) -{ - if (ctx->nstates <= 1) - return; - ctx->nstates--; -} - -void nvgReset(NVGcontext* ctx) -{ - NVGstate* state = nvg__getState(ctx); - memset(state, 0, sizeof(*state)); - - nvg__setPaintColor(&state->fill, nvgRGBA(255,255,255,255)); - nvg__setPaintColor(&state->stroke, nvgRGBA(0,0,0,255)); - state->compositeOperation = nvg__compositeOperationState(NVG_SOURCE_OVER); - state->strokeWidth = 1.0f; - state->miterLimit = 10.0f; - state->lineCap = NVG_BUTT; - state->lineJoin = NVG_MITER; - state->alpha = 1.0f; - nvgTransformIdentity(state->xform); - - state->scissor.extent[0] = -1.0f; - state->scissor.extent[1] = -1.0f; - - state->fontSize = 16.0f; - state->letterSpacing = 0.0f; - state->lineHeight = 1.0f; - state->fontBlur = 0.0f; - state->textAlign = NVG_ALIGN_LEFT | NVG_ALIGN_BASELINE; - state->fontId = 0; -} - -// State setting -void nvgStrokeWidth(NVGcontext* ctx, float width) -{ - NVGstate* state = nvg__getState(ctx); - state->strokeWidth = width; -} - -void nvgMiterLimit(NVGcontext* ctx, float limit) -{ - NVGstate* state = nvg__getState(ctx); - state->miterLimit = limit; -} - -void nvgLineCap(NVGcontext* ctx, int cap) -{ - NVGstate* state = nvg__getState(ctx); - state->lineCap = cap; -} - -void nvgLineJoin(NVGcontext* ctx, int join) -{ - NVGstate* state = nvg__getState(ctx); - state->lineJoin = join; -} - -void nvgGlobalAlpha(NVGcontext* ctx, float alpha) -{ - NVGstate* state = nvg__getState(ctx); - state->alpha = alpha; -} - -void nvgTransform(NVGcontext* ctx, float a, float b, float c, float d, float e, float f) -{ - NVGstate* state = nvg__getState(ctx); - float t[6] = { a, b, c, d, e, f }; - nvgTransformPremultiply(state->xform, t); -} - -void nvgResetTransform(NVGcontext* ctx) -{ - NVGstate* state = nvg__getState(ctx); - nvgTransformIdentity(state->xform); -} - -void nvgTranslate(NVGcontext* ctx, float x, float y) -{ - NVGstate* state = nvg__getState(ctx); - float t[6]; - nvgTransformTranslate(t, x,y); - nvgTransformPremultiply(state->xform, t); -} - -void nvgRotate(NVGcontext* ctx, float angle) -{ - NVGstate* state = nvg__getState(ctx); - float t[6]; - nvgTransformRotate(t, angle); - nvgTransformPremultiply(state->xform, t); -} - -void nvgSkewX(NVGcontext* ctx, float angle) -{ - NVGstate* state = nvg__getState(ctx); - float t[6]; - nvgTransformSkewX(t, angle); - nvgTransformPremultiply(state->xform, t); -} - -void nvgSkewY(NVGcontext* ctx, float angle) -{ - NVGstate* state = nvg__getState(ctx); - float t[6]; - nvgTransformSkewY(t, angle); - nvgTransformPremultiply(state->xform, t); -} - -void nvgScale(NVGcontext* ctx, float x, float y) -{ - NVGstate* state = nvg__getState(ctx); - float t[6]; - nvgTransformScale(t, x,y); - nvgTransformPremultiply(state->xform, t); -} - -void nvgCurrentTransform(NVGcontext* ctx, float* xform) -{ - NVGstate* state = nvg__getState(ctx); - if (xform == NULL) return; - memcpy(xform, state->xform, sizeof(float)*6); -} - -void nvgStrokeColor(NVGcontext* ctx, NVGcolor color) -{ - NVGstate* state = nvg__getState(ctx); - nvg__setPaintColor(&state->stroke, color); -} - -void nvgStrokePaint(NVGcontext* ctx, NVGpaint paint) -{ - NVGstate* state = nvg__getState(ctx); - state->stroke = paint; - nvgTransformMultiply(state->stroke.xform, state->xform); -} - -void nvgFillColor(NVGcontext* ctx, NVGcolor color) -{ - NVGstate* state = nvg__getState(ctx); - nvg__setPaintColor(&state->fill, color); -} - -void nvgFillPaint(NVGcontext* ctx, NVGpaint paint) -{ - NVGstate* state = nvg__getState(ctx); - state->fill = paint; - nvgTransformMultiply(state->fill.xform, state->xform); -} - -int nvgCreateImage(NVGcontext* ctx, const char* filename, int imageFlags) -{ - int w, h, n, image; - unsigned char* img; - stbi_set_unpremultiply_on_load(1); - stbi_convert_iphone_png_to_rgb(1); - img = stbi_load(filename, &w, &h, &n, 4); - if (img == NULL) { -// printf("Failed to load %s - %s\n", filename, stbi_failure_reason()); - return 0; - } - image = nvgCreateImageRGBA(ctx, w, h, imageFlags, img); - stbi_image_free(img); - return image; -} - -int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, unsigned char* data, int ndata) -{ - int w, h, n, image; - unsigned char* img = stbi_load_from_memory(data, ndata, &w, &h, &n, 4); - if (img == NULL) { -// printf("Failed to load %s - %s\n", filename, stbi_failure_reason()); - return 0; - } - image = nvgCreateImageRGBA(ctx, w, h, imageFlags, img); - stbi_image_free(img); - return image; -} - -int nvgCreateImageRGBA(NVGcontext* ctx, int w, int h, int imageFlags, const unsigned char* data) -{ - return ctx->params.renderCreateTexture(ctx->params.userPtr, NVG_TEXTURE_RGBA, w, h, imageFlags, data); -} - -void nvgUpdateImage(NVGcontext* ctx, int image, const unsigned char* data) -{ - int w, h; - ctx->params.renderGetTextureSize(ctx->params.userPtr, image, &w, &h); - ctx->params.renderUpdateTexture(ctx->params.userPtr, image, 0,0, w,h, data); -} - -void nvgImageSize(NVGcontext* ctx, int image, int* w, int* h) -{ - ctx->params.renderGetTextureSize(ctx->params.userPtr, image, w, h); -} - -void nvgDeleteImage(NVGcontext* ctx, int image) -{ - ctx->params.renderDeleteTexture(ctx->params.userPtr, image); -} - -NVGpaint nvgLinearGradient(NVGcontext* ctx, - float sx, float sy, float ex, float ey, - NVGcolor icol, NVGcolor ocol) -{ - NVGpaint p; - float dx, dy, d; - const float large = 1e5; - NVG_NOTUSED(ctx); - memset(&p, 0, sizeof(p)); - - // Calculate transform aligned to the line - dx = ex - sx; - dy = ey - sy; - d = sqrtf(dx*dx + dy*dy); - if (d > 0.0001f) { - dx /= d; - dy /= d; - } else { - dx = 0; - dy = 1; - } - - p.xform[0] = dy; p.xform[1] = -dx; - p.xform[2] = dx; p.xform[3] = dy; - p.xform[4] = sx - dx*large; p.xform[5] = sy - dy*large; - - p.extent[0] = large; - p.extent[1] = large + d*0.5f; - - p.radius = 0.0f; - - p.feather = nvg__maxf(1.0f, d); - - p.innerColor = icol; - p.outerColor = ocol; - - return p; -} - -NVGpaint nvgRadialGradient(NVGcontext* ctx, - float cx, float cy, float inr, float outr, - NVGcolor icol, NVGcolor ocol) -{ - NVGpaint p; - float r = (inr+outr)*0.5f; - float f = (outr-inr); - NVG_NOTUSED(ctx); - memset(&p, 0, sizeof(p)); - - nvgTransformIdentity(p.xform); - p.xform[4] = cx; - p.xform[5] = cy; - - p.extent[0] = r; - p.extent[1] = r; - - p.radius = r; - - p.feather = nvg__maxf(1.0f, f); - - p.innerColor = icol; - p.outerColor = ocol; - - return p; -} - -NVGpaint nvgBoxGradient(NVGcontext* ctx, - float x, float y, float w, float h, float r, float f, - NVGcolor icol, NVGcolor ocol) -{ - NVGpaint p; - NVG_NOTUSED(ctx); - memset(&p, 0, sizeof(p)); - - nvgTransformIdentity(p.xform); - p.xform[4] = x+w*0.5f; - p.xform[5] = y+h*0.5f; - - p.extent[0] = w*0.5f; - p.extent[1] = h*0.5f; - - p.radius = r; - - p.feather = nvg__maxf(1.0f, f); - - p.innerColor = icol; - p.outerColor = ocol; - - return p; -} - - -NVGpaint nvgImagePattern(NVGcontext* ctx, - float cx, float cy, float w, float h, float angle, - int image, float alpha) -{ - NVGpaint p; - NVG_NOTUSED(ctx); - memset(&p, 0, sizeof(p)); - - nvgTransformRotate(p.xform, angle); - p.xform[4] = cx; - p.xform[5] = cy; - - p.extent[0] = w; - p.extent[1] = h; - - p.image = image; - - p.innerColor = p.outerColor = nvgRGBAf(1,1,1,alpha); - - return p; -} - -// Scissoring -void nvgScissor(NVGcontext* ctx, float x, float y, float w, float h) -{ - NVGstate* state = nvg__getState(ctx); - - w = nvg__maxf(0.0f, w); - h = nvg__maxf(0.0f, h); - - nvgTransformIdentity(state->scissor.xform); - state->scissor.xform[4] = x+w*0.5f; - state->scissor.xform[5] = y+h*0.5f; - nvgTransformMultiply(state->scissor.xform, state->xform); - - state->scissor.extent[0] = w*0.5f; - state->scissor.extent[1] = h*0.5f; -} - -static void nvg__isectRects(float* dst, - float ax, float ay, float aw, float ah, - float bx, float by, float bw, float bh) -{ - float minx = nvg__maxf(ax, bx); - float miny = nvg__maxf(ay, by); - float maxx = nvg__minf(ax+aw, bx+bw); - float maxy = nvg__minf(ay+ah, by+bh); - dst[0] = minx; - dst[1] = miny; - dst[2] = nvg__maxf(0.0f, maxx - minx); - dst[3] = nvg__maxf(0.0f, maxy - miny); -} - -void nvgIntersectScissor(NVGcontext* ctx, float x, float y, float w, float h) -{ - NVGstate* state = nvg__getState(ctx); - float pxform[6], invxorm[6]; - float rect[4]; - float ex, ey, tex, tey; - - // If no previous scissor has been set, set the scissor as current scissor. - if (state->scissor.extent[0] < 0) { - nvgScissor(ctx, x, y, w, h); - return; - } - - // Transform the current scissor rect into current transform space. - // If there is difference in rotation, this will be approximation. - memcpy(pxform, state->scissor.xform, sizeof(float)*6); - ex = state->scissor.extent[0]; - ey = state->scissor.extent[1]; - nvgTransformInverse(invxorm, state->xform); - nvgTransformMultiply(pxform, invxorm); - tex = ex*nvg__absf(pxform[0]) + ey*nvg__absf(pxform[2]); - tey = ex*nvg__absf(pxform[1]) + ey*nvg__absf(pxform[3]); - - // Intersect rects. - nvg__isectRects(rect, pxform[4]-tex,pxform[5]-tey,tex*2,tey*2, x,y,w,h); - - nvgScissor(ctx, rect[0], rect[1], rect[2], rect[3]); -} - -void nvgResetScissor(NVGcontext* ctx) -{ - NVGstate* state = nvg__getState(ctx); - memset(state->scissor.xform, 0, sizeof(state->scissor.xform)); - state->scissor.extent[0] = -1.0f; - state->scissor.extent[1] = -1.0f; -} - -// Global composite operation. -void nvgGlobalCompositeOperation(NVGcontext* ctx, int op) -{ - NVGstate* state = nvg__getState(ctx); - state->compositeOperation = nvg__compositeOperationState(op); -} - -void nvgGlobalCompositeBlendFunc(NVGcontext* ctx, int sfactor, int dfactor) -{ - nvgGlobalCompositeBlendFuncSeparate(ctx, sfactor, dfactor, sfactor, dfactor); -} - -void nvgGlobalCompositeBlendFuncSeparate(NVGcontext* ctx, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) -{ - NVGcompositeOperationState op; - op.srcRGB = srcRGB; - op.dstRGB = dstRGB; - op.srcAlpha = srcAlpha; - op.dstAlpha = dstAlpha; - - NVGstate* state = nvg__getState(ctx); - state->compositeOperation = op; -} - -static int nvg__ptEquals(float x1, float y1, float x2, float y2, float tol) -{ - float dx = x2 - x1; - float dy = y2 - y1; - return dx*dx + dy*dy < tol*tol; -} - -static float nvg__distPtSeg(float x, float y, float px, float py, float qx, float qy) -{ - float pqx, pqy, dx, dy, d, t; - pqx = qx-px; - pqy = qy-py; - dx = x-px; - dy = y-py; - d = pqx*pqx + pqy*pqy; - t = pqx*dx + pqy*dy; - if (d > 0) t /= d; - if (t < 0) t = 0; - else if (t > 1) t = 1; - dx = px + t*pqx - x; - dy = py + t*pqy - y; - return dx*dx + dy*dy; -} - -static void nvg__appendCommands(NVGcontext* ctx, float* vals, int nvals) -{ - NVGstate* state = nvg__getState(ctx); - int i; - - if (ctx->ncommands+nvals > ctx->ccommands) { - float* commands; - int ccommands = ctx->ncommands+nvals + ctx->ccommands/2; - commands = (float*)realloc(ctx->commands, sizeof(float)*ccommands); - if (commands == NULL) return; - ctx->commands = commands; - ctx->ccommands = ccommands; - } - - if ((int)vals[0] != NVG_CLOSE && (int)vals[0] != NVG_WINDING) { - ctx->commandx = vals[nvals-2]; - ctx->commandy = vals[nvals-1]; - } - - // transform commands - i = 0; - while (i < nvals) { - int cmd = (int)vals[i]; - switch (cmd) { - case NVG_MOVETO: - nvgTransformPoint(&vals[i+1],&vals[i+2], state->xform, vals[i+1],vals[i+2]); - i += 3; - break; - case NVG_LINETO: - nvgTransformPoint(&vals[i+1],&vals[i+2], state->xform, vals[i+1],vals[i+2]); - i += 3; - break; - case NVG_BEZIERTO: - nvgTransformPoint(&vals[i+1],&vals[i+2], state->xform, vals[i+1],vals[i+2]); - nvgTransformPoint(&vals[i+3],&vals[i+4], state->xform, vals[i+3],vals[i+4]); - nvgTransformPoint(&vals[i+5],&vals[i+6], state->xform, vals[i+5],vals[i+6]); - i += 7; - break; - case NVG_CLOSE: - i++; - break; - case NVG_WINDING: - i += 2; - break; - default: - i++; - } - } - - memcpy(&ctx->commands[ctx->ncommands], vals, nvals*sizeof(float)); - - ctx->ncommands += nvals; -} - - -static void nvg__clearPathCache(NVGcontext* ctx) -{ - ctx->cache->npoints = 0; - ctx->cache->npaths = 0; -} - -static NVGpath* nvg__lastPath(NVGcontext* ctx) -{ - if (ctx->cache->npaths > 0) - return &ctx->cache->paths[ctx->cache->npaths-1]; - return NULL; -} - -static void nvg__addPath(NVGcontext* ctx) -{ - NVGpath* path; - if (ctx->cache->npaths+1 > ctx->cache->cpaths) { - NVGpath* paths; - int cpaths = ctx->cache->npaths+1 + ctx->cache->cpaths/2; - paths = (NVGpath*)realloc(ctx->cache->paths, sizeof(NVGpath)*cpaths); - if (paths == NULL) return; - ctx->cache->paths = paths; - ctx->cache->cpaths = cpaths; - } - path = &ctx->cache->paths[ctx->cache->npaths]; - memset(path, 0, sizeof(*path)); - path->first = ctx->cache->npoints; - path->winding = NVG_CCW; - - ctx->cache->npaths++; -} - -static NVGpoint* nvg__lastPoint(NVGcontext* ctx) -{ - if (ctx->cache->npoints > 0) - return &ctx->cache->points[ctx->cache->npoints-1]; - return NULL; -} - -static void nvg__addPoint(NVGcontext* ctx, float x, float y, int flags) -{ - NVGpath* path = nvg__lastPath(ctx); - NVGpoint* pt; - if (path == NULL) return; - - if (path->count > 0 && ctx->cache->npoints > 0) { - pt = nvg__lastPoint(ctx); - if (nvg__ptEquals(pt->x,pt->y, x,y, ctx->distTol)) { - pt->flags |= flags; - return; - } - } - - if (ctx->cache->npoints+1 > ctx->cache->cpoints) { - NVGpoint* points; - int cpoints = ctx->cache->npoints+1 + ctx->cache->cpoints/2; - points = (NVGpoint*)realloc(ctx->cache->points, sizeof(NVGpoint)*cpoints); - if (points == NULL) return; - ctx->cache->points = points; - ctx->cache->cpoints = cpoints; - } - - pt = &ctx->cache->points[ctx->cache->npoints]; - memset(pt, 0, sizeof(*pt)); - pt->x = x; - pt->y = y; - pt->flags = (unsigned char)flags; - - ctx->cache->npoints++; - path->count++; -} - -static void nvg__closePath(NVGcontext* ctx) -{ - NVGpath* path = nvg__lastPath(ctx); - if (path == NULL) return; - path->closed = 1; -} - -static void nvg__pathWinding(NVGcontext* ctx, int winding) -{ - NVGpath* path = nvg__lastPath(ctx); - if (path == NULL) return; - path->winding = winding; -} - -static float nvg__getAverageScale(float *t) -{ - float sx = sqrtf(t[0]*t[0] + t[2]*t[2]); - float sy = sqrtf(t[1]*t[1] + t[3]*t[3]); - return (sx + sy) * 0.5f; -} - -static NVGvertex* nvg__allocTempVerts(NVGcontext* ctx, int nverts) -{ - if (nverts > ctx->cache->cverts) { - NVGvertex* verts; - int cverts = (nverts + 0xff) & ~0xff; // Round up to prevent allocations when things change just slightly. - verts = (NVGvertex*)realloc(ctx->cache->verts, sizeof(NVGvertex)*cverts); - if (verts == NULL) return NULL; - ctx->cache->verts = verts; - ctx->cache->cverts = cverts; - } - - return ctx->cache->verts; -} - -static float nvg__triarea2(float ax, float ay, float bx, float by, float cx, float cy) -{ - float abx = bx - ax; - float aby = by - ay; - float acx = cx - ax; - float acy = cy - ay; - return acx*aby - abx*acy; -} - -static float nvg__polyArea(NVGpoint* pts, int npts) -{ - int i; - float area = 0; - for (i = 2; i < npts; i++) { - NVGpoint* a = &pts[0]; - NVGpoint* b = &pts[i-1]; - NVGpoint* c = &pts[i]; - area += nvg__triarea2(a->x,a->y, b->x,b->y, c->x,c->y); - } - return area * 0.5f; -} - -static void nvg__polyReverse(NVGpoint* pts, int npts) -{ - NVGpoint tmp; - int i = 0, j = npts-1; - while (i < j) { - tmp = pts[i]; - pts[i] = pts[j]; - pts[j] = tmp; - i++; - j--; - } -} - - -static void nvg__vset(NVGvertex* vtx, float x, float y, float u, float v) -{ - vtx->x = x; - vtx->y = y; - vtx->u = u; - vtx->v = v; -} - -static void nvg__tesselateBezier(NVGcontext* ctx, - float x1, float y1, float x2, float y2, - float x3, float y3, float x4, float y4, - int level, int type) -{ - float x12,y12,x23,y23,x34,y34,x123,y123,x234,y234,x1234,y1234; - float dx,dy,d2,d3; - - if (level > 10) return; - - x12 = (x1+x2)*0.5f; - y12 = (y1+y2)*0.5f; - x23 = (x2+x3)*0.5f; - y23 = (y2+y3)*0.5f; - x34 = (x3+x4)*0.5f; - y34 = (y3+y4)*0.5f; - x123 = (x12+x23)*0.5f; - y123 = (y12+y23)*0.5f; - - dx = x4 - x1; - dy = y4 - y1; - d2 = nvg__absf(((x2 - x4) * dy - (y2 - y4) * dx)); - d3 = nvg__absf(((x3 - x4) * dy - (y3 - y4) * dx)); - - if ((d2 + d3)*(d2 + d3) < ctx->tessTol * (dx*dx + dy*dy)) { - nvg__addPoint(ctx, x4, y4, type); - return; - } - -/* if (nvg__absf(x1+x3-x2-x2) + nvg__absf(y1+y3-y2-y2) + nvg__absf(x2+x4-x3-x3) + nvg__absf(y2+y4-y3-y3) < ctx->tessTol) { - nvg__addPoint(ctx, x4, y4, type); - return; - }*/ - - x234 = (x23+x34)*0.5f; - y234 = (y23+y34)*0.5f; - x1234 = (x123+x234)*0.5f; - y1234 = (y123+y234)*0.5f; - - nvg__tesselateBezier(ctx, x1,y1, x12,y12, x123,y123, x1234,y1234, level+1, 0); - nvg__tesselateBezier(ctx, x1234,y1234, x234,y234, x34,y34, x4,y4, level+1, type); -} - -static void nvg__flattenPaths(NVGcontext* ctx) -{ - NVGpathCache* cache = ctx->cache; -// NVGstate* state = nvg__getState(ctx); - NVGpoint* last; - NVGpoint* p0; - NVGpoint* p1; - NVGpoint* pts; - NVGpath* path; - int i, j; - float* cp1; - float* cp2; - float* p; - float area; - - if (cache->npaths > 0) - return; - - // Flatten - i = 0; - while (i < ctx->ncommands) { - int cmd = (int)ctx->commands[i]; - switch (cmd) { - case NVG_MOVETO: - nvg__addPath(ctx); - p = &ctx->commands[i+1]; - nvg__addPoint(ctx, p[0], p[1], NVG_PT_CORNER); - i += 3; - break; - case NVG_LINETO: - p = &ctx->commands[i+1]; - nvg__addPoint(ctx, p[0], p[1], NVG_PT_CORNER); - i += 3; - break; - case NVG_BEZIERTO: - last = nvg__lastPoint(ctx); - if (last != NULL) { - cp1 = &ctx->commands[i+1]; - cp2 = &ctx->commands[i+3]; - p = &ctx->commands[i+5]; - nvg__tesselateBezier(ctx, last->x,last->y, cp1[0],cp1[1], cp2[0],cp2[1], p[0],p[1], 0, NVG_PT_CORNER); - } - i += 7; - break; - case NVG_CLOSE: - nvg__closePath(ctx); - i++; - break; - case NVG_WINDING: - nvg__pathWinding(ctx, (int)ctx->commands[i+1]); - i += 2; - break; - default: - i++; - } - } - - cache->bounds[0] = cache->bounds[1] = 1e6f; - cache->bounds[2] = cache->bounds[3] = -1e6f; - - // Calculate the direction and length of line segments. - for (j = 0; j < cache->npaths; j++) { - path = &cache->paths[j]; - pts = &cache->points[path->first]; - - // If the first and last points are the same, remove the last, mark as closed path. - p0 = &pts[path->count-1]; - p1 = &pts[0]; - if (nvg__ptEquals(p0->x,p0->y, p1->x,p1->y, ctx->distTol)) { - path->count--; - p0 = &pts[path->count-1]; - path->closed = 1; - } - - // Enforce winding. - if (path->count > 2) { - area = nvg__polyArea(pts, path->count); - if (path->winding == NVG_CCW && area < 0.0f) - nvg__polyReverse(pts, path->count); - if (path->winding == NVG_CW && area > 0.0f) - nvg__polyReverse(pts, path->count); - } - - for(i = 0; i < path->count; i++) { - // Calculate segment direction and length - p0->dx = p1->x - p0->x; - p0->dy = p1->y - p0->y; - p0->len = nvg__normalize(&p0->dx, &p0->dy); - // Update bounds - cache->bounds[0] = nvg__minf(cache->bounds[0], p0->x); - cache->bounds[1] = nvg__minf(cache->bounds[1], p0->y); - cache->bounds[2] = nvg__maxf(cache->bounds[2], p0->x); - cache->bounds[3] = nvg__maxf(cache->bounds[3], p0->y); - // Advance - p0 = p1++; - } - } -} - -static int nvg__curveDivs(float r, float arc, float tol) -{ - float da = acosf(r / (r + tol)) * 2.0f; - return nvg__maxi(2, (int)ceilf(arc / da)); -} - -static void nvg__chooseBevel(int bevel, NVGpoint* p0, NVGpoint* p1, float w, - float* x0, float* y0, float* x1, float* y1) -{ - if (bevel) { - *x0 = p1->x + p0->dy * w; - *y0 = p1->y - p0->dx * w; - *x1 = p1->x + p1->dy * w; - *y1 = p1->y - p1->dx * w; - } else { - *x0 = p1->x + p1->dmx * w; - *y0 = p1->y + p1->dmy * w; - *x1 = p1->x + p1->dmx * w; - *y1 = p1->y + p1->dmy * w; - } -} - -static NVGvertex* nvg__roundJoin(NVGvertex* dst, NVGpoint* p0, NVGpoint* p1, - float lw, float rw, float lu, float ru, int ncap, float fringe) -{ - int i, n; - float dlx0 = p0->dy; - float dly0 = -p0->dx; - float dlx1 = p1->dy; - float dly1 = -p1->dx; - NVG_NOTUSED(fringe); - - if (p1->flags & NVG_PT_LEFT) { - float lx0,ly0,lx1,ly1,a0,a1; - nvg__chooseBevel(p1->flags & NVG_PR_INNERBEVEL, p0, p1, lw, &lx0,&ly0, &lx1,&ly1); - a0 = atan2f(-dly0, -dlx0); - a1 = atan2f(-dly1, -dlx1); - if (a1 > a0) a1 -= NVG_PI*2; - - nvg__vset(dst, lx0, ly0, lu,1); dst++; - nvg__vset(dst, p1->x - dlx0*rw, p1->y - dly0*rw, ru,1); dst++; - - n = nvg__clampi((int)ceilf(((a0 - a1) / NVG_PI) * ncap), 2, ncap); - for (i = 0; i < n; i++) { - float u = i/(float)(n-1); - float a = a0 + u*(a1-a0); - float rx = p1->x + cosf(a) * rw; - float ry = p1->y + sinf(a) * rw; - nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++; - nvg__vset(dst, rx, ry, ru,1); dst++; - } - - nvg__vset(dst, lx1, ly1, lu,1); dst++; - nvg__vset(dst, p1->x - dlx1*rw, p1->y - dly1*rw, ru,1); dst++; - - } else { - float rx0,ry0,rx1,ry1,a0,a1; - nvg__chooseBevel(p1->flags & NVG_PR_INNERBEVEL, p0, p1, -rw, &rx0,&ry0, &rx1,&ry1); - a0 = atan2f(dly0, dlx0); - a1 = atan2f(dly1, dlx1); - if (a1 < a0) a1 += NVG_PI*2; - - nvg__vset(dst, p1->x + dlx0*rw, p1->y + dly0*rw, lu,1); dst++; - nvg__vset(dst, rx0, ry0, ru,1); dst++; - - n = nvg__clampi((int)ceilf(((a1 - a0) / NVG_PI) * ncap), 2, ncap); - for (i = 0; i < n; i++) { - float u = i/(float)(n-1); - float a = a0 + u*(a1-a0); - float lx = p1->x + cosf(a) * lw; - float ly = p1->y + sinf(a) * lw; - nvg__vset(dst, lx, ly, lu,1); dst++; - nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++; - } - - nvg__vset(dst, p1->x + dlx1*rw, p1->y + dly1*rw, lu,1); dst++; - nvg__vset(dst, rx1, ry1, ru,1); dst++; - - } - return dst; -} - -static NVGvertex* nvg__bevelJoin(NVGvertex* dst, NVGpoint* p0, NVGpoint* p1, - float lw, float rw, float lu, float ru, float fringe) -{ - float rx0,ry0,rx1,ry1; - float lx0,ly0,lx1,ly1; - float dlx0 = p0->dy; - float dly0 = -p0->dx; - float dlx1 = p1->dy; - float dly1 = -p1->dx; - NVG_NOTUSED(fringe); - - if (p1->flags & NVG_PT_LEFT) { - nvg__chooseBevel(p1->flags & NVG_PR_INNERBEVEL, p0, p1, lw, &lx0,&ly0, &lx1,&ly1); - - nvg__vset(dst, lx0, ly0, lu,1); dst++; - nvg__vset(dst, p1->x - dlx0*rw, p1->y - dly0*rw, ru,1); dst++; - - if (p1->flags & NVG_PT_BEVEL) { - nvg__vset(dst, lx0, ly0, lu,1); dst++; - nvg__vset(dst, p1->x - dlx0*rw, p1->y - dly0*rw, ru,1); dst++; - - nvg__vset(dst, lx1, ly1, lu,1); dst++; - nvg__vset(dst, p1->x - dlx1*rw, p1->y - dly1*rw, ru,1); dst++; - } else { - rx0 = p1->x - p1->dmx * rw; - ry0 = p1->y - p1->dmy * rw; - - nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++; - nvg__vset(dst, p1->x - dlx0*rw, p1->y - dly0*rw, ru,1); dst++; - - nvg__vset(dst, rx0, ry0, ru,1); dst++; - nvg__vset(dst, rx0, ry0, ru,1); dst++; - - nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++; - nvg__vset(dst, p1->x - dlx1*rw, p1->y - dly1*rw, ru,1); dst++; - } - - nvg__vset(dst, lx1, ly1, lu,1); dst++; - nvg__vset(dst, p1->x - dlx1*rw, p1->y - dly1*rw, ru,1); dst++; - - } else { - nvg__chooseBevel(p1->flags & NVG_PR_INNERBEVEL, p0, p1, -rw, &rx0,&ry0, &rx1,&ry1); - - nvg__vset(dst, p1->x + dlx0*lw, p1->y + dly0*lw, lu,1); dst++; - nvg__vset(dst, rx0, ry0, ru,1); dst++; - - if (p1->flags & NVG_PT_BEVEL) { - nvg__vset(dst, p1->x + dlx0*lw, p1->y + dly0*lw, lu,1); dst++; - nvg__vset(dst, rx0, ry0, ru,1); dst++; - - nvg__vset(dst, p1->x + dlx1*lw, p1->y + dly1*lw, lu,1); dst++; - nvg__vset(dst, rx1, ry1, ru,1); dst++; - } else { - lx0 = p1->x + p1->dmx * lw; - ly0 = p1->y + p1->dmy * lw; - - nvg__vset(dst, p1->x + dlx0*lw, p1->y + dly0*lw, lu,1); dst++; - nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++; - - nvg__vset(dst, lx0, ly0, lu,1); dst++; - nvg__vset(dst, lx0, ly0, lu,1); dst++; - - nvg__vset(dst, p1->x + dlx1*lw, p1->y + dly1*lw, lu,1); dst++; - nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++; - } - - nvg__vset(dst, p1->x + dlx1*lw, p1->y + dly1*lw, lu,1); dst++; - nvg__vset(dst, rx1, ry1, ru,1); dst++; - } - - return dst; -} - -static NVGvertex* nvg__buttCapStart(NVGvertex* dst, NVGpoint* p, - float dx, float dy, float w, float d, float aa) -{ - float px = p->x - dx*d; - float py = p->y - dy*d; - float dlx = dy; - float dly = -dx; - nvg__vset(dst, px + dlx*w - dx*aa, py + dly*w - dy*aa, 0,0); dst++; - nvg__vset(dst, px - dlx*w - dx*aa, py - dly*w - dy*aa, 1,0); dst++; - nvg__vset(dst, px + dlx*w, py + dly*w, 0,1); dst++; - nvg__vset(dst, px - dlx*w, py - dly*w, 1,1); dst++; - return dst; -} - -static NVGvertex* nvg__buttCapEnd(NVGvertex* dst, NVGpoint* p, - float dx, float dy, float w, float d, float aa) -{ - float px = p->x + dx*d; - float py = p->y + dy*d; - float dlx = dy; - float dly = -dx; - nvg__vset(dst, px + dlx*w, py + dly*w, 0,1); dst++; - nvg__vset(dst, px - dlx*w, py - dly*w, 1,1); dst++; - nvg__vset(dst, px + dlx*w + dx*aa, py + dly*w + dy*aa, 0,0); dst++; - nvg__vset(dst, px - dlx*w + dx*aa, py - dly*w + dy*aa, 1,0); dst++; - return dst; -} - - -static NVGvertex* nvg__roundCapStart(NVGvertex* dst, NVGpoint* p, - float dx, float dy, float w, int ncap, float aa) -{ - int i; - float px = p->x; - float py = p->y; - float dlx = dy; - float dly = -dx; - NVG_NOTUSED(aa); - for (i = 0; i < ncap; i++) { - float a = i/(float)(ncap-1)*NVG_PI; - float ax = cosf(a) * w, ay = sinf(a) * w; - nvg__vset(dst, px - dlx*ax - dx*ay, py - dly*ax - dy*ay, 0,1); dst++; - nvg__vset(dst, px, py, 0.5f,1); dst++; - } - nvg__vset(dst, px + dlx*w, py + dly*w, 0,1); dst++; - nvg__vset(dst, px - dlx*w, py - dly*w, 1,1); dst++; - return dst; -} - -static NVGvertex* nvg__roundCapEnd(NVGvertex* dst, NVGpoint* p, - float dx, float dy, float w, int ncap, float aa) -{ - int i; - float px = p->x; - float py = p->y; - float dlx = dy; - float dly = -dx; - NVG_NOTUSED(aa); - nvg__vset(dst, px + dlx*w, py + dly*w, 0,1); dst++; - nvg__vset(dst, px - dlx*w, py - dly*w, 1,1); dst++; - for (i = 0; i < ncap; i++) { - float a = i/(float)(ncap-1)*NVG_PI; - float ax = cosf(a) * w, ay = sinf(a) * w; - nvg__vset(dst, px, py, 0.5f,1); dst++; - nvg__vset(dst, px - dlx*ax + dx*ay, py - dly*ax + dy*ay, 0,1); dst++; - } - return dst; -} - - -static void nvg__calculateJoins(NVGcontext* ctx, float w, int lineJoin, float miterLimit) -{ - NVGpathCache* cache = ctx->cache; - int i, j; - float iw = 0.0f; - - if (w > 0.0f) iw = 1.0f / w; - - // Calculate which joins needs extra vertices to append, and gather vertex count. - for (i = 0; i < cache->npaths; i++) { - NVGpath* path = &cache->paths[i]; - NVGpoint* pts = &cache->points[path->first]; - NVGpoint* p0 = &pts[path->count-1]; - NVGpoint* p1 = &pts[0]; - int nleft = 0; - - path->nbevel = 0; - - for (j = 0; j < path->count; j++) { - float dlx0, dly0, dlx1, dly1, dmr2, cross, limit; - dlx0 = p0->dy; - dly0 = -p0->dx; - dlx1 = p1->dy; - dly1 = -p1->dx; - // Calculate extrusions - p1->dmx = (dlx0 + dlx1) * 0.5f; - p1->dmy = (dly0 + dly1) * 0.5f; - dmr2 = p1->dmx*p1->dmx + p1->dmy*p1->dmy; - if (dmr2 > 0.000001f) { - float scale = 1.0f / dmr2; - if (scale > 600.0f) { - scale = 600.0f; - } - p1->dmx *= scale; - p1->dmy *= scale; - } - - // Clear flags, but keep the corner. - p1->flags = (p1->flags & NVG_PT_CORNER) ? NVG_PT_CORNER : 0; - - // Keep track of left turns. - cross = p1->dx * p0->dy - p0->dx * p1->dy; - if (cross > 0.0f) { - nleft++; - p1->flags |= NVG_PT_LEFT; - } - - // Calculate if we should use bevel or miter for inner join. - limit = nvg__maxf(1.01f, nvg__minf(p0->len, p1->len) * iw); - if ((dmr2 * limit*limit) < 1.0f) - p1->flags |= NVG_PR_INNERBEVEL; - - // Check to see if the corner needs to be beveled. - if (p1->flags & NVG_PT_CORNER) { - if ((dmr2 * miterLimit*miterLimit) < 1.0f || lineJoin == NVG_BEVEL || lineJoin == NVG_ROUND) { - p1->flags |= NVG_PT_BEVEL; - } - } - - if ((p1->flags & (NVG_PT_BEVEL | NVG_PR_INNERBEVEL)) != 0) - path->nbevel++; - - p0 = p1++; - } - - path->convex = (nleft == path->count) ? 1 : 0; - } -} - - -static int nvg__expandStroke(NVGcontext* ctx, float w, int lineCap, int lineJoin, float miterLimit) -{ - NVGpathCache* cache = ctx->cache; - NVGvertex* verts; - NVGvertex* dst; - int cverts, i, j; - float aa = ctx->fringeWidth; - int ncap = nvg__curveDivs(w, NVG_PI, ctx->tessTol); // Calculate divisions per half circle. - - nvg__calculateJoins(ctx, w, lineJoin, miterLimit); - - // Calculate max vertex usage. - cverts = 0; - for (i = 0; i < cache->npaths; i++) { - NVGpath* path = &cache->paths[i]; - int loop = (path->closed == 0) ? 0 : 1; - if (lineJoin == NVG_ROUND) - cverts += (path->count + path->nbevel*(ncap+2) + 1) * 2; // plus one for loop - else - cverts += (path->count + path->nbevel*5 + 1) * 2; // plus one for loop - if (loop == 0) { - // space for caps - if (lineCap == NVG_ROUND) { - cverts += (ncap*2 + 2)*2; - } else { - cverts += (3+3)*2; - } - } - } - - verts = nvg__allocTempVerts(ctx, cverts); - if (verts == NULL) return 0; - - for (i = 0; i < cache->npaths; i++) { - NVGpath* path = &cache->paths[i]; - NVGpoint* pts = &cache->points[path->first]; - NVGpoint* p0; - NVGpoint* p1; - int s, e, loop; - float dx, dy; - - path->fill = 0; - path->nfill = 0; - - // Calculate fringe or stroke - loop = (path->closed == 0) ? 0 : 1; - dst = verts; - path->stroke = dst; - - if (loop) { - // Looping - p0 = &pts[path->count-1]; - p1 = &pts[0]; - s = 0; - e = path->count; - } else { - // Add cap - p0 = &pts[0]; - p1 = &pts[1]; - s = 1; - e = path->count-1; - } - - if (loop == 0) { - // Add cap - dx = p1->x - p0->x; - dy = p1->y - p0->y; - nvg__normalize(&dx, &dy); - if (lineCap == NVG_BUTT) - dst = nvg__buttCapStart(dst, p0, dx, dy, w, -aa*0.5f, aa); - else if (lineCap == NVG_BUTT || lineCap == NVG_SQUARE) - dst = nvg__buttCapStart(dst, p0, dx, dy, w, w-aa, aa); - else if (lineCap == NVG_ROUND) - dst = nvg__roundCapStart(dst, p0, dx, dy, w, ncap, aa); - } - - for (j = s; j < e; ++j) { - if ((p1->flags & (NVG_PT_BEVEL | NVG_PR_INNERBEVEL)) != 0) { - if (lineJoin == NVG_ROUND) { - dst = nvg__roundJoin(dst, p0, p1, w, w, 0, 1, ncap, aa); - } else { - dst = nvg__bevelJoin(dst, p0, p1, w, w, 0, 1, aa); - } - } else { - nvg__vset(dst, p1->x + (p1->dmx * w), p1->y + (p1->dmy * w), 0,1); dst++; - nvg__vset(dst, p1->x - (p1->dmx * w), p1->y - (p1->dmy * w), 1,1); dst++; - } - p0 = p1++; - } - - if (loop) { - // Loop it - nvg__vset(dst, verts[0].x, verts[0].y, 0,1); dst++; - nvg__vset(dst, verts[1].x, verts[1].y, 1,1); dst++; - } else { - // Add cap - dx = p1->x - p0->x; - dy = p1->y - p0->y; - nvg__normalize(&dx, &dy); - if (lineCap == NVG_BUTT) - dst = nvg__buttCapEnd(dst, p1, dx, dy, w, -aa*0.5f, aa); - else if (lineCap == NVG_BUTT || lineCap == NVG_SQUARE) - dst = nvg__buttCapEnd(dst, p1, dx, dy, w, w-aa, aa); - else if (lineCap == NVG_ROUND) - dst = nvg__roundCapEnd(dst, p1, dx, dy, w, ncap, aa); - } - - path->nstroke = (int)(dst - verts); - - verts = dst; - } - - return 1; -} - -static int nvg__expandFill(NVGcontext* ctx, float w, int lineJoin, float miterLimit) -{ - NVGpathCache* cache = ctx->cache; - NVGvertex* verts; - NVGvertex* dst; - int cverts, convex, i, j; - float aa = ctx->fringeWidth; - int fringe = w > 0.0f; - - nvg__calculateJoins(ctx, w, lineJoin, miterLimit); - - // Calculate max vertex usage. - cverts = 0; - for (i = 0; i < cache->npaths; i++) { - NVGpath* path = &cache->paths[i]; - cverts += path->count + path->nbevel + 1; - if (fringe) - cverts += (path->count + path->nbevel*5 + 1) * 2; // plus one for loop - } - - verts = nvg__allocTempVerts(ctx, cverts); - if (verts == NULL) return 0; - - convex = cache->npaths == 1 && cache->paths[0].convex; - - for (i = 0; i < cache->npaths; i++) { - NVGpath* path = &cache->paths[i]; - NVGpoint* pts = &cache->points[path->first]; - NVGpoint* p0; - NVGpoint* p1; - float rw, lw, woff; - float ru, lu; - - // Calculate shape vertices. - woff = 0.5f*aa; - dst = verts; - path->fill = dst; - - if (fringe) { - // Looping - p0 = &pts[path->count-1]; - p1 = &pts[0]; - for (j = 0; j < path->count; ++j) { - if (p1->flags & NVG_PT_BEVEL) { - float dlx0 = p0->dy; - float dly0 = -p0->dx; - float dlx1 = p1->dy; - float dly1 = -p1->dx; - if (p1->flags & NVG_PT_LEFT) { - float lx = p1->x + p1->dmx * woff; - float ly = p1->y + p1->dmy * woff; - nvg__vset(dst, lx, ly, 0.5f,1); dst++; - } else { - float lx0 = p1->x + dlx0 * woff; - float ly0 = p1->y + dly0 * woff; - float lx1 = p1->x + dlx1 * woff; - float ly1 = p1->y + dly1 * woff; - nvg__vset(dst, lx0, ly0, 0.5f,1); dst++; - nvg__vset(dst, lx1, ly1, 0.5f,1); dst++; - } - } else { - nvg__vset(dst, p1->x + (p1->dmx * woff), p1->y + (p1->dmy * woff), 0.5f,1); dst++; - } - p0 = p1++; - } - } else { - for (j = 0; j < path->count; ++j) { - nvg__vset(dst, pts[j].x, pts[j].y, 0.5f,1); - dst++; - } - } - - path->nfill = (int)(dst - verts); - verts = dst; - - // Calculate fringe - if (fringe) { - lw = w + woff; - rw = w - woff; - lu = 0; - ru = 1; - dst = verts; - path->stroke = dst; - - // Create only half a fringe for convex shapes so that - // the shape can be rendered without stenciling. - if (convex) { - lw = woff; // This should generate the same vertex as fill inset above. - lu = 0.5f; // Set outline fade at middle. - } - - // Looping - p0 = &pts[path->count-1]; - p1 = &pts[0]; - - for (j = 0; j < path->count; ++j) { - if ((p1->flags & (NVG_PT_BEVEL | NVG_PR_INNERBEVEL)) != 0) { - dst = nvg__bevelJoin(dst, p0, p1, lw, rw, lu, ru, ctx->fringeWidth); - } else { - nvg__vset(dst, p1->x + (p1->dmx * lw), p1->y + (p1->dmy * lw), lu,1); dst++; - nvg__vset(dst, p1->x - (p1->dmx * rw), p1->y - (p1->dmy * rw), ru,1); dst++; - } - p0 = p1++; - } - - // Loop it - nvg__vset(dst, verts[0].x, verts[0].y, lu,1); dst++; - nvg__vset(dst, verts[1].x, verts[1].y, ru,1); dst++; - - path->nstroke = (int)(dst - verts); - verts = dst; - } else { - path->stroke = NULL; - path->nstroke = 0; - } - } - - return 1; -} - - -// Draw -void nvgBeginPath(NVGcontext* ctx) -{ - ctx->ncommands = 0; - nvg__clearPathCache(ctx); -} - -void nvgMoveTo(NVGcontext* ctx, float x, float y) -{ - float vals[] = { NVG_MOVETO, x, y }; - nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals)); -} - -void nvgLineTo(NVGcontext* ctx, float x, float y) -{ - float vals[] = { NVG_LINETO, x, y }; - nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals)); -} - -void nvgBezierTo(NVGcontext* ctx, float c1x, float c1y, float c2x, float c2y, float x, float y) -{ - float vals[] = { NVG_BEZIERTO, c1x, c1y, c2x, c2y, x, y }; - nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals)); -} - -void nvgQuadTo(NVGcontext* ctx, float cx, float cy, float x, float y) -{ - float x0 = ctx->commandx; - float y0 = ctx->commandy; - float vals[] = { NVG_BEZIERTO, - x0 + 2.0f/3.0f*(cx - x0), y0 + 2.0f/3.0f*(cy - y0), - x + 2.0f/3.0f*(cx - x), y + 2.0f/3.0f*(cy - y), - x, y }; - nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals)); -} - -void nvgArcTo(NVGcontext* ctx, float x1, float y1, float x2, float y2, float radius) -{ - float x0 = ctx->commandx; - float y0 = ctx->commandy; - float dx0,dy0, dx1,dy1, a, d, cx,cy, a0,a1; - int dir; - - if (ctx->ncommands == 0) { - return; - } - - // Handle degenerate cases. - if (nvg__ptEquals(x0,y0, x1,y1, ctx->distTol) || - nvg__ptEquals(x1,y1, x2,y2, ctx->distTol) || - nvg__distPtSeg(x1,y1, x0,y0, x2,y2) < ctx->distTol*ctx->distTol || - radius < ctx->distTol) { - nvgLineTo(ctx, x1,y1); - return; - } - - // Calculate tangential circle to lines (x0,y0)-(x1,y1) and (x1,y1)-(x2,y2). - dx0 = x0-x1; - dy0 = y0-y1; - dx1 = x2-x1; - dy1 = y2-y1; - nvg__normalize(&dx0,&dy0); - nvg__normalize(&dx1,&dy1); - a = nvg__acosf(dx0*dx1 + dy0*dy1); - d = radius / nvg__tanf(a/2.0f); - -// printf("a=%f° d=%f\n", a/NVG_PI*180.0f, d); - - if (d > 10000.0f) { - nvgLineTo(ctx, x1,y1); - return; - } - - if (nvg__cross(dx0,dy0, dx1,dy1) > 0.0f) { - cx = x1 + dx0*d + dy0*radius; - cy = y1 + dy0*d + -dx0*radius; - a0 = nvg__atan2f(dx0, -dy0); - a1 = nvg__atan2f(-dx1, dy1); - dir = NVG_CW; -// printf("CW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f); - } else { - cx = x1 + dx0*d + -dy0*radius; - cy = y1 + dy0*d + dx0*radius; - a0 = nvg__atan2f(-dx0, dy0); - a1 = nvg__atan2f(dx1, -dy1); - dir = NVG_CCW; -// printf("CCW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f); - } - - nvgArc(ctx, cx, cy, radius, a0, a1, dir); -} - -void nvgClosePath(NVGcontext* ctx) -{ - float vals[] = { NVG_CLOSE }; - nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals)); -} - -void nvgPathWinding(NVGcontext* ctx, int dir) -{ - float vals[] = { NVG_WINDING, (float)dir }; - nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals)); -} - -void nvgArc(NVGcontext* ctx, float cx, float cy, float r, float a0, float a1, int dir) -{ - float a = 0, da = 0, hda = 0, kappa = 0; - float dx = 0, dy = 0, x = 0, y = 0, tanx = 0, tany = 0; - float px = 0, py = 0, ptanx = 0, ptany = 0; - float vals[3 + 5*7 + 100]; - int i, ndivs, nvals; - int move = ctx->ncommands > 0 ? NVG_LINETO : NVG_MOVETO; - - // Clamp angles - da = a1 - a0; - if (dir == NVG_CW) { - if (nvg__absf(da) >= NVG_PI*2) { - da = NVG_PI*2; - } else { - while (da < 0.0f) da += NVG_PI*2; - } - } else { - if (nvg__absf(da) >= NVG_PI*2) { - da = -NVG_PI*2; - } else { - while (da > 0.0f) da -= NVG_PI*2; - } - } - - // Split arc into max 90 degree segments. - ndivs = nvg__maxi(1, nvg__mini((int)(nvg__absf(da) / (NVG_PI*0.5f) + 0.5f), 5)); - hda = (da / (float)ndivs) / 2.0f; - kappa = nvg__absf(4.0f / 3.0f * (1.0f - nvg__cosf(hda)) / nvg__sinf(hda)); - - if (dir == NVG_CCW) - kappa = -kappa; - - nvals = 0; - for (i = 0; i <= ndivs; i++) { - a = a0 + da * (i/(float)ndivs); - dx = nvg__cosf(a); - dy = nvg__sinf(a); - x = cx + dx*r; - y = cy + dy*r; - tanx = -dy*r*kappa; - tany = dx*r*kappa; - - if (i == 0) { - vals[nvals++] = (float)move; - vals[nvals++] = x; - vals[nvals++] = y; - } else { - vals[nvals++] = NVG_BEZIERTO; - vals[nvals++] = px+ptanx; - vals[nvals++] = py+ptany; - vals[nvals++] = x-tanx; - vals[nvals++] = y-tany; - vals[nvals++] = x; - vals[nvals++] = y; - } - px = x; - py = y; - ptanx = tanx; - ptany = tany; - } - - nvg__appendCommands(ctx, vals, nvals); -} - -void nvgRect(NVGcontext* ctx, float x, float y, float w, float h) -{ - float vals[] = { - NVG_MOVETO, x,y, - NVG_LINETO, x,y+h, - NVG_LINETO, x+w,y+h, - NVG_LINETO, x+w,y, - NVG_CLOSE - }; - nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals)); -} - -void nvgRoundedRect(NVGcontext* ctx, float x, float y, float w, float h, float r) -{ - nvgRoundedRectVarying(ctx, x, y, w, h, r, r, r, r); -} - -void nvgRoundedRectVarying(NVGcontext* ctx, float x, float y, float w, float h, float radTopLeft, float radTopRight, float radBottomRight, float radBottomLeft) -{ - if(radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) { - nvgRect(ctx, x, y, w, h); - return; - } else { - float halfw = nvg__absf(w)*0.5f; - float halfh = nvg__absf(h)*0.5f; - float rxBL = nvg__minf(radBottomLeft, halfw) * nvg__signf(w), ryBL = nvg__minf(radBottomLeft, halfh) * nvg__signf(h); - float rxBR = nvg__minf(radBottomRight, halfw) * nvg__signf(w), ryBR = nvg__minf(radBottomRight, halfh) * nvg__signf(h); - float rxTR = nvg__minf(radTopRight, halfw) * nvg__signf(w), ryTR = nvg__minf(radTopRight, halfh) * nvg__signf(h); - float rxTL = nvg__minf(radTopLeft, halfw) * nvg__signf(w), ryTL = nvg__minf(radTopLeft, halfh) * nvg__signf(h); - float vals[] = { - NVG_MOVETO, x, y + ryTL, - NVG_LINETO, x, y + h - ryBL, - NVG_BEZIERTO, x, y + h - ryBL*(1 - NVG_KAPPA90), x + rxBL*(1 - NVG_KAPPA90), y + h, x + rxBL, y + h, - NVG_LINETO, x + w - rxBR, y + h, - NVG_BEZIERTO, x + w - rxBR*(1 - NVG_KAPPA90), y + h, x + w, y + h - ryBR*(1 - NVG_KAPPA90), x + w, y + h - ryBR, - NVG_LINETO, x + w, y + ryTR, - NVG_BEZIERTO, x + w, y + ryTR*(1 - NVG_KAPPA90), x + w - rxTR*(1 - NVG_KAPPA90), y, x + w - rxTR, y, - NVG_LINETO, x + rxTL, y, - NVG_BEZIERTO, x + rxTL*(1 - NVG_KAPPA90), y, x, y + ryTL*(1 - NVG_KAPPA90), x, y + ryTL, - NVG_CLOSE - }; - nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals)); - } -} - -void nvgEllipse(NVGcontext* ctx, float cx, float cy, float rx, float ry) -{ - float vals[] = { - NVG_MOVETO, cx-rx, cy, - NVG_BEZIERTO, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry, - NVG_BEZIERTO, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy, - NVG_BEZIERTO, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry, - NVG_BEZIERTO, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy, - NVG_CLOSE - }; - nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals)); -} - -void nvgCircle(NVGcontext* ctx, float cx, float cy, float r) -{ - nvgEllipse(ctx, cx,cy, r,r); -} - -void nvgDebugDumpPathCache(NVGcontext* ctx) -{ - const NVGpath* path; - int i, j; - - printf("Dumping %d cached paths\n", ctx->cache->npaths); - for (i = 0; i < ctx->cache->npaths; i++) { - path = &ctx->cache->paths[i]; - printf(" - Path %d\n", i); - if (path->nfill) { - printf(" - fill: %d\n", path->nfill); - for (j = 0; j < path->nfill; j++) - printf("%f\t%f\n", path->fill[j].x, path->fill[j].y); - } - if (path->nstroke) { - printf(" - stroke: %d\n", path->nstroke); - for (j = 0; j < path->nstroke; j++) - printf("%f\t%f\n", path->stroke[j].x, path->stroke[j].y); - } - } -} - -void nvgFill(NVGcontext* ctx) -{ - NVGstate* state = nvg__getState(ctx); - const NVGpath* path; - NVGpaint fillPaint = state->fill; - int i; - - nvg__flattenPaths(ctx); - if (ctx->params.edgeAntiAlias) - nvg__expandFill(ctx, ctx->fringeWidth, NVG_MITER, 2.4f); - else - nvg__expandFill(ctx, 0.0f, NVG_MITER, 2.4f); - - // Apply global alpha - fillPaint.innerColor.a *= state->alpha; - fillPaint.outerColor.a *= state->alpha; - - ctx->params.renderFill(ctx->params.userPtr, &fillPaint, &state->scissor, ctx->fringeWidth, - ctx->cache->bounds, ctx->cache->paths, ctx->cache->npaths); - - // Count triangles - for (i = 0; i < ctx->cache->npaths; i++) { - path = &ctx->cache->paths[i]; - ctx->fillTriCount += path->nfill-2; - ctx->fillTriCount += path->nstroke-2; - ctx->drawCallCount += 2; - } -} - -void nvgStroke(NVGcontext* ctx) -{ - NVGstate* state = nvg__getState(ctx); - float scale = nvg__getAverageScale(state->xform); - float strokeWidth = nvg__clampf(state->strokeWidth * scale, 0.0f, 200.0f); - NVGpaint strokePaint = state->stroke; - const NVGpath* path; - int i; - - if (strokeWidth < ctx->fringeWidth) { - // If the stroke width is less than pixel size, use alpha to emulate coverage. - // Since coverage is area, scale by alpha*alpha. - float alpha = nvg__clampf(strokeWidth / ctx->fringeWidth, 0.0f, 1.0f); - strokePaint.innerColor.a *= alpha*alpha; - strokePaint.outerColor.a *= alpha*alpha; - strokeWidth = ctx->fringeWidth; - } - - // Apply global alpha - strokePaint.innerColor.a *= state->alpha; - strokePaint.outerColor.a *= state->alpha; - - nvg__flattenPaths(ctx); - - if (ctx->params.edgeAntiAlias) - nvg__expandStroke(ctx, strokeWidth*0.5f + ctx->fringeWidth*0.5f, state->lineCap, state->lineJoin, state->miterLimit); - else - nvg__expandStroke(ctx, strokeWidth*0.5f, state->lineCap, state->lineJoin, state->miterLimit); - - ctx->params.renderStroke(ctx->params.userPtr, &strokePaint, &state->scissor, ctx->fringeWidth, - strokeWidth, ctx->cache->paths, ctx->cache->npaths); - - // Count triangles - for (i = 0; i < ctx->cache->npaths; i++) { - path = &ctx->cache->paths[i]; - ctx->strokeTriCount += path->nstroke-2; - ctx->drawCallCount++; - } -} - -// Add fonts -int nvgCreateFont(NVGcontext* ctx, const char* name, const char* path) -{ - return fonsAddFont(ctx->fs, name, path); -} - -int nvgCreateFontMem(NVGcontext* ctx, const char* name, unsigned char* data, int ndata, int freeData) -{ - return fonsAddFontMem(ctx->fs, name, data, ndata, freeData); -} - -int nvgFindFont(NVGcontext* ctx, const char* name) -{ - if (name == NULL) return -1; - return fonsGetFontByName(ctx->fs, name); -} - - -int nvgAddFallbackFontId(NVGcontext* ctx, int baseFont, int fallbackFont) -{ - if(baseFont == -1 || fallbackFont == -1) return 0; - return fonsAddFallbackFont(ctx->fs, baseFont, fallbackFont); -} - -int nvgAddFallbackFont(NVGcontext* ctx, const char* baseFont, const char* fallbackFont) -{ - return nvgAddFallbackFontId(ctx, nvgFindFont(ctx, baseFont), nvgFindFont(ctx, fallbackFont)); -} - -// State setting -void nvgFontSize(NVGcontext* ctx, float size) -{ - NVGstate* state = nvg__getState(ctx); - state->fontSize = size; -} - -void nvgFontBlur(NVGcontext* ctx, float blur) -{ - NVGstate* state = nvg__getState(ctx); - state->fontBlur = blur; -} - -void nvgTextLetterSpacing(NVGcontext* ctx, float spacing) -{ - NVGstate* state = nvg__getState(ctx); - state->letterSpacing = spacing; -} - -void nvgTextLineHeight(NVGcontext* ctx, float lineHeight) -{ - NVGstate* state = nvg__getState(ctx); - state->lineHeight = lineHeight; -} - -void nvgTextAlign(NVGcontext* ctx, int align) -{ - NVGstate* state = nvg__getState(ctx); - state->textAlign = align; -} - -void nvgFontFaceId(NVGcontext* ctx, int font) -{ - NVGstate* state = nvg__getState(ctx); - state->fontId = font; -} - -void nvgFontFace(NVGcontext* ctx, const char* font) -{ - NVGstate* state = nvg__getState(ctx); - state->fontId = fonsGetFontByName(ctx->fs, font); -} - -static float nvg__quantize(float a, float d) -{ - return ((int)(a / d + 0.5f)) * d; -} - -static float nvg__getFontScale(NVGstate* state) -{ - return nvg__minf(nvg__quantize(nvg__getAverageScale(state->xform), 0.01f), 4.0f); -} - -static void nvg__flushTextTexture(NVGcontext* ctx) -{ - int dirty[4]; - - if (fonsValidateTexture(ctx->fs, dirty)) { - int fontImage = ctx->fontImages[ctx->fontImageIdx]; - // Update texture - if (fontImage != 0) { - int iw, ih; - const unsigned char* data = fonsGetTextureData(ctx->fs, &iw, &ih); - int x = dirty[0]; - int y = dirty[1]; - int w = dirty[2] - dirty[0]; - int h = dirty[3] - dirty[1]; - ctx->params.renderUpdateTexture(ctx->params.userPtr, fontImage, x,y, w,h, data); - } - } -} - -static int nvg__allocTextAtlas(NVGcontext* ctx) -{ - int iw, ih; - nvg__flushTextTexture(ctx); - if (ctx->fontImageIdx >= NVG_MAX_FONTIMAGES-1) - return 0; - // if next fontImage already have a texture - if (ctx->fontImages[ctx->fontImageIdx+1] != 0) - nvgImageSize(ctx, ctx->fontImages[ctx->fontImageIdx+1], &iw, &ih); - else { // calculate the new font image size and create it. - nvgImageSize(ctx, ctx->fontImages[ctx->fontImageIdx], &iw, &ih); - if (iw > ih) - ih *= 2; - else - iw *= 2; - if (iw > NVG_MAX_FONTIMAGE_SIZE || ih > NVG_MAX_FONTIMAGE_SIZE) - iw = ih = NVG_MAX_FONTIMAGE_SIZE; - ctx->fontImages[ctx->fontImageIdx+1] = ctx->params.renderCreateTexture(ctx->params.userPtr, NVG_TEXTURE_ALPHA, iw, ih, 0, NULL); - } - ++ctx->fontImageIdx; - fonsResetAtlas(ctx->fs, iw, ih); - return 1; -} - -static void nvg__renderText(NVGcontext* ctx, NVGvertex* verts, int nverts) -{ - NVGstate* state = nvg__getState(ctx); - NVGpaint paint = state->fill; - - // Render triangles. - paint.image = ctx->fontImages[ctx->fontImageIdx]; - - // Apply global alpha - paint.innerColor.a *= state->alpha; - paint.outerColor.a *= state->alpha; - - ctx->params.renderTriangles(ctx->params.userPtr, &paint, &state->scissor, verts, nverts); - - ctx->drawCallCount++; - ctx->textTriCount += nverts/3; -} - -float nvgText(NVGcontext* ctx, float x, float y, const char* string, const char* end) -{ - NVGstate* state = nvg__getState(ctx); - FONStextIter iter, prevIter; - FONSquad q; - NVGvertex* verts; - float scale = nvg__getFontScale(state) * ctx->devicePxRatio; - float invscale = 1.0f / scale; - int cverts = 0; - int nverts = 0; - - if (end == NULL) - end = string + strlen(string); - - if (state->fontId == FONS_INVALID) return x; - - fonsSetSize(ctx->fs, state->fontSize*scale); - fonsSetSpacing(ctx->fs, state->letterSpacing*scale); - fonsSetBlur(ctx->fs, state->fontBlur*scale); - fonsSetAlign(ctx->fs, state->textAlign); - fonsSetFont(ctx->fs, state->fontId); - - cverts = nvg__maxi(2, (int)(end - string)) * 6; // conservative estimate. - verts = nvg__allocTempVerts(ctx, cverts); - if (verts == NULL) return x; - - fonsTextIterInit(ctx->fs, &iter, x*scale, y*scale, string, end); - prevIter = iter; - while (fonsTextIterNext(ctx->fs, &iter, &q)) { - float c[4*2]; - if (iter.prevGlyphIndex == -1) { // can not retrieve glyph? - if (!nvg__allocTextAtlas(ctx)) - break; // no memory :( - if (nverts != 0) { - nvg__renderText(ctx, verts, nverts); - nverts = 0; - } - iter = prevIter; - fonsTextIterNext(ctx->fs, &iter, &q); // try again - if (iter.prevGlyphIndex == -1) // still can not find glyph? - break; - } - prevIter = iter; - // Transform corners. - nvgTransformPoint(&c[0],&c[1], state->xform, q.x0*invscale, q.y0*invscale); - nvgTransformPoint(&c[2],&c[3], state->xform, q.x1*invscale, q.y0*invscale); - nvgTransformPoint(&c[4],&c[5], state->xform, q.x1*invscale, q.y1*invscale); - nvgTransformPoint(&c[6],&c[7], state->xform, q.x0*invscale, q.y1*invscale); - // Create triangles - if (nverts+6 <= cverts) { - nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); nverts++; - nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); nverts++; - nvg__vset(&verts[nverts], c[2], c[3], q.s1, q.t0); nverts++; - nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); nverts++; - nvg__vset(&verts[nverts], c[6], c[7], q.s0, q.t1); nverts++; - nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); nverts++; - } - } - - // TODO: add back-end bit to do this just once per frame. - nvg__flushTextTexture(ctx); - - nvg__renderText(ctx, verts, nverts); - - return iter.x; -} - -void nvgTextBox(NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end) -{ - NVGstate* state = nvg__getState(ctx); - NVGtextRow rows[2]; - int nrows = 0, i; - int oldAlign = state->textAlign; - int haling = state->textAlign & (NVG_ALIGN_LEFT | NVG_ALIGN_CENTER | NVG_ALIGN_RIGHT); - int valign = state->textAlign & (NVG_ALIGN_TOP | NVG_ALIGN_MIDDLE | NVG_ALIGN_BOTTOM | NVG_ALIGN_BASELINE); - float lineh = 0; - - if (state->fontId == FONS_INVALID) return; - - nvgTextMetrics(ctx, NULL, NULL, &lineh); - - state->textAlign = NVG_ALIGN_LEFT | valign; - - while ((nrows = nvgTextBreakLines(ctx, string, end, breakRowWidth, rows, 2))) { - for (i = 0; i < nrows; i++) { - NVGtextRow* row = &rows[i]; - if (haling & NVG_ALIGN_LEFT) - nvgText(ctx, x, y, row->start, row->end); - else if (haling & NVG_ALIGN_CENTER) - nvgText(ctx, x + breakRowWidth*0.5f - row->width*0.5f, y, row->start, row->end); - else if (haling & NVG_ALIGN_RIGHT) - nvgText(ctx, x + breakRowWidth - row->width, y, row->start, row->end); - y += lineh * state->lineHeight; - } - string = rows[nrows-1].next; - } - - state->textAlign = oldAlign; -} - -int nvgTextGlyphPositions(NVGcontext* ctx, float x, float y, const char* string, const char* end, NVGglyphPosition* positions, int maxPositions) -{ - NVGstate* state = nvg__getState(ctx); - float scale = nvg__getFontScale(state) * ctx->devicePxRatio; - float invscale = 1.0f / scale; - FONStextIter iter, prevIter; - FONSquad q; - int npos = 0; - - if (state->fontId == FONS_INVALID) return 0; - - if (end == NULL) - end = string + strlen(string); - - if (string == end) - return 0; - - fonsSetSize(ctx->fs, state->fontSize*scale); - fonsSetSpacing(ctx->fs, state->letterSpacing*scale); - fonsSetBlur(ctx->fs, state->fontBlur*scale); - fonsSetAlign(ctx->fs, state->textAlign); - fonsSetFont(ctx->fs, state->fontId); - - fonsTextIterInit(ctx->fs, &iter, x*scale, y*scale, string, end); - prevIter = iter; - while (fonsTextIterNext(ctx->fs, &iter, &q)) { - if (iter.prevGlyphIndex < 0 && nvg__allocTextAtlas(ctx)) { // can not retrieve glyph? - iter = prevIter; - fonsTextIterNext(ctx->fs, &iter, &q); // try again - } - prevIter = iter; - positions[npos].str = iter.str; - positions[npos].x = iter.x * invscale; - positions[npos].minx = nvg__minf(iter.x, q.x0) * invscale; - positions[npos].maxx = nvg__maxf(iter.nextx, q.x1) * invscale; - npos++; - if (npos >= maxPositions) - break; - } - - return npos; -} - -enum NVGcodepointType { - NVG_SPACE, - NVG_NEWLINE, - NVG_CHAR, -}; - -int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, float breakRowWidth, NVGtextRow* rows, int maxRows) -{ - NVGstate* state = nvg__getState(ctx); - float scale = nvg__getFontScale(state) * ctx->devicePxRatio; - float invscale = 1.0f / scale; - FONStextIter iter, prevIter; - FONSquad q; - int nrows = 0; - float rowStartX = 0; - float rowWidth = 0; - float rowMinX = 0; - float rowMaxX = 0; - const char* rowStart = NULL; - const char* rowEnd = NULL; - const char* wordStart = NULL; - float wordStartX = 0; - float wordMinX = 0; - const char* breakEnd = NULL; - float breakWidth = 0; - float breakMaxX = 0; - int type = NVG_SPACE, ptype = NVG_SPACE; - unsigned int pcodepoint = 0; - - if (maxRows == 0) return 0; - if (state->fontId == FONS_INVALID) return 0; - - if (end == NULL) - end = string + strlen(string); - - if (string == end) return 0; - - fonsSetSize(ctx->fs, state->fontSize*scale); - fonsSetSpacing(ctx->fs, state->letterSpacing*scale); - fonsSetBlur(ctx->fs, state->fontBlur*scale); - fonsSetAlign(ctx->fs, state->textAlign); - fonsSetFont(ctx->fs, state->fontId); - - breakRowWidth *= scale; - - fonsTextIterInit(ctx->fs, &iter, 0, 0, string, end); - prevIter = iter; - while (fonsTextIterNext(ctx->fs, &iter, &q)) { - if (iter.prevGlyphIndex < 0 && nvg__allocTextAtlas(ctx)) { // can not retrieve glyph? - iter = prevIter; - fonsTextIterNext(ctx->fs, &iter, &q); // try again - } - prevIter = iter; - switch (iter.codepoint) { - case 9: // \t - case 11: // \v - case 12: // \f - case 32: // space - case 0x00a0: // NBSP - type = NVG_SPACE; - break; - case 10: // \n - type = pcodepoint == 13 ? NVG_SPACE : NVG_NEWLINE; - break; - case 13: // \r - type = pcodepoint == 10 ? NVG_SPACE : NVG_NEWLINE; - break; - case 0x0085: // NEL - type = NVG_NEWLINE; - break; - default: - type = NVG_CHAR; - break; - } - - if (type == NVG_NEWLINE) { - // Always handle new lines. - rows[nrows].start = rowStart != NULL ? rowStart : iter.str; - rows[nrows].end = rowEnd != NULL ? rowEnd : iter.str; - rows[nrows].width = rowWidth * invscale; - rows[nrows].minx = rowMinX * invscale; - rows[nrows].maxx = rowMaxX * invscale; - rows[nrows].next = iter.next; - nrows++; - if (nrows >= maxRows) - return nrows; - // Set null break point - breakEnd = rowStart; - breakWidth = 0.0; - breakMaxX = 0.0; - // Indicate to skip the white space at the beginning of the row. - rowStart = NULL; - rowEnd = NULL; - rowWidth = 0; - rowMinX = rowMaxX = 0; - } else { - if (rowStart == NULL) { - // Skip white space until the beginning of the line - if (type == NVG_CHAR) { - // The current char is the row so far - rowStartX = iter.x; - rowStart = iter.str; - rowEnd = iter.next; - rowWidth = iter.nextx - rowStartX; // q.x1 - rowStartX; - rowMinX = q.x0 - rowStartX; - rowMaxX = q.x1 - rowStartX; - wordStart = iter.str; - wordStartX = iter.x; - wordMinX = q.x0 - rowStartX; - // Set null break point - breakEnd = rowStart; - breakWidth = 0.0; - breakMaxX = 0.0; - } - } else { - float nextWidth = iter.nextx - rowStartX; - - // track last non-white space character - if (type == NVG_CHAR) { - rowEnd = iter.next; - rowWidth = iter.nextx - rowStartX; - rowMaxX = q.x1 - rowStartX; - } - // track last end of a word - if (ptype == NVG_CHAR && type == NVG_SPACE) { - breakEnd = iter.str; - breakWidth = rowWidth; - breakMaxX = rowMaxX; - } - // track last beginning of a word - if (ptype == NVG_SPACE && type == NVG_CHAR) { - wordStart = iter.str; - wordStartX = iter.x; - wordMinX = q.x0 - rowStartX; - } - - // Break to new line when a character is beyond break width. - if (type == NVG_CHAR && nextWidth > breakRowWidth) { - // The run length is too long, need to break to new line. - if (breakEnd == rowStart) { - // The current word is longer than the row length, just break it from here. - rows[nrows].start = rowStart; - rows[nrows].end = iter.str; - rows[nrows].width = rowWidth * invscale; - rows[nrows].minx = rowMinX * invscale; - rows[nrows].maxx = rowMaxX * invscale; - rows[nrows].next = iter.str; - nrows++; - if (nrows >= maxRows) - return nrows; - rowStartX = iter.x; - rowStart = iter.str; - rowEnd = iter.next; - rowWidth = iter.nextx - rowStartX; - rowMinX = q.x0 - rowStartX; - rowMaxX = q.x1 - rowStartX; - wordStart = iter.str; - wordStartX = iter.x; - wordMinX = q.x0 - rowStartX; - } else { - // Break the line from the end of the last word, and start new line from the beginning of the new. - rows[nrows].start = rowStart; - rows[nrows].end = breakEnd; - rows[nrows].width = breakWidth * invscale; - rows[nrows].minx = rowMinX * invscale; - rows[nrows].maxx = breakMaxX * invscale; - rows[nrows].next = wordStart; - nrows++; - if (nrows >= maxRows) - return nrows; - rowStartX = wordStartX; - rowStart = wordStart; - rowEnd = iter.next; - rowWidth = iter.nextx - rowStartX; - rowMinX = wordMinX; - rowMaxX = q.x1 - rowStartX; - // No change to the word start - } - // Set null break point - breakEnd = rowStart; - breakWidth = 0.0; - breakMaxX = 0.0; - } - } - } - - pcodepoint = iter.codepoint; - ptype = type; - } - - // Break the line from the end of the last word, and start new line from the beginning of the new. - if (rowStart != NULL) { - rows[nrows].start = rowStart; - rows[nrows].end = rowEnd; - rows[nrows].width = rowWidth * invscale; - rows[nrows].minx = rowMinX * invscale; - rows[nrows].maxx = rowMaxX * invscale; - rows[nrows].next = end; - nrows++; - } - - return nrows; -} - -float nvgTextBounds(NVGcontext* ctx, float x, float y, const char* string, const char* end, float* bounds) -{ - NVGstate* state = nvg__getState(ctx); - float scale = nvg__getFontScale(state) * ctx->devicePxRatio; - float invscale = 1.0f / scale; - float width; - - if (state->fontId == FONS_INVALID) return 0; - - fonsSetSize(ctx->fs, state->fontSize*scale); - fonsSetSpacing(ctx->fs, state->letterSpacing*scale); - fonsSetBlur(ctx->fs, state->fontBlur*scale); - fonsSetAlign(ctx->fs, state->textAlign); - fonsSetFont(ctx->fs, state->fontId); - - width = fonsTextBounds(ctx->fs, x*scale, y*scale, string, end, bounds); - if (bounds != NULL) { - // Use line bounds for height. - fonsLineBounds(ctx->fs, y*scale, &bounds[1], &bounds[3]); - bounds[0] *= invscale; - bounds[1] *= invscale; - bounds[2] *= invscale; - bounds[3] *= invscale; - } - return width * invscale; -} - -void nvgTextBoxBounds(NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end, float* bounds) -{ - NVGstate* state = nvg__getState(ctx); - NVGtextRow rows[2]; - float scale = nvg__getFontScale(state) * ctx->devicePxRatio; - float invscale = 1.0f / scale; - int nrows = 0, i; - int oldAlign = state->textAlign; - int haling = state->textAlign & (NVG_ALIGN_LEFT | NVG_ALIGN_CENTER | NVG_ALIGN_RIGHT); - int valign = state->textAlign & (NVG_ALIGN_TOP | NVG_ALIGN_MIDDLE | NVG_ALIGN_BOTTOM | NVG_ALIGN_BASELINE); - float lineh = 0, rminy = 0, rmaxy = 0; - float minx, miny, maxx, maxy; - - if (state->fontId == FONS_INVALID) { - if (bounds != NULL) - bounds[0] = bounds[1] = bounds[2] = bounds[3] = 0.0f; - return; - } - - nvgTextMetrics(ctx, NULL, NULL, &lineh); - - state->textAlign = NVG_ALIGN_LEFT | valign; - - minx = maxx = x; - miny = maxy = y; - - fonsSetSize(ctx->fs, state->fontSize*scale); - fonsSetSpacing(ctx->fs, state->letterSpacing*scale); - fonsSetBlur(ctx->fs, state->fontBlur*scale); - fonsSetAlign(ctx->fs, state->textAlign); - fonsSetFont(ctx->fs, state->fontId); - fonsLineBounds(ctx->fs, 0, &rminy, &rmaxy); - rminy *= invscale; - rmaxy *= invscale; - - while ((nrows = nvgTextBreakLines(ctx, string, end, breakRowWidth, rows, 2))) { - for (i = 0; i < nrows; i++) { - NVGtextRow* row = &rows[i]; - float rminx, rmaxx, dx = 0; - // Horizontal bounds - if (haling & NVG_ALIGN_LEFT) - dx = 0; - else if (haling & NVG_ALIGN_CENTER) - dx = breakRowWidth*0.5f - row->width*0.5f; - else if (haling & NVG_ALIGN_RIGHT) - dx = breakRowWidth - row->width; - rminx = x + row->minx + dx; - rmaxx = x + row->maxx + dx; - minx = nvg__minf(minx, rminx); - maxx = nvg__maxf(maxx, rmaxx); - // Vertical bounds. - miny = nvg__minf(miny, y + rminy); - maxy = nvg__maxf(maxy, y + rmaxy); - - y += lineh * state->lineHeight; - } - string = rows[nrows-1].next; - } - - state->textAlign = oldAlign; - - if (bounds != NULL) { - bounds[0] = minx; - bounds[1] = miny; - bounds[2] = maxx; - bounds[3] = maxy; - } -} - -void nvgTextMetrics(NVGcontext* ctx, float* ascender, float* descender, float* lineh) -{ - NVGstate* state = nvg__getState(ctx); - float scale = nvg__getFontScale(state) * ctx->devicePxRatio; - float invscale = 1.0f / scale; - - if (state->fontId == FONS_INVALID) return; - - fonsSetSize(ctx->fs, state->fontSize*scale); - fonsSetSpacing(ctx->fs, state->letterSpacing*scale); - fonsSetBlur(ctx->fs, state->fontBlur*scale); - fonsSetAlign(ctx->fs, state->textAlign); - fonsSetFont(ctx->fs, state->fontId); - - fonsVertMetrics(ctx->fs, ascender, descender, lineh); - if (ascender != NULL) - *ascender *= invscale; - if (descender != NULL) - *descender *= invscale; - if (lineh != NULL) - *lineh *= invscale; -} -// vim: ft=c nu noet ts=4 diff --git a/third_party/nanovg/nanovg.h b/third_party/nanovg/nanovg.h deleted file mode 100644 index bb0d3417a..000000000 --- a/third_party/nanovg/nanovg.h +++ /dev/null @@ -1,681 +0,0 @@ -// -// Copyright (c) 2013 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// - -#ifndef NANOVG_H -#define NANOVG_H - -#ifdef __cplusplus -extern "C" { -#endif - -#define NVG_PI 3.14159265358979323846264338327f - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4201) // nonstandard extension used : nameless struct/union -#endif - -typedef struct NVGcontext NVGcontext; - -struct NVGcolor { - union { - float rgba[4]; - struct { - float r,g,b,a; - }; - }; -}; -typedef struct NVGcolor NVGcolor; - -struct NVGpaint { - float xform[6]; - float extent[2]; - float radius; - float feather; - NVGcolor innerColor; - NVGcolor outerColor; - int image; -}; -typedef struct NVGpaint NVGpaint; - -enum NVGwinding { - NVG_CCW = 1, // Winding for solid shapes - NVG_CW = 2, // Winding for holes -}; - -enum NVGsolidity { - NVG_SOLID = 1, // CCW - NVG_HOLE = 2, // CW -}; - -enum NVGlineCap { - NVG_BUTT, - NVG_ROUND, - NVG_SQUARE, - NVG_BEVEL, - NVG_MITER, -}; - -enum NVGalign { - // Horizontal align - NVG_ALIGN_LEFT = 1<<0, // Default, align text horizontally to left. - NVG_ALIGN_CENTER = 1<<1, // Align text horizontally to center. - NVG_ALIGN_RIGHT = 1<<2, // Align text horizontally to right. - // Vertical align - NVG_ALIGN_TOP = 1<<3, // Align text vertically to top. - NVG_ALIGN_MIDDLE = 1<<4, // Align text vertically to middle. - NVG_ALIGN_BOTTOM = 1<<5, // Align text vertically to bottom. - NVG_ALIGN_BASELINE = 1<<6, // Default, align text vertically to baseline. -}; - -enum NVGblendFactor { - NVG_ZERO = 1<<0, - NVG_ONE = 1<<1, - NVG_SRC_COLOR = 1<<2, - NVG_ONE_MINUS_SRC_COLOR = 1<<3, - NVG_DST_COLOR = 1<<4, - NVG_ONE_MINUS_DST_COLOR = 1<<5, - NVG_SRC_ALPHA = 1<<6, - NVG_ONE_MINUS_SRC_ALPHA = 1<<7, - NVG_DST_ALPHA = 1<<8, - NVG_ONE_MINUS_DST_ALPHA = 1<<9, - NVG_SRC_ALPHA_SATURATE = 1<<10, -}; - -enum NVGcompositeOperation { - NVG_SOURCE_OVER, - NVG_SOURCE_IN, - NVG_SOURCE_OUT, - NVG_ATOP, - NVG_DESTINATION_OVER, - NVG_DESTINATION_IN, - NVG_DESTINATION_OUT, - NVG_DESTINATION_ATOP, - NVG_LIGHTER, - NVG_COPY, - NVG_XOR, -}; - -struct NVGcompositeOperationState { - int srcRGB; - int dstRGB; - int srcAlpha; - int dstAlpha; -}; -typedef struct NVGcompositeOperationState NVGcompositeOperationState; - -struct NVGglyphPosition { - const char* str; // Position of the glyph in the input string. - float x; // The x-coordinate of the logical glyph position. - float minx, maxx; // The bounds of the glyph shape. -}; -typedef struct NVGglyphPosition NVGglyphPosition; - -struct NVGtextRow { - const char* start; // Pointer to the input text where the row starts. - const char* end; // Pointer to the input text where the row ends (one past the last character). - const char* next; // Pointer to the beginning of the next row. - float width; // Logical width of the row. - float minx, maxx; // Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending. -}; -typedef struct NVGtextRow NVGtextRow; - -enum NVGimageFlags { - NVG_IMAGE_GENERATE_MIPMAPS = 1<<0, // Generate mipmaps during creation of the image. - NVG_IMAGE_REPEATX = 1<<1, // Repeat image in X direction. - NVG_IMAGE_REPEATY = 1<<2, // Repeat image in Y direction. - NVG_IMAGE_FLIPY = 1<<3, // Flips (inverses) image in Y direction when rendered. - NVG_IMAGE_PREMULTIPLIED = 1<<4, // Image data has premultiplied alpha. -}; - -// Begin drawing a new frame -// Calls to nanovg drawing API should be wrapped in nvgBeginFrame() & nvgEndFrame() -// nvgBeginFrame() defines the size of the window to render to in relation currently -// set viewport (i.e. glViewport on GL backends). Device pixel ration allows to -// control the rendering on Hi-DPI devices. -// For example, GLFW returns two dimension for an opened window: window size and -// frame buffer size. In that case you would set windowWidth/Height to the window size -// devicePixelRatio to: frameBufferWidth / windowWidth. -void nvgBeginFrame(NVGcontext* ctx, int windowWidth, int windowHeight, float devicePixelRatio); - -// Cancels drawing the current frame. -void nvgCancelFrame(NVGcontext* ctx); - -// Ends drawing flushing remaining render state. -void nvgEndFrame(NVGcontext* ctx); - -// -// Composite operation -// -// The composite operations in NanoVG are modeled after HTML Canvas API, and -// the blend func is based on OpenGL (see corresponding manuals for more info). -// The colors in the blending state have premultiplied alpha. - -// Sets the composite operation. The op parameter should be one of NVGcompositeOperation. -void nvgGlobalCompositeOperation(NVGcontext* ctx, int op); - -// Sets the composite operation with custom pixel arithmetic. The parameters should be one of NVGblendFactor. -void nvgGlobalCompositeBlendFunc(NVGcontext* ctx, int sfactor, int dfactor); - -// Sets the composite operation with custom pixel arithmetic for RGB and alpha components separately. The parameters should be one of NVGblendFactor. -void nvgGlobalCompositeBlendFuncSeparate(NVGcontext* ctx, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha); - -// -// Color utils -// -// Colors in NanoVG are stored as unsigned ints in ABGR format. - -// Returns a color value from red, green, blue values. Alpha will be set to 255 (1.0f). -NVGcolor nvgRGB(unsigned char r, unsigned char g, unsigned char b); - -// Returns a color value from red, green, blue values. Alpha will be set to 1.0f. -NVGcolor nvgRGBf(float r, float g, float b); - - -// Returns a color value from red, green, blue and alpha values. -NVGcolor nvgRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a); - -// Returns a color value from red, green, blue and alpha values. -NVGcolor nvgRGBAf(float r, float g, float b, float a); - - -// Linearly interpolates from color c0 to c1, and returns resulting color value. -NVGcolor nvgLerpRGBA(NVGcolor c0, NVGcolor c1, float u); - -// Sets transparency of a color value. -NVGcolor nvgTransRGBA(NVGcolor c0, unsigned char a); - -// Sets transparency of a color value. -NVGcolor nvgTransRGBAf(NVGcolor c0, float a); - -// Returns color value specified by hue, saturation and lightness. -// HSL values are all in range [0..1], alpha will be set to 255. -NVGcolor nvgHSL(float h, float s, float l); - -// Returns color value specified by hue, saturation and lightness and alpha. -// HSL values are all in range [0..1], alpha in range [0..255] -NVGcolor nvgHSLA(float h, float s, float l, unsigned char a); - -// -// State Handling -// -// NanoVG contains state which represents how paths will be rendered. -// The state contains transform, fill and stroke styles, text and font styles, -// and scissor clipping. - -// Pushes and saves the current render state into a state stack. -// A matching nvgRestore() must be used to restore the state. -void nvgSave(NVGcontext* ctx); - -// Pops and restores current render state. -void nvgRestore(NVGcontext* ctx); - -// Resets current render state to default values. Does not affect the render state stack. -void nvgReset(NVGcontext* ctx); - -// -// Render styles -// -// Fill and stroke render style can be either a solid color or a paint which is a gradient or a pattern. -// Solid color is simply defined as a color value, different kinds of paints can be created -// using nvgLinearGradient(), nvgBoxGradient(), nvgRadialGradient() and nvgImagePattern(). -// -// Current render style can be saved and restored using nvgSave() and nvgRestore(). - -// Sets current stroke style to a solid color. -void nvgStrokeColor(NVGcontext* ctx, NVGcolor color); - -// Sets current stroke style to a paint, which can be a one of the gradients or a pattern. -void nvgStrokePaint(NVGcontext* ctx, NVGpaint paint); - -// Sets current fill style to a solid color. -void nvgFillColor(NVGcontext* ctx, NVGcolor color); - -// Sets current fill style to a paint, which can be a one of the gradients or a pattern. -void nvgFillPaint(NVGcontext* ctx, NVGpaint paint); - -// Sets the miter limit of the stroke style. -// Miter limit controls when a sharp corner is beveled. -void nvgMiterLimit(NVGcontext* ctx, float limit); - -// Sets the stroke width of the stroke style. -void nvgStrokeWidth(NVGcontext* ctx, float size); - -// Sets how the end of the line (cap) is drawn, -// Can be one of: NVG_BUTT (default), NVG_ROUND, NVG_SQUARE. -void nvgLineCap(NVGcontext* ctx, int cap); - -// Sets how sharp path corners are drawn. -// Can be one of NVG_MITER (default), NVG_ROUND, NVG_BEVEL. -void nvgLineJoin(NVGcontext* ctx, int join); - -// Sets the transparency applied to all rendered shapes. -// Already transparent paths will get proportionally more transparent as well. -void nvgGlobalAlpha(NVGcontext* ctx, float alpha); - -// -// Transforms -// -// The paths, gradients, patterns and scissor region are transformed by an transformation -// matrix at the time when they are passed to the API. -// The current transformation matrix is a affine matrix: -// [sx kx tx] -// [ky sy ty] -// [ 0 0 1] -// Where: sx,sy define scaling, kx,ky skewing, and tx,ty translation. -// The last row is assumed to be 0,0,1 and is not stored. -// -// Apart from nvgResetTransform(), each transformation function first creates -// specific transformation matrix and pre-multiplies the current transformation by it. -// -// Current coordinate system (transformation) can be saved and restored using nvgSave() and nvgRestore(). - -// Resets current transform to a identity matrix. -void nvgResetTransform(NVGcontext* ctx); - -// Premultiplies current coordinate system by specified matrix. -// The parameters are interpreted as matrix as follows: -// [a c e] -// [b d f] -// [0 0 1] -void nvgTransform(NVGcontext* ctx, float a, float b, float c, float d, float e, float f); - -// Translates current coordinate system. -void nvgTranslate(NVGcontext* ctx, float x, float y); - -// Rotates current coordinate system. Angle is specified in radians. -void nvgRotate(NVGcontext* ctx, float angle); - -// Skews the current coordinate system along X axis. Angle is specified in radians. -void nvgSkewX(NVGcontext* ctx, float angle); - -// Skews the current coordinate system along Y axis. Angle is specified in radians. -void nvgSkewY(NVGcontext* ctx, float angle); - -// Scales the current coordinate system. -void nvgScale(NVGcontext* ctx, float x, float y); - -// Stores the top part (a-f) of the current transformation matrix in to the specified buffer. -// [a c e] -// [b d f] -// [0 0 1] -// There should be space for 6 floats in the return buffer for the values a-f. -void nvgCurrentTransform(NVGcontext* ctx, float* xform); - - -// The following functions can be used to make calculations on 2x3 transformation matrices. -// A 2x3 matrix is represented as float[6]. - -// Sets the transform to identity matrix. -void nvgTransformIdentity(float* dst); - -// Sets the transform to translation matrix matrix. -void nvgTransformTranslate(float* dst, float tx, float ty); - -// Sets the transform to scale matrix. -void nvgTransformScale(float* dst, float sx, float sy); - -// Sets the transform to rotate matrix. Angle is specified in radians. -void nvgTransformRotate(float* dst, float a); - -// Sets the transform to skew-x matrix. Angle is specified in radians. -void nvgTransformSkewX(float* dst, float a); - -// Sets the transform to skew-y matrix. Angle is specified in radians. -void nvgTransformSkewY(float* dst, float a); - -// Sets the transform to the result of multiplication of two transforms, of A = A*B. -void nvgTransformMultiply(float* dst, const float* src); - -// Sets the transform to the result of multiplication of two transforms, of A = B*A. -void nvgTransformPremultiply(float* dst, const float* src); - -// Sets the destination to inverse of specified transform. -// Returns 1 if the inverse could be calculated, else 0. -int nvgTransformInverse(float* dst, const float* src); - -// Transform a point by given transform. -void nvgTransformPoint(float* dstx, float* dsty, const float* xform, float srcx, float srcy); - -// Converts degrees to radians and vice versa. -float nvgDegToRad(float deg); -float nvgRadToDeg(float rad); - -// -// Images -// -// NanoVG allows you to load jpg, png, psd, tga, pic and gif files to be used for rendering. -// In addition you can upload your own image. The image loading is provided by stb_image. -// The parameter imageFlags is combination of flags defined in NVGimageFlags. - -// Creates image by loading it from the disk from specified file name. -// Returns handle to the image. -int nvgCreateImage(NVGcontext* ctx, const char* filename, int imageFlags); - -// Creates image by loading it from the specified chunk of memory. -// Returns handle to the image. -int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, unsigned char* data, int ndata); - -// Creates image from specified image data. -// Returns handle to the image. -int nvgCreateImageRGBA(NVGcontext* ctx, int w, int h, int imageFlags, const unsigned char* data); - -// Updates image data specified by image handle. -void nvgUpdateImage(NVGcontext* ctx, int image, const unsigned char* data); - -// Returns the dimensions of a created image. -void nvgImageSize(NVGcontext* ctx, int image, int* w, int* h); - -// Deletes created image. -void nvgDeleteImage(NVGcontext* ctx, int image); - -// -// Paints -// -// NanoVG supports four types of paints: linear gradient, box gradient, radial gradient and image pattern. -// These can be used as paints for strokes and fills. - -// Creates and returns a linear gradient. Parameters (sx,sy)-(ex,ey) specify the start and end coordinates -// of the linear gradient, icol specifies the start color and ocol the end color. -// The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint(). -NVGpaint nvgLinearGradient(NVGcontext* ctx, float sx, float sy, float ex, float ey, - NVGcolor icol, NVGcolor ocol); - -// Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering -// drop shadows or highlights for boxes. Parameters (x,y) define the top-left corner of the rectangle, -// (w,h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry -// the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient. -// The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint(). -NVGpaint nvgBoxGradient(NVGcontext* ctx, float x, float y, float w, float h, - float r, float f, NVGcolor icol, NVGcolor ocol); - -// Creates and returns a radial gradient. Parameters (cx,cy) specify the center, inr and outr specify -// the inner and outer radius of the gradient, icol specifies the start color and ocol the end color. -// The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint(). -NVGpaint nvgRadialGradient(NVGcontext* ctx, float cx, float cy, float inr, float outr, - NVGcolor icol, NVGcolor ocol); - -// Creates and returns an image patter. Parameters (ox,oy) specify the left-top location of the image pattern, -// (ex,ey) the size of one image, angle rotation around the top-left corner, image is handle to the image to render. -// The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint(). -NVGpaint nvgImagePattern(NVGcontext* ctx, float ox, float oy, float ex, float ey, - float angle, int image, float alpha); - -// -// Scissoring -// -// Scissoring allows you to clip the rendering into a rectangle. This is useful for various -// user interface cases like rendering a text edit or a timeline. - -// Sets the current scissor rectangle. -// The scissor rectangle is transformed by the current transform. -void nvgScissor(NVGcontext* ctx, float x, float y, float w, float h); - -// Intersects current scissor rectangle with the specified rectangle. -// The scissor rectangle is transformed by the current transform. -// Note: in case the rotation of previous scissor rect differs from -// the current one, the intersection will be done between the specified -// rectangle and the previous scissor rectangle transformed in the current -// transform space. The resulting shape is always rectangle. -void nvgIntersectScissor(NVGcontext* ctx, float x, float y, float w, float h); - -// Reset and disables scissoring. -void nvgResetScissor(NVGcontext* ctx); - -// -// Paths -// -// Drawing a new shape starts with nvgBeginPath(), it clears all the currently defined paths. -// Then you define one or more paths and sub-paths which describe the shape. The are functions -// to draw common shapes like rectangles and circles, and lower level step-by-step functions, -// which allow to define a path curve by curve. -// -// NanoVG uses even-odd fill rule to draw the shapes. Solid shapes should have counter clockwise -// winding and holes should have counter clockwise order. To specify winding of a path you can -// call nvgPathWinding(). This is useful especially for the common shapes, which are drawn CCW. -// -// Finally you can fill the path using current fill style by calling nvgFill(), and stroke it -// with current stroke style by calling nvgStroke(). -// -// The curve segments and sub-paths are transformed by the current transform. - -// Clears the current path and sub-paths. -void nvgBeginPath(NVGcontext* ctx); - -// Starts new sub-path with specified point as first point. -void nvgMoveTo(NVGcontext* ctx, float x, float y); - -// Adds line segment from the last point in the path to the specified point. -void nvgLineTo(NVGcontext* ctx, float x, float y); - -// Adds cubic bezier segment from last point in the path via two control points to the specified point. -void nvgBezierTo(NVGcontext* ctx, float c1x, float c1y, float c2x, float c2y, float x, float y); - -// Adds quadratic bezier segment from last point in the path via a control point to the specified point. -void nvgQuadTo(NVGcontext* ctx, float cx, float cy, float x, float y); - -// Adds an arc segment at the corner defined by the last path point, and two specified points. -void nvgArcTo(NVGcontext* ctx, float x1, float y1, float x2, float y2, float radius); - -// Closes current sub-path with a line segment. -void nvgClosePath(NVGcontext* ctx); - -// Sets the current sub-path winding, see NVGwinding and NVGsolidity. -void nvgPathWinding(NVGcontext* ctx, int dir); - -// Creates new circle arc shaped sub-path. The arc center is at cx,cy, the arc radius is r, -// and the arc is drawn from angle a0 to a1, and swept in direction dir (NVG_CCW, or NVG_CW). -// Angles are specified in radians. -void nvgArc(NVGcontext* ctx, float cx, float cy, float r, float a0, float a1, int dir); - -// Creates new rectangle shaped sub-path. -void nvgRect(NVGcontext* ctx, float x, float y, float w, float h); - -// Creates new rounded rectangle shaped sub-path. -void nvgRoundedRect(NVGcontext* ctx, float x, float y, float w, float h, float r); - -// Creates new rounded rectangle shaped sub-path with varying radii for each corner. -void nvgRoundedRectVarying(NVGcontext* ctx, float x, float y, float w, float h, float radTopLeft, float radTopRight, float radBottomRight, float radBottomLeft); - -// Creates new ellipse shaped sub-path. -void nvgEllipse(NVGcontext* ctx, float cx, float cy, float rx, float ry); - -// Creates new circle shaped sub-path. -void nvgCircle(NVGcontext* ctx, float cx, float cy, float r); - -// Fills the current path with current fill style. -void nvgFill(NVGcontext* ctx); - -// Fills the current path with current stroke style. -void nvgStroke(NVGcontext* ctx); - - -// -// Text -// -// NanoVG allows you to load .ttf files and use the font to render text. -// -// The appearance of the text can be defined by setting the current text style -// and by specifying the fill color. Common text and font settings such as -// font size, letter spacing and text align are supported. Font blur allows you -// to create simple text effects such as drop shadows. -// -// At render time the font face can be set based on the font handles or name. -// -// Font measure functions return values in local space, the calculations are -// carried in the same resolution as the final rendering. This is done because -// the text glyph positions are snapped to the nearest pixels sharp rendering. -// -// The local space means that values are not rotated or scale as per the current -// transformation. For example if you set font size to 12, which would mean that -// line height is 16, then regardless of the current scaling and rotation, the -// returned line height is always 16. Some measures may vary because of the scaling -// since aforementioned pixel snapping. -// -// While this may sound a little odd, the setup allows you to always render the -// same way regardless of scaling. I.e. following works regardless of scaling: -// -// const char* txt = "Text me up."; -// nvgTextBounds(vg, x,y, txt, NULL, bounds); -// nvgBeginPath(vg); -// nvgRoundedRect(vg, bounds[0],bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]); -// nvgFill(vg); -// -// Note: currently only solid color fill is supported for text. - -// Creates font by loading it from the disk from specified file name. -// Returns handle to the font. -int nvgCreateFont(NVGcontext* ctx, const char* name, const char* filename); - -// Creates font by loading it from the specified memory chunk. -// Returns handle to the font. -int nvgCreateFontMem(NVGcontext* ctx, const char* name, unsigned char* data, int ndata, int freeData); - -// Finds a loaded font of specified name, and returns handle to it, or -1 if the font is not found. -int nvgFindFont(NVGcontext* ctx, const char* name); - -// Adds a fallback font by handle. -int nvgAddFallbackFontId(NVGcontext* ctx, int baseFont, int fallbackFont); - -// Adds a fallback font by name. -int nvgAddFallbackFont(NVGcontext* ctx, const char* baseFont, const char* fallbackFont); - -// Sets the font size of current text style. -void nvgFontSize(NVGcontext* ctx, float size); - -// Sets the blur of current text style. -void nvgFontBlur(NVGcontext* ctx, float blur); - -// Sets the letter spacing of current text style. -void nvgTextLetterSpacing(NVGcontext* ctx, float spacing); - -// Sets the proportional line height of current text style. The line height is specified as multiple of font size. -void nvgTextLineHeight(NVGcontext* ctx, float lineHeight); - -// Sets the text align of current text style, see NVGalign for options. -void nvgTextAlign(NVGcontext* ctx, int align); - -// Sets the font face based on specified id of current text style. -void nvgFontFaceId(NVGcontext* ctx, int font); - -// Sets the font face based on specified name of current text style. -void nvgFontFace(NVGcontext* ctx, const char* font); - -// Draws text string at specified location. If end is specified only the sub-string up to the end is drawn. -float nvgText(NVGcontext* ctx, float x, float y, const char* string, const char* end); - -// Draws multi-line text string at specified location wrapped at the specified width. If end is specified only the sub-string up to the end is drawn. -// White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. -// Words longer than the max width are slit at nearest character (i.e. no hyphenation). -void nvgTextBox(NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end); - -// Measures the specified text string. Parameter bounds should be a pointer to float[4], -// if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax] -// Returns the horizontal advance of the measured text (i.e. where the next character should drawn). -// Measured values are returned in local coordinate space. -float nvgTextBounds(NVGcontext* ctx, float x, float y, const char* string, const char* end, float* bounds); - -// Measures the specified multi-text string. Parameter bounds should be a pointer to float[4], -// if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax] -// Measured values are returned in local coordinate space. -void nvgTextBoxBounds(NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end, float* bounds); - -// Calculates the glyph x positions of the specified text. If end is specified only the sub-string will be used. -// Measured values are returned in local coordinate space. -int nvgTextGlyphPositions(NVGcontext* ctx, float x, float y, const char* string, const char* end, NVGglyphPosition* positions, int maxPositions); - -// Returns the vertical metrics based on the current text style. -// Measured values are returned in local coordinate space. -void nvgTextMetrics(NVGcontext* ctx, float* ascender, float* descender, float* lineh); - -// Breaks the specified text into lines. If end is specified only the sub-string will be used. -// White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. -// Words longer than the max width are slit at nearest character (i.e. no hyphenation). -int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, float breakRowWidth, NVGtextRow* rows, int maxRows); - -// -// Internal Render API -// -enum NVGtexture { - NVG_TEXTURE_ALPHA = 0x01, - NVG_TEXTURE_RGBA = 0x02, -}; - -struct NVGscissor { - float xform[6]; - float extent[2]; -}; -typedef struct NVGscissor NVGscissor; - -struct NVGvertex { - float x,y,u,v; -}; -typedef struct NVGvertex NVGvertex; - -struct NVGpath { - int first; - int count; - unsigned char closed; - int nbevel; - NVGvertex* fill; - int nfill; - NVGvertex* stroke; - int nstroke; - int winding; - int convex; -}; -typedef struct NVGpath NVGpath; - -struct NVGparams { - void* userPtr; - int edgeAntiAlias; - int (*renderCreate)(void* uptr); - int (*renderCreateTexture)(void* uptr, int type, int w, int h, int imageFlags, const unsigned char* data); - int (*renderDeleteTexture)(void* uptr, int image); - int (*renderUpdateTexture)(void* uptr, int image, int x, int y, int w, int h, const unsigned char* data); - int (*renderGetTextureSize)(void* uptr, int image, int* w, int* h); - void (*renderViewport)(void* uptr, int width, int height, float devicePixelRatio); - void (*renderCancel)(void* uptr); - void (*renderFlush)(void* uptr, NVGcompositeOperationState compositeOperation); - void (*renderFill)(void* uptr, NVGpaint* paint, NVGscissor* scissor, float fringe, const float* bounds, const NVGpath* paths, int npaths); - void (*renderStroke)(void* uptr, NVGpaint* paint, NVGscissor* scissor, float fringe, float strokeWidth, const NVGpath* paths, int npaths); - void (*renderTriangles)(void* uptr, NVGpaint* paint, NVGscissor* scissor, const NVGvertex* verts, int nverts); - void (*renderDelete)(void* uptr); -}; -typedef struct NVGparams NVGparams; - -// Constructor and destructor, called by the render back-end. -NVGcontext* nvgCreateInternal(NVGparams* params); -void nvgDeleteInternal(NVGcontext* ctx); - -NVGparams* nvgInternalParams(NVGcontext* ctx); - -// Debug function to dump cached path data. -void nvgDebugDumpPathCache(NVGcontext* ctx); - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -#define NVG_NOTUSED(v) for (;;) { (void)(1 ? (void)0 : ( (void)(v) ) ); break; } - -#ifdef __cplusplus -} -#endif - -#endif // NANOVG_H diff --git a/third_party/nanovg/nanovg_gl.h b/third_party/nanovg/nanovg_gl.h deleted file mode 100644 index c05006732..000000000 --- a/third_party/nanovg/nanovg_gl.h +++ /dev/null @@ -1,1592 +0,0 @@ -// -// Copyright (c) 2009-2013 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// -#ifndef NANOVG_GL_H -#define NANOVG_GL_H - -#ifdef __cplusplus -extern "C" { -#endif - -// Create flags - -enum NVGcreateFlags { - // Flag indicating if geometry based anti-aliasing is used (may not be needed when using MSAA). - NVG_ANTIALIAS = 1<<0, - // Flag indicating if strokes should be drawn using stencil buffer. The rendering will be a little - // slower, but path overlaps (i.e. self-intersecting or sharp turns) will be drawn just once. - NVG_STENCIL_STROKES = 1<<1, - // Flag indicating that additional debug checks are done. - NVG_DEBUG = 1<<2, -}; - -#if defined NANOVG_GL2_IMPLEMENTATION -# define NANOVG_GL2 1 -# define NANOVG_GL_IMPLEMENTATION 1 -#elif defined NANOVG_GL3_IMPLEMENTATION -# define NANOVG_GL3 1 -# define NANOVG_GL_IMPLEMENTATION 1 -# define NANOVG_GL_USE_UNIFORMBUFFER 1 -#elif defined NANOVG_GLES2_IMPLEMENTATION -# define NANOVG_GLES2 1 -# define NANOVG_GL_IMPLEMENTATION 1 -#elif defined NANOVG_GLES3_IMPLEMENTATION -# define NANOVG_GLES3 1 -# define NANOVG_GL_IMPLEMENTATION 1 -#endif - -#define NANOVG_GL_USE_STATE_FILTER (1) - -// Creates NanoVG contexts for different OpenGL (ES) versions. -// Flags should be combination of the create flags above. - -#if defined NANOVG_GL2 - -NVGcontext* nvgCreateGL2(int flags); -void nvgDeleteGL2(NVGcontext* ctx); - -int nvglCreateImageFromHandleGL2(NVGcontext* ctx, GLuint textureId, int w, int h, int flags); -GLuint nvglImageHandleGL2(NVGcontext* ctx, int image); - -#endif - -#if defined NANOVG_GL3 - -NVGcontext* nvgCreateGL3(int flags); -void nvgDeleteGL3(NVGcontext* ctx); - -int nvglCreateImageFromHandleGL3(NVGcontext* ctx, GLuint textureId, int w, int h, int flags); -GLuint nvglImageHandleGL3(NVGcontext* ctx, int image); - -#endif - -#if defined NANOVG_GLES2 - -NVGcontext* nvgCreateGLES2(int flags); -void nvgDeleteGLES2(NVGcontext* ctx); - -int nvglCreateImageFromHandleGLES2(NVGcontext* ctx, GLuint textureId, int w, int h, int flags); -GLuint nvglImageHandleGLES2(NVGcontext* ctx, int image); - -#endif - -#if defined NANOVG_GLES3 - -NVGcontext* nvgCreateGLES3(int flags); -void nvgDeleteGLES3(NVGcontext* ctx); - -int nvglCreateImageFromHandleGLES3(NVGcontext* ctx, GLuint textureId, int w, int h, int flags); -GLuint nvglImageHandleGLES3(NVGcontext* ctx, int image); - -#endif - -// These are additional flags on top of NVGimageFlags. -enum NVGimageFlagsGL { - NVG_IMAGE_NODELETE = 1<<16, // Do not delete GL texture handle. -}; - -#ifdef __cplusplus -} -#endif - -#endif /* NANOVG_GL_H */ - -#ifdef NANOVG_GL_IMPLEMENTATION - -#include -#include -#include -#include -#include "nanovg.h" - -enum GLNVGuniformLoc { - GLNVG_LOC_VIEWSIZE, - GLNVG_LOC_TEX, - GLNVG_LOC_FRAG, - GLNVG_MAX_LOCS -}; - -enum GLNVGshaderType { - NSVG_SHADER_FILLGRAD, - NSVG_SHADER_FILLIMG, - NSVG_SHADER_SIMPLE, - NSVG_SHADER_IMG -}; - -#if NANOVG_GL_USE_UNIFORMBUFFER -enum GLNVGuniformBindings { - GLNVG_FRAG_BINDING = 0, -}; -#endif - -struct GLNVGshader { - GLuint prog; - GLuint frag; - GLuint vert; - GLint loc[GLNVG_MAX_LOCS]; -}; -typedef struct GLNVGshader GLNVGshader; - -struct GLNVGtexture { - int id; - GLuint tex; - int width, height; - int type; - int flags; -}; -typedef struct GLNVGtexture GLNVGtexture; - -enum GLNVGcallType { - GLNVG_NONE = 0, - GLNVG_FILL, - GLNVG_CONVEXFILL, - GLNVG_STROKE, - GLNVG_TRIANGLES, -}; - -struct GLNVGcall { - int type; - int image; - int pathOffset; - int pathCount; - int triangleOffset; - int triangleCount; - int uniformOffset; -}; -typedef struct GLNVGcall GLNVGcall; - -struct GLNVGpath { - int fillOffset; - int fillCount; - int strokeOffset; - int strokeCount; -}; -typedef struct GLNVGpath GLNVGpath; - -struct GLNVGfragUniforms { - #if NANOVG_GL_USE_UNIFORMBUFFER - float scissorMat[12]; // matrices are actually 3 vec4s - float paintMat[12]; - struct NVGcolor innerCol; - struct NVGcolor outerCol; - float scissorExt[2]; - float scissorScale[2]; - float extent[2]; - float radius; - float feather; - float strokeMult; - float strokeThr; - int texType; - int type; - #else - // note: after modifying layout or size of uniform array, - // don't forget to also update the fragment shader source! - #define NANOVG_GL_UNIFORMARRAY_SIZE 11 - union { - struct { - float scissorMat[12]; // matrices are actually 3 vec4s - float paintMat[12]; - struct NVGcolor innerCol; - struct NVGcolor outerCol; - float scissorExt[2]; - float scissorScale[2]; - float extent[2]; - float radius; - float feather; - float strokeMult; - float strokeThr; - float texType; - float type; - }; - float uniformArray[NANOVG_GL_UNIFORMARRAY_SIZE][4]; - }; - #endif -}; -typedef struct GLNVGfragUniforms GLNVGfragUniforms; - -struct GLNVGcontext { - GLNVGshader shader; - GLNVGtexture* textures; - float view[2]; - int ntextures; - int ctextures; - int textureId; - GLuint vertBuf; -#if defined NANOVG_GL3 - GLuint vertArr; -#endif -#if NANOVG_GL_USE_UNIFORMBUFFER - GLuint fragBuf; -#endif - int fragSize; - int flags; - - // Per frame buffers - GLNVGcall* calls; - int ccalls; - int ncalls; - GLNVGpath* paths; - int cpaths; - int npaths; - struct NVGvertex* verts; - int cverts; - int nverts; - unsigned char* uniforms; - int cuniforms; - int nuniforms; - - // cached state - #if NANOVG_GL_USE_STATE_FILTER - GLuint boundTexture; - GLuint stencilMask; - GLenum stencilFunc; - GLint stencilFuncRef; - GLuint stencilFuncMask; - #endif -}; -typedef struct GLNVGcontext GLNVGcontext; - -static int glnvg__maxi(int a, int b) { return a > b ? a : b; } - -#ifdef NANOVG_GLES2 -static unsigned int glnvg__nearestPow2(unsigned int num) -{ - unsigned n = num > 0 ? num - 1 : 0; - n |= n >> 1; - n |= n >> 2; - n |= n >> 4; - n |= n >> 8; - n |= n >> 16; - n++; - return n; -} -#endif - -static void glnvg__bindTexture(GLNVGcontext* gl, GLuint tex) -{ -#if NANOVG_GL_USE_STATE_FILTER - if (gl->boundTexture != tex) { - gl->boundTexture = tex; - glBindTexture(GL_TEXTURE_2D, tex); - } -#else - glBindTexture(GL_TEXTURE_2D, tex); -#endif -} - -static void glnvg__stencilMask(GLNVGcontext* gl, GLuint mask) -{ -#if NANOVG_GL_USE_STATE_FILTER - if (gl->stencilMask != mask) { - gl->stencilMask = mask; - glStencilMask(mask); - } -#else - glStencilMask(mask); -#endif -} - -static void glnvg__stencilFunc(GLNVGcontext* gl, GLenum func, GLint ref, GLuint mask) -{ -#if NANOVG_GL_USE_STATE_FILTER - if ((gl->stencilFunc != func) || - (gl->stencilFuncRef != ref) || - (gl->stencilFuncMask != mask)) { - - gl->stencilFunc = func; - gl->stencilFuncRef = ref; - gl->stencilFuncMask = mask; - glStencilFunc(func, ref, mask); - } -#else - glStencilFunc(func, ref, mask); -#endif -} - -static GLNVGtexture* glnvg__allocTexture(GLNVGcontext* gl) -{ - GLNVGtexture* tex = NULL; - int i; - - for (i = 0; i < gl->ntextures; i++) { - if (gl->textures[i].id == 0) { - tex = &gl->textures[i]; - break; - } - } - if (tex == NULL) { - if (gl->ntextures+1 > gl->ctextures) { - GLNVGtexture* textures; - int ctextures = glnvg__maxi(gl->ntextures+1, 4) + gl->ctextures/2; // 1.5x Overallocate - textures = (GLNVGtexture*)realloc(gl->textures, sizeof(GLNVGtexture)*ctextures); - if (textures == NULL) return NULL; - gl->textures = textures; - gl->ctextures = ctextures; - } - tex = &gl->textures[gl->ntextures++]; - } - - memset(tex, 0, sizeof(*tex)); - tex->id = ++gl->textureId; - - return tex; -} - -static GLNVGtexture* glnvg__findTexture(GLNVGcontext* gl, int id) -{ - int i; - for (i = 0; i < gl->ntextures; i++) - if (gl->textures[i].id == id) - return &gl->textures[i]; - return NULL; -} - -static int glnvg__deleteTexture(GLNVGcontext* gl, int id) -{ - int i; - for (i = 0; i < gl->ntextures; i++) { - if (gl->textures[i].id == id) { - if (gl->textures[i].tex != 0 && (gl->textures[i].flags & NVG_IMAGE_NODELETE) == 0) - glDeleteTextures(1, &gl->textures[i].tex); - memset(&gl->textures[i], 0, sizeof(gl->textures[i])); - return 1; - } - } - return 0; -} - -static void glnvg__dumpShaderError(GLuint shader, const char* name, const char* type) -{ - GLchar str[512+1]; - GLsizei len = 0; - glGetShaderInfoLog(shader, 512, &len, str); - if (len > 512) len = 512; - str[len] = '\0'; - printf("Shader %s/%s error:\n%s\n", name, type, str); -} - -static void glnvg__dumpProgramError(GLuint prog, const char* name) -{ - GLchar str[512+1]; - GLsizei len = 0; - glGetProgramInfoLog(prog, 512, &len, str); - if (len > 512) len = 512; - str[len] = '\0'; - printf("Program %s error:\n%s\n", name, str); -} - -static void glnvg__checkError(GLNVGcontext* gl, const char* str) -{ - GLenum err; - if ((gl->flags & NVG_DEBUG) == 0) return; - err = glGetError(); - if (err != GL_NO_ERROR) { - printf("Error %08x after %s\n", err, str); - return; - } -} - -static int glnvg__createShader(GLNVGshader* shader, const char* name, const char* header, const char* opts, const char* vshader, const char* fshader) -{ - GLint status; - GLuint prog, vert, frag; - const char* str[3]; - str[0] = header; - str[1] = opts != NULL ? opts : ""; - - memset(shader, 0, sizeof(*shader)); - - prog = glCreateProgram(); - vert = glCreateShader(GL_VERTEX_SHADER); - frag = glCreateShader(GL_FRAGMENT_SHADER); - str[2] = vshader; - glShaderSource(vert, 3, str, 0); - str[2] = fshader; - glShaderSource(frag, 3, str, 0); - - glCompileShader(vert); - glGetShaderiv(vert, GL_COMPILE_STATUS, &status); - if (status != GL_TRUE) { - glnvg__dumpShaderError(vert, name, "vert"); - return 0; - } - - glCompileShader(frag); - glGetShaderiv(frag, GL_COMPILE_STATUS, &status); - if (status != GL_TRUE) { - glnvg__dumpShaderError(frag, name, "frag"); - return 0; - } - - glAttachShader(prog, vert); - glAttachShader(prog, frag); - - glBindAttribLocation(prog, 0, "vertex"); - glBindAttribLocation(prog, 1, "tcoord"); - - glLinkProgram(prog); - glGetProgramiv(prog, GL_LINK_STATUS, &status); - if (status != GL_TRUE) { - glnvg__dumpProgramError(prog, name); - return 0; - } - - shader->prog = prog; - shader->vert = vert; - shader->frag = frag; - - return 1; -} - -static void glnvg__deleteShader(GLNVGshader* shader) -{ - if (shader->prog != 0) - glDeleteProgram(shader->prog); - if (shader->vert != 0) - glDeleteShader(shader->vert); - if (shader->frag != 0) - glDeleteShader(shader->frag); -} - -static void glnvg__getUniforms(GLNVGshader* shader) -{ - shader->loc[GLNVG_LOC_VIEWSIZE] = glGetUniformLocation(shader->prog, "viewSize"); - shader->loc[GLNVG_LOC_TEX] = glGetUniformLocation(shader->prog, "tex"); - -#if NANOVG_GL_USE_UNIFORMBUFFER - shader->loc[GLNVG_LOC_FRAG] = glGetUniformBlockIndex(shader->prog, "frag"); -#else - shader->loc[GLNVG_LOC_FRAG] = glGetUniformLocation(shader->prog, "frag"); -#endif -} - -static int glnvg__renderCreate(void* uptr) -{ - GLNVGcontext* gl = (GLNVGcontext*)uptr; - int align = 4; - - // TODO: mediump float may not be enough for GLES2 in iOS. - // see the following discussion: https://github.com/memononen/nanovg/issues/46 - static const char* shaderHeader = -#if defined NANOVG_GL2 - "#define NANOVG_GL2 1\n" -#elif defined NANOVG_GL3 - "#version 150 core\n" - "#define NANOVG_GL3 1\n" -#elif defined NANOVG_GLES2 - "#version 100\n" - "#define NANOVG_GL2 1\n" -#elif defined NANOVG_GLES3 - "#version 300 es\n" - "#define NANOVG_GL3 1\n" -#endif - -#if NANOVG_GL_USE_UNIFORMBUFFER - "#define USE_UNIFORMBUFFER 1\n" -#else - "#define UNIFORMARRAY_SIZE 11\n" -#endif - "\n"; - - static const char* fillVertShader = - "#ifdef NANOVG_GL3\n" - " uniform vec2 viewSize;\n" - " in vec2 vertex;\n" - " in vec2 tcoord;\n" - " out vec2 ftcoord;\n" - " out vec2 fpos;\n" - "#else\n" - " uniform vec2 viewSize;\n" - " attribute vec2 vertex;\n" - " attribute vec2 tcoord;\n" - " varying vec2 ftcoord;\n" - " varying vec2 fpos;\n" - "#endif\n" - "void main(void) {\n" - " ftcoord = tcoord;\n" - " fpos = vertex;\n" - " gl_Position = vec4(2.0*vertex.x/viewSize.x - 1.0, 1.0 - 2.0*vertex.y/viewSize.y, 0, 1);\n" - "}\n"; - - static const char* fillFragShader = - "#ifdef GL_ES\n" - "#if defined(GL_FRAGMENT_PRECISION_HIGH) || defined(NANOVG_GL3)\n" - " precision highp float;\n" - "#else\n" - " precision mediump float;\n" - "#endif\n" - "#endif\n" - "#ifdef NANOVG_GL3\n" - "#ifdef USE_UNIFORMBUFFER\n" - " layout(std140) uniform frag {\n" - " mat3 scissorMat;\n" - " mat3 paintMat;\n" - " vec4 innerCol;\n" - " vec4 outerCol;\n" - " vec2 scissorExt;\n" - " vec2 scissorScale;\n" - " vec2 extent;\n" - " float radius;\n" - " float feather;\n" - " float strokeMult;\n" - " float strokeThr;\n" - " int texType;\n" - " int type;\n" - " };\n" - "#else\n" // NANOVG_GL3 && !USE_UNIFORMBUFFER - " uniform vec4 frag[UNIFORMARRAY_SIZE];\n" - "#endif\n" - " uniform sampler2D tex;\n" - " in vec2 ftcoord;\n" - " in vec2 fpos;\n" - " out vec4 outColor;\n" - "#else\n" // !NANOVG_GL3 - " uniform vec4 frag[UNIFORMARRAY_SIZE];\n" - " uniform sampler2D tex;\n" - " varying vec2 ftcoord;\n" - " varying vec2 fpos;\n" - "#endif\n" - "#ifndef USE_UNIFORMBUFFER\n" - " #define scissorMat mat3(frag[0].xyz, frag[1].xyz, frag[2].xyz)\n" - " #define paintMat mat3(frag[3].xyz, frag[4].xyz, frag[5].xyz)\n" - " #define innerCol frag[6]\n" - " #define outerCol frag[7]\n" - " #define scissorExt frag[8].xy\n" - " #define scissorScale frag[8].zw\n" - " #define extent frag[9].xy\n" - " #define radius frag[9].z\n" - " #define feather frag[9].w\n" - " #define strokeMult frag[10].x\n" - " #define strokeThr frag[10].y\n" - " #define texType int(frag[10].z)\n" - " #define type int(frag[10].w)\n" - "#endif\n" - "\n" - "float sdroundrect(vec2 pt, vec2 ext, float rad) {\n" - " vec2 ext2 = ext - vec2(rad,rad);\n" - " vec2 d = abs(pt) - ext2;\n" - " return min(max(d.x,d.y),0.0) + length(max(d,0.0)) - rad;\n" - "}\n" - "\n" - "// Scissoring\n" - "float scissorMask(vec2 p) {\n" - " vec2 sc = (abs((scissorMat * vec3(p,1.0)).xy) - scissorExt);\n" - " sc = vec2(0.5,0.5) - sc * scissorScale;\n" - " return clamp(sc.x,0.0,1.0) * clamp(sc.y,0.0,1.0);\n" - "}\n" - "#ifdef EDGE_AA\n" - "// Stroke - from [0..1] to clipped pyramid, where the slope is 1px.\n" - "float strokeMask() {\n" - " return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult) * min(1.0, ftcoord.y);\n" - "}\n" - "#endif\n" - "\n" - "void main(void) {\n" - " vec4 result;\n" - " float scissor = scissorMask(fpos);\n" - "#ifdef EDGE_AA\n" - " float strokeAlpha = strokeMask();\n" - "#else\n" - " float strokeAlpha = 1.0;\n" - "#endif\n" - " if (type == 0) { // Gradient\n" - " // Calculate gradient color using box gradient\n" - " vec2 pt = (paintMat * vec3(fpos,1.0)).xy;\n" - " float d = clamp((sdroundrect(pt, extent, radius) + feather*0.5) / feather, 0.0, 1.0);\n" - " vec4 color = mix(innerCol,outerCol,d);\n" - " // Combine alpha\n" - " color *= strokeAlpha * scissor;\n" - " result = color;\n" - " } else if (type == 1) { // Image\n" - " // Calculate color fron texture\n" - " vec2 pt = (paintMat * vec3(fpos,1.0)).xy / extent;\n" - "#ifdef NANOVG_GL3\n" - " vec4 color = texture(tex, pt);\n" - "#else\n" - " vec4 color = texture2D(tex, pt);\n" - "#endif\n" - " if (texType == 1) color = vec4(color.xyz*color.w,color.w);" - " if (texType == 2) color = vec4(color.x);" - " // Apply color tint and alpha.\n" - " color *= innerCol;\n" - " // Combine alpha\n" - " color *= strokeAlpha * scissor;\n" - " result = color;\n" - " } else if (type == 2) { // Stencil fill\n" - " result = vec4(1,1,1,1);\n" - " } else if (type == 3) { // Textured tris\n" - "#ifdef NANOVG_GL3\n" - " vec4 color = texture(tex, ftcoord);\n" - "#else\n" - " vec4 color = texture2D(tex, ftcoord);\n" - "#endif\n" - " if (texType == 1) color = vec4(color.xyz*color.w,color.w);" - " if (texType == 2) color = vec4(color.x);" - " color *= scissor;\n" - " result = color * innerCol;\n" - " }\n" - "#ifdef EDGE_AA\n" - " if (strokeAlpha < strokeThr) discard;\n" - "#endif\n" - "#ifdef NANOVG_GL3\n" - " outColor = result;\n" - "#else\n" - " gl_FragColor = result;\n" - "#endif\n" - "}\n"; - - glnvg__checkError(gl, "init"); - - if (gl->flags & NVG_ANTIALIAS) { - if (glnvg__createShader(&gl->shader, "shader", shaderHeader, "#define EDGE_AA 1\n", fillVertShader, fillFragShader) == 0) - return 0; - } else { - if (glnvg__createShader(&gl->shader, "shader", shaderHeader, NULL, fillVertShader, fillFragShader) == 0) - return 0; - } - - glnvg__checkError(gl, "uniform locations"); - glnvg__getUniforms(&gl->shader); - - // Create dynamic vertex array -#if defined NANOVG_GL3 - glGenVertexArrays(1, &gl->vertArr); -#endif - glGenBuffers(1, &gl->vertBuf); - -#if NANOVG_GL_USE_UNIFORMBUFFER - // Create UBOs - glUniformBlockBinding(gl->shader.prog, gl->shader.loc[GLNVG_LOC_FRAG], GLNVG_FRAG_BINDING); - glGenBuffers(1, &gl->fragBuf); - glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &align); -#endif - gl->fragSize = sizeof(GLNVGfragUniforms) + align - sizeof(GLNVGfragUniforms) % align; - - glnvg__checkError(gl, "create done"); - - glFinish(); - - return 1; -} - -static int glnvg__renderCreateTexture(void* uptr, int type, int w, int h, int imageFlags, const unsigned char* data) -{ - GLNVGcontext* gl = (GLNVGcontext*)uptr; - GLNVGtexture* tex = glnvg__allocTexture(gl); - - if (tex == NULL) return 0; - -#ifdef NANOVG_GLES2 - // Check for non-power of 2. - if (glnvg__nearestPow2(w) != (unsigned int)w || glnvg__nearestPow2(h) != (unsigned int)h) { - // No repeat - if ((imageFlags & NVG_IMAGE_REPEATX) != 0 || (imageFlags & NVG_IMAGE_REPEATY) != 0) { - printf("Repeat X/Y is not supported for non power-of-two textures (%d x %d)\n", w, h); - imageFlags &= ~(NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY); - } - // No mips. - if (imageFlags & NVG_IMAGE_GENERATE_MIPMAPS) { - printf("Mip-maps is not support for non power-of-two textures (%d x %d)\n", w, h); - imageFlags &= ~NVG_IMAGE_GENERATE_MIPMAPS; - } - } -#endif - - glGenTextures(1, &tex->tex); - tex->width = w; - tex->height = h; - tex->type = type; - tex->flags = imageFlags; - glnvg__bindTexture(gl, tex->tex); - - glPixelStorei(GL_UNPACK_ALIGNMENT,1); -#ifndef NANOVG_GLES2 - glPixelStorei(GL_UNPACK_ROW_LENGTH, tex->width); - glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); - glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); -#endif - -#if defined (NANOVG_GL2) - // GL 1.4 and later has support for generating mipmaps using a tex parameter. - if (imageFlags & NVG_IMAGE_GENERATE_MIPMAPS) { - glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); - } -#endif - - if (type == NVG_TEXTURE_RGBA) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); - else -#if defined(NANOVG_GLES2) - glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, w, h, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data); -#elif defined(NANOVG_GLES3) - glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, data); -#else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, data); -#endif - - if (imageFlags & NVG_IMAGE_GENERATE_MIPMAPS) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - } else { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - } - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - if (imageFlags & NVG_IMAGE_REPEATX) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - else - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - - if (imageFlags & NVG_IMAGE_REPEATY) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - else - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); -#ifndef NANOVG_GLES2 - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); - glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); -#endif - - // The new way to build mipmaps on GLES and GL3 -#if !defined(NANOVG_GL2) - if (imageFlags & NVG_IMAGE_GENERATE_MIPMAPS) { - glGenerateMipmap(GL_TEXTURE_2D); - } -#endif - - glnvg__checkError(gl, "create tex"); - glnvg__bindTexture(gl, 0); - - return tex->id; -} - - -static int glnvg__renderDeleteTexture(void* uptr, int image) -{ - GLNVGcontext* gl = (GLNVGcontext*)uptr; - return glnvg__deleteTexture(gl, image); -} - -static int glnvg__renderUpdateTexture(void* uptr, int image, int x, int y, int w, int h, const unsigned char* data) -{ - GLNVGcontext* gl = (GLNVGcontext*)uptr; - GLNVGtexture* tex = glnvg__findTexture(gl, image); - - if (tex == NULL) return 0; - glnvg__bindTexture(gl, tex->tex); - - glPixelStorei(GL_UNPACK_ALIGNMENT,1); - -#ifndef NANOVG_GLES2 - glPixelStorei(GL_UNPACK_ROW_LENGTH, tex->width); - glPixelStorei(GL_UNPACK_SKIP_PIXELS, x); - glPixelStorei(GL_UNPACK_SKIP_ROWS, y); -#else - // No support for all of skip, need to update a whole row at a time. - if (tex->type == NVG_TEXTURE_RGBA) - data += y*tex->width*4; - else - data += y*tex->width; - x = 0; - w = tex->width; -#endif - - if (tex->type == NVG_TEXTURE_RGBA) - glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_RGBA, GL_UNSIGNED_BYTE, data); - else -#ifdef NANOVG_GLES2 - glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_LUMINANCE, GL_UNSIGNED_BYTE, data); -#else - glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_RED, GL_UNSIGNED_BYTE, data); -#endif - - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); -#ifndef NANOVG_GLES2 - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); - glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); -#endif - - glnvg__bindTexture(gl, 0); - - return 1; -} - -static int glnvg__renderGetTextureSize(void* uptr, int image, int* w, int* h) -{ - GLNVGcontext* gl = (GLNVGcontext*)uptr; - GLNVGtexture* tex = glnvg__findTexture(gl, image); - if (tex == NULL) return 0; - *w = tex->width; - *h = tex->height; - return 1; -} - -static void glnvg__xformToMat3x4(float* m3, float* t) -{ - m3[0] = t[0]; - m3[1] = t[1]; - m3[2] = 0.0f; - m3[3] = 0.0f; - m3[4] = t[2]; - m3[5] = t[3]; - m3[6] = 0.0f; - m3[7] = 0.0f; - m3[8] = t[4]; - m3[9] = t[5]; - m3[10] = 1.0f; - m3[11] = 0.0f; -} - -static NVGcolor glnvg__premulColor(NVGcolor c) -{ - c.r *= c.a; - c.g *= c.a; - c.b *= c.a; - return c; -} - -static int glnvg__convertPaint(GLNVGcontext* gl, GLNVGfragUniforms* frag, NVGpaint* paint, - NVGscissor* scissor, float width, float fringe, float strokeThr) -{ - GLNVGtexture* tex = NULL; - float invxform[6]; - - memset(frag, 0, sizeof(*frag)); - - frag->innerCol = glnvg__premulColor(paint->innerColor); - frag->outerCol = glnvg__premulColor(paint->outerColor); - - if (scissor->extent[0] < -0.5f || scissor->extent[1] < -0.5f) { - memset(frag->scissorMat, 0, sizeof(frag->scissorMat)); - frag->scissorExt[0] = 1.0f; - frag->scissorExt[1] = 1.0f; - frag->scissorScale[0] = 1.0f; - frag->scissorScale[1] = 1.0f; - } else { - nvgTransformInverse(invxform, scissor->xform); - glnvg__xformToMat3x4(frag->scissorMat, invxform); - frag->scissorExt[0] = scissor->extent[0]; - frag->scissorExt[1] = scissor->extent[1]; - frag->scissorScale[0] = sqrtf(scissor->xform[0]*scissor->xform[0] + scissor->xform[2]*scissor->xform[2]) / fringe; - frag->scissorScale[1] = sqrtf(scissor->xform[1]*scissor->xform[1] + scissor->xform[3]*scissor->xform[3]) / fringe; - } - - memcpy(frag->extent, paint->extent, sizeof(frag->extent)); - frag->strokeMult = (width*0.5f + fringe*0.5f) / fringe; - frag->strokeThr = strokeThr; - - if (paint->image != 0) { - tex = glnvg__findTexture(gl, paint->image); - if (tex == NULL) return 0; - if ((tex->flags & NVG_IMAGE_FLIPY) != 0) { - float m1[6], m2[6]; - nvgTransformTranslate(m1, 0.0f, frag->extent[1] * 0.5f); - nvgTransformMultiply(m1, paint->xform); - nvgTransformScale(m2, 1.0f, -1.0f); - nvgTransformMultiply(m2, m1); - nvgTransformTranslate(m1, 0.0f, -frag->extent[1] * 0.5f); - nvgTransformMultiply(m1, m2); - nvgTransformInverse(invxform, m1); - } else { - nvgTransformInverse(invxform, paint->xform); - } - frag->type = NSVG_SHADER_FILLIMG; - - if (tex->type == NVG_TEXTURE_RGBA) - frag->texType = (tex->flags & NVG_IMAGE_PREMULTIPLIED) ? 0 : 1; - else - frag->texType = 2; -// printf("frag->texType = %d\n", frag->texType); - } else { - frag->type = NSVG_SHADER_FILLGRAD; - frag->radius = paint->radius; - frag->feather = paint->feather; - nvgTransformInverse(invxform, paint->xform); - } - - glnvg__xformToMat3x4(frag->paintMat, invxform); - - return 1; -} - -static GLNVGfragUniforms* nvg__fragUniformPtr(GLNVGcontext* gl, int i); - -static void glnvg__setUniforms(GLNVGcontext* gl, int uniformOffset, int image) -{ -#if NANOVG_GL_USE_UNIFORMBUFFER - glBindBufferRange(GL_UNIFORM_BUFFER, GLNVG_FRAG_BINDING, gl->fragBuf, uniformOffset, sizeof(GLNVGfragUniforms)); -#else - GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset); - glUniform4fv(gl->shader.loc[GLNVG_LOC_FRAG], NANOVG_GL_UNIFORMARRAY_SIZE, &(frag->uniformArray[0][0])); -#endif - - if (image != 0) { - GLNVGtexture* tex = glnvg__findTexture(gl, image); - glnvg__bindTexture(gl, tex != NULL ? tex->tex : 0); - glnvg__checkError(gl, "tex paint tex"); - } else { - glnvg__bindTexture(gl, 0); - } -} - -static void glnvg__renderViewport(void* uptr, int width, int height, float devicePixelRatio) -{ - GLNVGcontext* gl = (GLNVGcontext*)uptr; - gl->view[0] = (float)width; - gl->view[1] = (float)height; -} - -static void glnvg__fill(GLNVGcontext* gl, GLNVGcall* call) -{ - GLNVGpath* paths = &gl->paths[call->pathOffset]; - int i, npaths = call->pathCount; - - // Draw shapes - glEnable(GL_STENCIL_TEST); - glnvg__stencilMask(gl, 0xff); - glnvg__stencilFunc(gl, GL_ALWAYS, 0, 0xff); - glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); - - // set bindpoint for solid loc - glnvg__setUniforms(gl, call->uniformOffset, 0); - glnvg__checkError(gl, "fill simple"); - - glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP); - glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP); - glDisable(GL_CULL_FACE); - for (i = 0; i < npaths; i++) - glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); - glEnable(GL_CULL_FACE); - - // Draw anti-aliased pixels - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - - glnvg__setUniforms(gl, call->uniformOffset + gl->fragSize, call->image); - glnvg__checkError(gl, "fill fill"); - - if (gl->flags & NVG_ANTIALIAS) { - glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); - glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); - // Draw fringes - for (i = 0; i < npaths; i++) - glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); - } - - // Draw fill - glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x0, 0xff); - glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); - glDrawArrays(GL_TRIANGLES, call->triangleOffset, call->triangleCount); - - glDisable(GL_STENCIL_TEST); -} - -static void glnvg__convexFill(GLNVGcontext* gl, GLNVGcall* call) -{ - GLNVGpath* paths = &gl->paths[call->pathOffset]; - int i, npaths = call->pathCount; - - glnvg__setUniforms(gl, call->uniformOffset, call->image); - glnvg__checkError(gl, "convex fill"); - - for (i = 0; i < npaths; i++) - glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); - if (gl->flags & NVG_ANTIALIAS) { - // Draw fringes - for (i = 0; i < npaths; i++) - glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); - } -} - -static void glnvg__stroke(GLNVGcontext* gl, GLNVGcall* call) -{ - GLNVGpath* paths = &gl->paths[call->pathOffset]; - int npaths = call->pathCount, i; - - if (gl->flags & NVG_STENCIL_STROKES) { - - glEnable(GL_STENCIL_TEST); - glnvg__stencilMask(gl, 0xff); - - // Fill the stroke base without overlap - glnvg__stencilFunc(gl, GL_EQUAL, 0x0, 0xff); - glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); - glnvg__setUniforms(gl, call->uniformOffset + gl->fragSize, call->image); - glnvg__checkError(gl, "stroke fill 0"); - for (i = 0; i < npaths; i++) - glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); - - // Draw anti-aliased pixels. - glnvg__setUniforms(gl, call->uniformOffset, call->image); - glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); - glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); - for (i = 0; i < npaths; i++) - glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); - - // Clear stencil buffer. - glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); - glnvg__stencilFunc(gl, GL_ALWAYS, 0x0, 0xff); - glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); - glnvg__checkError(gl, "stroke fill 1"); - for (i = 0; i < npaths; i++) - glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - - glDisable(GL_STENCIL_TEST); - -// glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call->uniformOffset + gl->fragSize), paint, scissor, strokeWidth, fringe, 1.0f - 0.5f/255.0f); - - } else { - glnvg__setUniforms(gl, call->uniformOffset, call->image); - glnvg__checkError(gl, "stroke fill"); - // Draw Strokes - for (i = 0; i < npaths; i++) - glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); - } -} - -static void glnvg__triangles(GLNVGcontext* gl, GLNVGcall* call) -{ - glnvg__setUniforms(gl, call->uniformOffset, call->image); - glnvg__checkError(gl, "triangles fill"); - - glDrawArrays(GL_TRIANGLES, call->triangleOffset, call->triangleCount); -} - -static void glnvg__renderCancel(void* uptr) { - GLNVGcontext* gl = (GLNVGcontext*)uptr; - gl->nverts = 0; - gl->npaths = 0; - gl->ncalls = 0; - gl->nuniforms = 0; -} - -static GLenum glnvg_convertBlendFuncFactor(int factor) -{ - if (factor == NVG_ZERO) - return GL_ZERO; - if (factor == NVG_ONE) - return GL_ONE; - if (factor == NVG_SRC_COLOR) - return GL_SRC_COLOR; - if (factor == NVG_ONE_MINUS_SRC_COLOR) - return GL_ONE_MINUS_SRC_COLOR; - if (factor == NVG_DST_COLOR) - return GL_DST_COLOR; - if (factor == NVG_ONE_MINUS_DST_COLOR) - return GL_ONE_MINUS_DST_COLOR; - if (factor == NVG_SRC_ALPHA) - return GL_SRC_ALPHA; - if (factor == NVG_ONE_MINUS_SRC_ALPHA) - return GL_ONE_MINUS_SRC_ALPHA; - if (factor == NVG_DST_ALPHA) - return GL_DST_ALPHA; - if (factor == NVG_ONE_MINUS_DST_ALPHA) - return GL_ONE_MINUS_DST_ALPHA; - if (factor == NVG_SRC_ALPHA_SATURATE) - return GL_SRC_ALPHA_SATURATE; - return GL_INVALID_ENUM; -} - -static void glnvg__blendCompositeOperation(NVGcompositeOperationState op) -{ - GLenum srcRGB = glnvg_convertBlendFuncFactor(op.srcRGB); - GLenum dstRGB = glnvg_convertBlendFuncFactor(op.dstRGB); - GLenum srcAlpha = glnvg_convertBlendFuncFactor(op.srcAlpha); - GLenum dstAlpha = glnvg_convertBlendFuncFactor(op.dstAlpha); - if (srcRGB == GL_INVALID_ENUM || dstRGB == GL_INVALID_ENUM || srcAlpha == GL_INVALID_ENUM || dstAlpha == GL_INVALID_ENUM) - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - else - glBlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha); -} - -static void glnvg__renderFlush(void* uptr, NVGcompositeOperationState compositeOperation) -{ - GLNVGcontext* gl = (GLNVGcontext*)uptr; - int i; - - if (gl->ncalls > 0) { - - // Setup require GL state. - glUseProgram(gl->shader.prog); - - glnvg__blendCompositeOperation(compositeOperation); - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - glFrontFace(GL_CCW); - glEnable(GL_BLEND); - glDisable(GL_DEPTH_TEST); - glDisable(GL_SCISSOR_TEST); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glStencilMask(0xffffffff); - glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); - glStencilFunc(GL_ALWAYS, 0, 0xffffffff); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, 0); - #if NANOVG_GL_USE_STATE_FILTER - gl->boundTexture = 0; - gl->stencilMask = 0xffffffff; - gl->stencilFunc = GL_ALWAYS; - gl->stencilFuncRef = 0; - gl->stencilFuncMask = 0xffffffff; - #endif - -#if NANOVG_GL_USE_UNIFORMBUFFER - // Upload ubo for frag shaders - glBindBuffer(GL_UNIFORM_BUFFER, gl->fragBuf); - glBufferData(GL_UNIFORM_BUFFER, gl->nuniforms * gl->fragSize, gl->uniforms, GL_STREAM_DRAW); -#endif - - // Upload vertex data -#if defined NANOVG_GL3 - glBindVertexArray(gl->vertArr); -#endif - glBindBuffer(GL_ARRAY_BUFFER, gl->vertBuf); - glBufferData(GL_ARRAY_BUFFER, gl->nverts * sizeof(NVGvertex), gl->verts, GL_STREAM_DRAW); - glEnableVertexAttribArray(0); - glEnableVertexAttribArray(1); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(NVGvertex), (const GLvoid*)(size_t)0); - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(NVGvertex), (const GLvoid*)(0 + 2*sizeof(float))); - - // Set view and texture just once per frame. - glUniform1i(gl->shader.loc[GLNVG_LOC_TEX], 0); - glUniform2fv(gl->shader.loc[GLNVG_LOC_VIEWSIZE], 1, gl->view); - -#if NANOVG_GL_USE_UNIFORMBUFFER - glBindBuffer(GL_UNIFORM_BUFFER, gl->fragBuf); -#endif - - for (i = 0; i < gl->ncalls; i++) { - GLNVGcall* call = &gl->calls[i]; - if (call->type == GLNVG_FILL) - glnvg__fill(gl, call); - else if (call->type == GLNVG_CONVEXFILL) - glnvg__convexFill(gl, call); - else if (call->type == GLNVG_STROKE) - glnvg__stroke(gl, call); - else if (call->type == GLNVG_TRIANGLES) - glnvg__triangles(gl, call); - } - - glDisableVertexAttribArray(0); - glDisableVertexAttribArray(1); -#if defined NANOVG_GL3 - glBindVertexArray(0); -#endif - glDisable(GL_CULL_FACE); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glUseProgram(0); - glnvg__bindTexture(gl, 0); - } - - // Reset calls - gl->nverts = 0; - gl->npaths = 0; - gl->ncalls = 0; - gl->nuniforms = 0; -} - -static int glnvg__maxVertCount(const NVGpath* paths, int npaths) -{ - int i, count = 0; - for (i = 0; i < npaths; i++) { - count += paths[i].nfill; - count += paths[i].nstroke; - } - return count; -} - -static GLNVGcall* glnvg__allocCall(GLNVGcontext* gl) -{ - GLNVGcall* ret = NULL; - if (gl->ncalls+1 > gl->ccalls) { - GLNVGcall* calls; - int ccalls = glnvg__maxi(gl->ncalls+1, 128) + gl->ccalls/2; // 1.5x Overallocate - calls = (GLNVGcall*)realloc(gl->calls, sizeof(GLNVGcall) * ccalls); - if (calls == NULL) return NULL; - gl->calls = calls; - gl->ccalls = ccalls; - } - ret = &gl->calls[gl->ncalls++]; - memset(ret, 0, sizeof(GLNVGcall)); - return ret; -} - -static int glnvg__allocPaths(GLNVGcontext* gl, int n) -{ - int ret = 0; - if (gl->npaths+n > gl->cpaths) { - GLNVGpath* paths; - int cpaths = glnvg__maxi(gl->npaths + n, 128) + gl->cpaths/2; // 1.5x Overallocate - paths = (GLNVGpath*)realloc(gl->paths, sizeof(GLNVGpath) * cpaths); - if (paths == NULL) return -1; - gl->paths = paths; - gl->cpaths = cpaths; - } - ret = gl->npaths; - gl->npaths += n; - return ret; -} - -static int glnvg__allocVerts(GLNVGcontext* gl, int n) -{ - int ret = 0; - if (gl->nverts+n > gl->cverts) { - NVGvertex* verts; - int cverts = glnvg__maxi(gl->nverts + n, 4096) + gl->cverts/2; // 1.5x Overallocate - verts = (NVGvertex*)realloc(gl->verts, sizeof(NVGvertex) * cverts); - if (verts == NULL) return -1; - gl->verts = verts; - gl->cverts = cverts; - } - ret = gl->nverts; - gl->nverts += n; - return ret; -} - -static int glnvg__allocFragUniforms(GLNVGcontext* gl, int n) -{ - int ret = 0, structSize = gl->fragSize; - if (gl->nuniforms+n > gl->cuniforms) { - unsigned char* uniforms; - int cuniforms = glnvg__maxi(gl->nuniforms+n, 128) + gl->cuniforms/2; // 1.5x Overallocate - uniforms = (unsigned char*)realloc(gl->uniforms, structSize * cuniforms); - if (uniforms == NULL) return -1; - gl->uniforms = uniforms; - gl->cuniforms = cuniforms; - } - ret = gl->nuniforms * structSize; - gl->nuniforms += n; - return ret; -} - -static GLNVGfragUniforms* nvg__fragUniformPtr(GLNVGcontext* gl, int i) -{ - return (GLNVGfragUniforms*)&gl->uniforms[i]; -} - -static void glnvg__vset(NVGvertex* vtx, float x, float y, float u, float v) -{ - vtx->x = x; - vtx->y = y; - vtx->u = u; - vtx->v = v; -} - -static void glnvg__renderFill(void* uptr, NVGpaint* paint, NVGscissor* scissor, float fringe, - const float* bounds, const NVGpath* paths, int npaths) -{ - GLNVGcontext* gl = (GLNVGcontext*)uptr; - GLNVGcall* call = glnvg__allocCall(gl); - NVGvertex* quad; - GLNVGfragUniforms* frag; - int i, maxverts, offset; - - if (call == NULL) return; - - call->type = GLNVG_FILL; - call->pathOffset = glnvg__allocPaths(gl, npaths); - if (call->pathOffset == -1) goto error; - call->pathCount = npaths; - call->image = paint->image; - - if (npaths == 1 && paths[0].convex) - call->type = GLNVG_CONVEXFILL; - - // Allocate vertices for all the paths. - maxverts = glnvg__maxVertCount(paths, npaths) + 6; - offset = glnvg__allocVerts(gl, maxverts); - if (offset == -1) goto error; - - for (i = 0; i < npaths; i++) { - GLNVGpath* copy = &gl->paths[call->pathOffset + i]; - const NVGpath* path = &paths[i]; - memset(copy, 0, sizeof(GLNVGpath)); - if (path->nfill > 0) { - copy->fillOffset = offset; - copy->fillCount = path->nfill; - memcpy(&gl->verts[offset], path->fill, sizeof(NVGvertex) * path->nfill); - offset += path->nfill; - } - if (path->nstroke > 0) { - copy->strokeOffset = offset; - copy->strokeCount = path->nstroke; - memcpy(&gl->verts[offset], path->stroke, sizeof(NVGvertex) * path->nstroke); - offset += path->nstroke; - } - } - - // Quad - call->triangleOffset = offset; - call->triangleCount = 6; - quad = &gl->verts[call->triangleOffset]; - glnvg__vset(&quad[0], bounds[0], bounds[3], 0.5f, 1.0f); - glnvg__vset(&quad[1], bounds[2], bounds[3], 0.5f, 1.0f); - glnvg__vset(&quad[2], bounds[2], bounds[1], 0.5f, 1.0f); - - glnvg__vset(&quad[3], bounds[0], bounds[3], 0.5f, 1.0f); - glnvg__vset(&quad[4], bounds[2], bounds[1], 0.5f, 1.0f); - glnvg__vset(&quad[5], bounds[0], bounds[1], 0.5f, 1.0f); - - // Setup uniforms for draw calls - if (call->type == GLNVG_FILL) { - call->uniformOffset = glnvg__allocFragUniforms(gl, 2); - if (call->uniformOffset == -1) goto error; - // Simple shader for stencil - frag = nvg__fragUniformPtr(gl, call->uniformOffset); - memset(frag, 0, sizeof(*frag)); - frag->strokeThr = -1.0f; - frag->type = NSVG_SHADER_SIMPLE; - // Fill shader - glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call->uniformOffset + gl->fragSize), paint, scissor, fringe, fringe, -1.0f); - } else { - call->uniformOffset = glnvg__allocFragUniforms(gl, 1); - if (call->uniformOffset == -1) goto error; - // Fill shader - glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call->uniformOffset), paint, scissor, fringe, fringe, -1.0f); - } - - return; - -error: - // We get here if call alloc was ok, but something else is not. - // Roll back the last call to prevent drawing it. - if (gl->ncalls > 0) gl->ncalls--; -} - -static void glnvg__renderStroke(void* uptr, NVGpaint* paint, NVGscissor* scissor, float fringe, - float strokeWidth, const NVGpath* paths, int npaths) -{ - GLNVGcontext* gl = (GLNVGcontext*)uptr; - GLNVGcall* call = glnvg__allocCall(gl); - int i, maxverts, offset; - - if (call == NULL) return; - - call->type = GLNVG_STROKE; - call->pathOffset = glnvg__allocPaths(gl, npaths); - if (call->pathOffset == -1) goto error; - call->pathCount = npaths; - call->image = paint->image; - - // Allocate vertices for all the paths. - maxverts = glnvg__maxVertCount(paths, npaths); - offset = glnvg__allocVerts(gl, maxverts); - if (offset == -1) goto error; - - for (i = 0; i < npaths; i++) { - GLNVGpath* copy = &gl->paths[call->pathOffset + i]; - const NVGpath* path = &paths[i]; - memset(copy, 0, sizeof(GLNVGpath)); - if (path->nstroke) { - copy->strokeOffset = offset; - copy->strokeCount = path->nstroke; - memcpy(&gl->verts[offset], path->stroke, sizeof(NVGvertex) * path->nstroke); - offset += path->nstroke; - } - } - - if (gl->flags & NVG_STENCIL_STROKES) { - // Fill shader - call->uniformOffset = glnvg__allocFragUniforms(gl, 2); - if (call->uniformOffset == -1) goto error; - - glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call->uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f); - glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call->uniformOffset + gl->fragSize), paint, scissor, strokeWidth, fringe, 1.0f - 0.5f/255.0f); - - } else { - // Fill shader - call->uniformOffset = glnvg__allocFragUniforms(gl, 1); - if (call->uniformOffset == -1) goto error; - glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call->uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f); - } - - return; - -error: - // We get here if call alloc was ok, but something else is not. - // Roll back the last call to prevent drawing it. - if (gl->ncalls > 0) gl->ncalls--; -} - -static void glnvg__renderTriangles(void* uptr, NVGpaint* paint, NVGscissor* scissor, - const NVGvertex* verts, int nverts) -{ - GLNVGcontext* gl = (GLNVGcontext*)uptr; - GLNVGcall* call = glnvg__allocCall(gl); - GLNVGfragUniforms* frag; - - if (call == NULL) return; - - call->type = GLNVG_TRIANGLES; - call->image = paint->image; - - // Allocate vertices for all the paths. - call->triangleOffset = glnvg__allocVerts(gl, nverts); - if (call->triangleOffset == -1) goto error; - call->triangleCount = nverts; - - memcpy(&gl->verts[call->triangleOffset], verts, sizeof(NVGvertex) * nverts); - - // Fill shader - call->uniformOffset = glnvg__allocFragUniforms(gl, 1); - if (call->uniformOffset == -1) goto error; - frag = nvg__fragUniformPtr(gl, call->uniformOffset); - glnvg__convertPaint(gl, frag, paint, scissor, 1.0f, 1.0f, -1.0f); - frag->type = NSVG_SHADER_IMG; - - return; - -error: - // We get here if call alloc was ok, but something else is not. - // Roll back the last call to prevent drawing it. - if (gl->ncalls > 0) gl->ncalls--; -} - -static void glnvg__renderDelete(void* uptr) -{ - GLNVGcontext* gl = (GLNVGcontext*)uptr; - int i; - if (gl == NULL) return; - - glnvg__deleteShader(&gl->shader); - -#if NANOVG_GL3 -#if NANOVG_GL_USE_UNIFORMBUFFER - if (gl->fragBuf != 0) - glDeleteBuffers(1, &gl->fragBuf); -#endif - if (gl->vertArr != 0) - glDeleteVertexArrays(1, &gl->vertArr); -#endif - if (gl->vertBuf != 0) - glDeleteBuffers(1, &gl->vertBuf); - - for (i = 0; i < gl->ntextures; i++) { - if (gl->textures[i].tex != 0 && (gl->textures[i].flags & NVG_IMAGE_NODELETE) == 0) - glDeleteTextures(1, &gl->textures[i].tex); - } - free(gl->textures); - - free(gl->paths); - free(gl->verts); - free(gl->uniforms); - free(gl->calls); - - free(gl); -} - - -#if defined NANOVG_GL2 -NVGcontext* nvgCreateGL2(int flags) -#elif defined NANOVG_GL3 -NVGcontext* nvgCreateGL3(int flags) -#elif defined NANOVG_GLES2 -NVGcontext* nvgCreateGLES2(int flags) -#elif defined NANOVG_GLES3 -NVGcontext* nvgCreateGLES3(int flags) -#endif -{ - NVGparams params; - NVGcontext* ctx = NULL; - GLNVGcontext* gl = (GLNVGcontext*)malloc(sizeof(GLNVGcontext)); - if (gl == NULL) goto error; - memset(gl, 0, sizeof(GLNVGcontext)); - - memset(¶ms, 0, sizeof(params)); - params.renderCreate = glnvg__renderCreate; - params.renderCreateTexture = glnvg__renderCreateTexture; - params.renderDeleteTexture = glnvg__renderDeleteTexture; - params.renderUpdateTexture = glnvg__renderUpdateTexture; - params.renderGetTextureSize = glnvg__renderGetTextureSize; - params.renderViewport = glnvg__renderViewport; - params.renderCancel = glnvg__renderCancel; - params.renderFlush = glnvg__renderFlush; - params.renderFill = glnvg__renderFill; - params.renderStroke = glnvg__renderStroke; - params.renderTriangles = glnvg__renderTriangles; - params.renderDelete = glnvg__renderDelete; - params.userPtr = gl; - params.edgeAntiAlias = flags & NVG_ANTIALIAS ? 1 : 0; - - gl->flags = flags; - - ctx = nvgCreateInternal(¶ms); - if (ctx == NULL) goto error; - - return ctx; - -error: - // 'gl' is freed by nvgDeleteInternal. - if (ctx != NULL) nvgDeleteInternal(ctx); - return NULL; -} - -#if defined NANOVG_GL2 -void nvgDeleteGL2(NVGcontext* ctx) -#elif defined NANOVG_GL3 -void nvgDeleteGL3(NVGcontext* ctx) -#elif defined NANOVG_GLES2 -void nvgDeleteGLES2(NVGcontext* ctx) -#elif defined NANOVG_GLES3 -void nvgDeleteGLES3(NVGcontext* ctx) -#endif -{ - nvgDeleteInternal(ctx); -} - -#if defined NANOVG_GL2 -int nvglCreateImageFromHandleGL2(NVGcontext* ctx, GLuint textureId, int w, int h, int imageFlags) -#elif defined NANOVG_GL3 -int nvglCreateImageFromHandleGL3(NVGcontext* ctx, GLuint textureId, int w, int h, int imageFlags) -#elif defined NANOVG_GLES2 -int nvglCreateImageFromHandleGLES2(NVGcontext* ctx, GLuint textureId, int w, int h, int imageFlags) -#elif defined NANOVG_GLES3 -int nvglCreateImageFromHandleGLES3(NVGcontext* ctx, GLuint textureId, int w, int h, int imageFlags) -#endif -{ - GLNVGcontext* gl = (GLNVGcontext*)nvgInternalParams(ctx)->userPtr; - GLNVGtexture* tex = glnvg__allocTexture(gl); - - if (tex == NULL) return 0; - - tex->type = NVG_TEXTURE_RGBA; - tex->tex = textureId; - tex->flags = imageFlags; - tex->width = w; - tex->height = h; - - return tex->id; -} - -#if defined NANOVG_GL2 -GLuint nvglImageHandleGL2(NVGcontext* ctx, int image) -#elif defined NANOVG_GL3 -GLuint nvglImageHandleGL3(NVGcontext* ctx, int image) -#elif defined NANOVG_GLES2 -GLuint nvglImageHandleGLES2(NVGcontext* ctx, int image) -#elif defined NANOVG_GLES3 -GLuint nvglImageHandleGLES3(NVGcontext* ctx, int image) -#endif -{ - GLNVGcontext* gl = (GLNVGcontext*)nvgInternalParams(ctx)->userPtr; - GLNVGtexture* tex = glnvg__findTexture(gl, image); - return tex->tex; -} - -#endif /* NANOVG_GL_IMPLEMENTATION */ diff --git a/third_party/nanovg/nanovg_gl_utils.h b/third_party/nanovg/nanovg_gl_utils.h deleted file mode 100644 index 2d8699c4c..000000000 --- a/third_party/nanovg/nanovg_gl_utils.h +++ /dev/null @@ -1,143 +0,0 @@ -// -// Copyright (c) 2009-2013 Mikko Mononen memon@inside.org -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. -// -#ifndef NANOVG_GL_UTILS_H -#define NANOVG_GL_UTILS_H - -struct NVGLUframebuffer { - NVGcontext* ctx; - GLuint fbo; - GLuint rbo; - GLuint texture; - int image; -}; -typedef struct NVGLUframebuffer NVGLUframebuffer; - -// Helper function to create GL frame buffer to render to. -void nvgluBindFramebuffer(NVGLUframebuffer* fb); -NVGLUframebuffer* nvgluCreateFramebuffer(NVGcontext* ctx, int w, int h, int imageFlags); -void nvgluDeleteFramebuffer(NVGLUframebuffer* fb); - -#endif // NANOVG_GL_UTILS_H - -#ifdef NANOVG_GL_IMPLEMENTATION - -#if defined(NANOVG_GL3) || defined(NANOVG_GLES2) || defined(NANOVG_GLES3) -// FBO is core in OpenGL 3>. -# define NANOVG_FBO_VALID 1 -#elif defined(NANOVG_GL2) -// On OS X including glext defines FBO on GL2 too. -# ifdef __APPLE__ -# include -# define NANOVG_FBO_VALID 1 -# endif -#endif - -static GLint defaultFBO = -1; - -NVGLUframebuffer* nvgluCreateFramebuffer(NVGcontext* ctx, int w, int h, int imageFlags) -{ -#ifdef NANOVG_FBO_VALID - GLint localDefaultFBO; - GLint defaultRBO; - NVGLUframebuffer* fb = NULL; - - glGetIntegerv(GL_FRAMEBUFFER_BINDING, &localDefaultFBO); - glGetIntegerv(GL_RENDERBUFFER_BINDING, &defaultRBO); - - fb = (NVGLUframebuffer*)malloc(sizeof(NVGLUframebuffer)); - if (fb == NULL) goto error; - memset(fb, 0, sizeof(NVGLUframebuffer)); - - fb->image = nvgCreateImageRGBA(ctx, w, h, imageFlags | NVG_IMAGE_FLIPY | NVG_IMAGE_PREMULTIPLIED, NULL); - -#if defined NANOVG_GL2 - fb->texture = nvglImageHandleGL2(ctx, fb->image); -#elif defined NANOVG_GL3 - fb->texture = nvglImageHandleGL3(ctx, fb->image); -#elif defined NANOVG_GLES2 - fb->texture = nvglImageHandleGLES2(ctx, fb->image); -#elif defined NANOVG_GLES3 - fb->texture = nvglImageHandleGLES3(ctx, fb->image); -#endif - - fb->ctx = ctx; - - // frame buffer object - glGenFramebuffers(1, &fb->fbo); - glBindFramebuffer(GL_FRAMEBUFFER, fb->fbo); - - // render buffer object - glGenRenderbuffers(1, &fb->rbo); - glBindRenderbuffer(GL_RENDERBUFFER, fb->rbo); - glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, w, h); - - // combine all - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb->texture, 0); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb->rbo); - - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) goto error; - - glBindFramebuffer(GL_FRAMEBUFFER, localDefaultFBO); - glBindRenderbuffer(GL_RENDERBUFFER, defaultRBO); - return fb; -error: - glBindFramebuffer(GL_FRAMEBUFFER, localDefaultFBO); - glBindRenderbuffer(GL_RENDERBUFFER, defaultRBO); - nvgluDeleteFramebuffer(fb); - return NULL; -#else - NVG_NOTUSED(ctx); - NVG_NOTUSED(w); - NVG_NOTUSED(h); - NVG_NOTUSED(imageFlags); - return NULL; -#endif -} - -void nvgluBindFramebuffer(NVGLUframebuffer* fb) -{ -#ifdef NANOVG_FBO_VALID - if (defaultFBO == -1) glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFBO); - glBindFramebuffer(GL_FRAMEBUFFER, fb != NULL ? fb->fbo : defaultFBO); -#else - NVG_NOTUSED(fb); -#endif -} - -void nvgluDeleteFramebuffer(NVGLUframebuffer* fb) -{ -#ifdef NANOVG_FBO_VALID - if (fb == NULL) return; - if (fb->fbo != 0) - glDeleteFramebuffers(1, &fb->fbo); - if (fb->rbo != 0) - glDeleteRenderbuffers(1, &fb->rbo); - if (fb->image >= 0) - nvgDeleteImage(fb->ctx, fb->image); - fb->ctx = NULL; - fb->fbo = 0; - fb->rbo = 0; - fb->texture = 0; - fb->image = -1; - free(fb); -#else - NVG_NOTUSED(fb); -#endif -} - -#endif // NANOVG_GL_IMPLEMENTATION diff --git a/third_party/nanovg/stb_image.h b/third_party/nanovg/stb_image.h deleted file mode 100644 index e06f7a1d7..000000000 --- a/third_party/nanovg/stb_image.h +++ /dev/null @@ -1,6614 +0,0 @@ -/* stb_image - v2.10 - public domain image loader - http://nothings.org/stb_image.h - no warranty implied; use at your own risk - - Do this: - #define STB_IMAGE_IMPLEMENTATION - before you include this file in *one* C or C++ file to create the implementation. - - // i.e. it should look like this: - #include ... - #include ... - #include ... - #define STB_IMAGE_IMPLEMENTATION - #include "stb_image.h" - - You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. - And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free - - - QUICK NOTES: - Primarily of interest to game developers and other people who can - avoid problematic images and only need the trivial interface - - JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) - PNG 1/2/4/8-bit-per-channel (16 bpc not supported) - - TGA (not sure what subset, if a subset) - BMP non-1bpp, non-RLE - PSD (composited view only, no extra channels, 8/16 bit-per-channel) - - GIF (*comp always reports as 4-channel) - HDR (radiance rgbE format) - PIC (Softimage PIC) - PNM (PPM and PGM binary only) - - Animated GIF still needs a proper API, but here's one way to do it: - http://gist.github.com/urraka/685d9a6340b26b830d49 - - - decode from memory or through FILE (define STBI_NO_STDIO to remove code) - - decode from arbitrary I/O callbacks - - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) - - Full documentation under "DOCUMENTATION" below. - - - Revision 2.00 release notes: - - - Progressive JPEG is now supported. - - - PPM and PGM binary formats are now supported, thanks to Ken Miller. - - - x86 platforms now make use of SSE2 SIMD instructions for - JPEG decoding, and ARM platforms can use NEON SIMD if requested. - This work was done by Fabian "ryg" Giesen. SSE2 is used by - default, but NEON must be enabled explicitly; see docs. - - With other JPEG optimizations included in this version, we see - 2x speedup on a JPEG on an x86 machine, and a 1.5x speedup - on a JPEG on an ARM machine, relative to previous versions of this - library. The same results will not obtain for all JPGs and for all - x86/ARM machines. (Note that progressive JPEGs are significantly - slower to decode than regular JPEGs.) This doesn't mean that this - is the fastest JPEG decoder in the land; rather, it brings it - closer to parity with standard libraries. If you want the fastest - decode, look elsewhere. (See "Philosophy" section of docs below.) - - See final bullet items below for more info on SIMD. - - - Added STBI_MALLOC, STBI_REALLOC, and STBI_FREE macros for replacing - the memory allocator. Unlike other STBI libraries, these macros don't - support a context parameter, so if you need to pass a context in to - the allocator, you'll have to store it in a global or a thread-local - variable. - - - Split existing STBI_NO_HDR flag into two flags, STBI_NO_HDR and - STBI_NO_LINEAR. - STBI_NO_HDR: suppress implementation of .hdr reader format - STBI_NO_LINEAR: suppress high-dynamic-range light-linear float API - - - You can suppress implementation of any of the decoders to reduce - your code footprint by #defining one or more of the following - symbols before creating the implementation. - - STBI_NO_JPEG - STBI_NO_PNG - STBI_NO_BMP - STBI_NO_PSD - STBI_NO_TGA - STBI_NO_GIF - STBI_NO_HDR - STBI_NO_PIC - STBI_NO_PNM (.ppm and .pgm) - - - You can request *only* certain decoders and suppress all other ones - (this will be more forward-compatible, as addition of new decoders - doesn't require you to disable them explicitly): - - STBI_ONLY_JPEG - STBI_ONLY_PNG - STBI_ONLY_BMP - STBI_ONLY_PSD - STBI_ONLY_TGA - STBI_ONLY_GIF - STBI_ONLY_HDR - STBI_ONLY_PIC - STBI_ONLY_PNM (.ppm and .pgm) - - Note that you can define multiples of these, and you will get all - of them ("only x" and "only y" is interpreted to mean "only x&y"). - - - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still - want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB - - - Compilation of all SIMD code can be suppressed with - #define STBI_NO_SIMD - It should not be necessary to disable SIMD unless you have issues - compiling (e.g. using an x86 compiler which doesn't support SSE - intrinsics or that doesn't support the method used to detect - SSE2 support at run-time), and even those can be reported as - bugs so I can refine the built-in compile-time checking to be - smarter. - - - The old STBI_SIMD system which allowed installing a user-defined - IDCT etc. has been removed. If you need this, don't upgrade. My - assumption is that almost nobody was doing this, and those who - were will find the built-in SIMD more satisfactory anyway. - - - RGB values computed for JPEG images are slightly different from - previous versions of stb_image. (This is due to using less - integer precision in SIMD.) The C code has been adjusted so - that the same RGB values will be computed regardless of whether - SIMD support is available, so your app should always produce - consistent results. But these results are slightly different from - previous versions. (Specifically, about 3% of available YCbCr values - will compute different RGB results from pre-1.49 versions by +-1; - most of the deviating values are one smaller in the G channel.) - - - If you must produce consistent results with previous versions of - stb_image, #define STBI_JPEG_OLD and you will get the same results - you used to; however, you will not get the SIMD speedups for - the YCbCr-to-RGB conversion step (although you should still see - significant JPEG speedup from the other changes). - - Please note that STBI_JPEG_OLD is a temporary feature; it will be - removed in future versions of the library. It is only intended for - near-term back-compatibility use. - - - Latest revision history: - 2.10 (2016-01-22) avoid warning introduced in 2.09 - 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED - 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA - 2.07 (2015-09-13) partial animated GIF support - limited 16-bit PSD support - minor bugs, code cleanup, and compiler warnings - 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value - 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning - 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit - 2.03 (2015-04-12) additional corruption checking - stbi_set_flip_vertically_on_load - fix NEON support; fix mingw support - 2.02 (2015-01-19) fix incorrect assert, fix warning - 2.01 (2015-01-17) fix various warnings - 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG - 2.00 (2014-12-25) optimize JPEG, including x86 SSE2 & ARM NEON SIMD - progressive JPEG - PGM/PPM support - STBI_MALLOC,STBI_REALLOC,STBI_FREE - STBI_NO_*, STBI_ONLY_* - GIF bugfix - 1.48 (2014-12-14) fix incorrectly-named assert() - 1.47 (2014-12-14) 1/2/4-bit PNG support (both grayscale and paletted) - optimize PNG - fix bug in interlaced PNG with user-specified channel count - - See end of file for full revision history. - - - ============================ Contributors ========================= - - Image formats Extensions, features - Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) - Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) - Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) - Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) - Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) - Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) - Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) - urraka@github (animated gif) Junggon Kim (PNM comments) - Daniel Gibson (16-bit TGA) - - Optimizations & bugfixes - Fabian "ryg" Giesen - Arseny Kapoulkine - - Bug & warning fixes - Marc LeBlanc David Woo Guillaume George Martins Mozeiko - Christpher Lloyd Martin Golini Jerry Jansson Joseph Thomson - Dave Moore Roy Eltham Hayaki Saito Phil Jordan - Won Chun Luke Graham Johan Duparc Nathan Reed - the Horde3D community Thomas Ruf Ronny Chevalier Nick Verigakis - Janez Zemva John Bartholomew Michal Cichon svdijk@github - Jonathan Blow Ken Hamada Tero Hanninen Baldur Karlsson - Laurent Gomila Cort Stratton Sergio Gonzalez romigrou@github - Aruelien Pocheville Thibault Reuille Cass Everitt - Ryamond Barbiero Paul Du Bois Engin Manap - Blazej Dariusz Roszkowski - Michaelangel007@github - - -LICENSE - -This software is in the public domain. Where that dedication is not -recognized, you are granted a perpetual, irrevocable license to copy, -distribute, and modify this file as you see fit. - -*/ - -#ifndef STBI_INCLUDE_STB_IMAGE_H -#define STBI_INCLUDE_STB_IMAGE_H - -// DOCUMENTATION -// -// Limitations: -// - no 16-bit-per-channel PNG -// - no 12-bit-per-channel JPEG -// - no JPEGs with arithmetic coding -// - no 1-bit BMP -// - GIF always returns *comp=4 -// -// Basic usage (see HDR discussion below for HDR usage): -// int x,y,n; -// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); -// // ... process data if not NULL ... -// // ... x = width, y = height, n = # 8-bit components per pixel ... -// // ... replace '0' with '1'..'4' to force that many components per pixel -// // ... but 'n' will always be the number that it would have been if you said 0 -// stbi_image_free(data) -// -// Standard parameters: -// int *x -- outputs image width in pixels -// int *y -- outputs image height in pixels -// int *comp -- outputs # of image components in image file -// int req_comp -- if non-zero, # of image components requested in result -// -// The return value from an image loader is an 'unsigned char *' which points -// to the pixel data, or NULL on an allocation failure or if the image is -// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, -// with each pixel consisting of N interleaved 8-bit components; the first -// pixel pointed to is top-left-most in the image. There is no padding between -// image scanlines or between pixels, regardless of format. The number of -// components N is 'req_comp' if req_comp is non-zero, or *comp otherwise. -// If req_comp is non-zero, *comp has the number of components that _would_ -// have been output otherwise. E.g. if you set req_comp to 4, you will always -// get RGBA output, but you can check *comp to see if it's trivially opaque -// because e.g. there were only 3 channels in the source image. -// -// An output image with N components has the following components interleaved -// in this order in each pixel: -// -// N=#comp components -// 1 grey -// 2 grey, alpha -// 3 red, green, blue -// 4 red, green, blue, alpha -// -// If image loading fails for any reason, the return value will be NULL, -// and *x, *y, *comp will be unchanged. The function stbi_failure_reason() -// can be queried for an extremely brief, end-user unfriendly explanation -// of why the load failed. Define STBI_NO_FAILURE_STRINGS to avoid -// compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly -// more user-friendly ones. -// -// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. -// -// =========================================================================== -// -// Philosophy -// -// stb libraries are designed with the following priorities: -// -// 1. easy to use -// 2. easy to maintain -// 3. good performance -// -// Sometimes I let "good performance" creep up in priority over "easy to maintain", -// and for best performance I may provide less-easy-to-use APIs that give higher -// performance, in addition to the easy to use ones. Nevertheless, it's important -// to keep in mind that from the standpoint of you, a client of this library, -// all you care about is #1 and #3, and stb libraries do not emphasize #3 above all. -// -// Some secondary priorities arise directly from the first two, some of which -// make more explicit reasons why performance can't be emphasized. -// -// - Portable ("ease of use") -// - Small footprint ("easy to maintain") -// - No dependencies ("ease of use") -// -// =========================================================================== -// -// I/O callbacks -// -// I/O callbacks allow you to read from arbitrary sources, like packaged -// files or some other source. Data read from callbacks are processed -// through a small internal buffer (currently 128 bytes) to try to reduce -// overhead. -// -// The three functions you must define are "read" (reads some bytes of data), -// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). -// -// =========================================================================== -// -// SIMD support -// -// The JPEG decoder will try to automatically use SIMD kernels on x86 when -// supported by the compiler. For ARM Neon support, you must explicitly -// request it. -// -// (The old do-it-yourself SIMD API is no longer supported in the current -// code.) -// -// On x86, SSE2 will automatically be used when available based on a run-time -// test; if not, the generic C versions are used as a fall-back. On ARM targets, -// the typical path is to have separate builds for NEON and non-NEON devices -// (at least this is true for iOS and Android). Therefore, the NEON support is -// toggled by a build flag: define STBI_NEON to get NEON loops. -// -// The output of the JPEG decoder is slightly different from versions where -// SIMD support was introduced (that is, for versions before 1.49). The -// difference is only +-1 in the 8-bit RGB channels, and only on a small -// fraction of pixels. You can force the pre-1.49 behavior by defining -// STBI_JPEG_OLD, but this will disable some of the SIMD decoding path -// and hence cost some performance. -// -// If for some reason you do not want to use any of SIMD code, or if -// you have issues compiling it, you can disable it entirely by -// defining STBI_NO_SIMD. -// -// =========================================================================== -// -// HDR image support (disable by defining STBI_NO_HDR) -// -// stb_image now supports loading HDR images in general, and currently -// the Radiance .HDR file format, although the support is provided -// generically. You can still load any file through the existing interface; -// if you attempt to load an HDR file, it will be automatically remapped to -// LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; -// both of these constants can be reconfigured through this interface: -// -// stbi_hdr_to_ldr_gamma(2.2f); -// stbi_hdr_to_ldr_scale(1.0f); -// -// (note, do not use _inverse_ constants; stbi_image will invert them -// appropriately). -// -// Additionally, there is a new, parallel interface for loading files as -// (linear) floats to preserve the full dynamic range: -// -// float *data = stbi_loadf(filename, &x, &y, &n, 0); -// -// If you load LDR images through this interface, those images will -// be promoted to floating point values, run through the inverse of -// constants corresponding to the above: -// -// stbi_ldr_to_hdr_scale(1.0f); -// stbi_ldr_to_hdr_gamma(2.2f); -// -// Finally, given a filename (or an open file or memory block--see header -// file for details) containing image data, you can query for the "most -// appropriate" interface to use (that is, whether the image is HDR or -// not), using: -// -// stbi_is_hdr(char *filename); -// -// =========================================================================== -// -// iPhone PNG support: -// -// By default we convert iphone-formatted PNGs back to RGB, even though -// they are internally encoded differently. You can disable this conversion -// by by calling stbi_convert_iphone_png_to_rgb(0), in which case -// you will always just get the native iphone "format" through (which -// is BGR stored in RGB). -// -// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per -// pixel to remove any premultiplied alpha *only* if the image file explicitly -// says there's premultiplied data (currently only happens in iPhone images, -// and only if iPhone convert-to-rgb processing is on). -// - - -#ifndef STBI_NO_STDIO -#include -#endif // STBI_NO_STDIO - -#define STBI_VERSION 1 - -enum -{ - STBI_default = 0, // only used for req_comp - - STBI_grey = 1, - STBI_grey_alpha = 2, - STBI_rgb = 3, - STBI_rgb_alpha = 4 -}; - -typedef unsigned char stbi_uc; - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef STB_IMAGE_STATIC -#define STBIDEF static -#else -#define STBIDEF extern -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// PRIMARY API - works on images of any type -// - -// -// load image by filename, open file, or memory buffer -// - -typedef struct -{ - int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read - void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative - int (*eof) (void *user); // returns nonzero if we are at end of file/data -} stbi_io_callbacks; - -STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *comp, int req_comp); -STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *comp, int req_comp); -STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *comp, int req_comp); - -#ifndef STBI_NO_STDIO -STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); -// for stbi_load_from_file, file pointer is left pointing immediately after image -#endif - -#ifndef STBI_NO_LINEAR - STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *comp, int req_comp); - STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); - STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp); - - #ifndef STBI_NO_STDIO - STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); - #endif -#endif - -#ifndef STBI_NO_HDR - STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); - STBIDEF void stbi_hdr_to_ldr_scale(float scale); -#endif // STBI_NO_HDR - -#ifndef STBI_NO_LINEAR - STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); - STBIDEF void stbi_ldr_to_hdr_scale(float scale); -#endif // STBI_NO_LINEAR - -// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR -STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); -STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); -#ifndef STBI_NO_STDIO -STBIDEF int stbi_is_hdr (char const *filename); -STBIDEF int stbi_is_hdr_from_file(FILE *f); -#endif // STBI_NO_STDIO - - -// get a VERY brief reason for failure -// NOT THREADSAFE -STBIDEF const char *stbi_failure_reason (void); - -// free the loaded image -- this is just free() -STBIDEF void stbi_image_free (void *retval_from_stbi_load); - -// get image dimensions & components without fully decoding -STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); -STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); - -#ifndef STBI_NO_STDIO -STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); -STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); - -#endif - - - -// for image formats that explicitly notate that they have premultiplied alpha, -// we just return the colors as stored in the file. set this flag to force -// unpremultiplication. results are undefined if the unpremultiply overflow. -STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); - -// indicate whether we should process iphone images back to canonical format, -// or just pass them through "as-is" -STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); - -// flip the image vertically, so the first pixel in the output array is the bottom left -STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); - -// ZLIB client - used by PNG, available for other purposes - -STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); -STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); -STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); -STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); - -STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); -STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); - - -#ifdef __cplusplus -} -#endif - -// -// -//// end header file ///////////////////////////////////////////////////// -#endif // STBI_INCLUDE_STB_IMAGE_H - -#ifdef STB_IMAGE_IMPLEMENTATION - -#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ - || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ - || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ - || defined(STBI_ONLY_ZLIB) - #ifndef STBI_ONLY_JPEG - #define STBI_NO_JPEG - #endif - #ifndef STBI_ONLY_PNG - #define STBI_NO_PNG - #endif - #ifndef STBI_ONLY_BMP - #define STBI_NO_BMP - #endif - #ifndef STBI_ONLY_PSD - #define STBI_NO_PSD - #endif - #ifndef STBI_ONLY_TGA - #define STBI_NO_TGA - #endif - #ifndef STBI_ONLY_GIF - #define STBI_NO_GIF - #endif - #ifndef STBI_ONLY_HDR - #define STBI_NO_HDR - #endif - #ifndef STBI_ONLY_PIC - #define STBI_NO_PIC - #endif - #ifndef STBI_ONLY_PNM - #define STBI_NO_PNM - #endif -#endif - -#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) -#define STBI_NO_ZLIB -#endif - - -#include -#include // ptrdiff_t on osx -#include -#include - -#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) -#include // ldexp -#endif - -#ifndef STBI_NO_STDIO -#include -#endif - -#ifndef STBI_ASSERT -#include -#define STBI_ASSERT(x) assert(x) -#endif - - -#ifndef _MSC_VER - #ifdef __cplusplus - #define stbi_inline inline - #else - #define stbi_inline - #endif -#else - #define stbi_inline __forceinline -#endif - - -#ifdef _MSC_VER -typedef unsigned short stbi__uint16; -typedef signed short stbi__int16; -typedef unsigned int stbi__uint32; -typedef signed int stbi__int32; -#else -#include -typedef uint16_t stbi__uint16; -typedef int16_t stbi__int16; -typedef uint32_t stbi__uint32; -typedef int32_t stbi__int32; -#endif - -// should produce compiler error if size is wrong -typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; - -#ifdef _MSC_VER -#define STBI_NOTUSED(v) (void)(v) -#else -#define STBI_NOTUSED(v) (void)sizeof(v) -#endif - -#ifdef _MSC_VER -#define STBI_HAS_LROTL -#endif - -#ifdef STBI_HAS_LROTL - #define stbi_lrot(x,y) _lrotl(x,y) -#else - #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y)))) -#endif - -#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) -// ok -#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) -// ok -#else -#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." -#endif - -#ifndef STBI_MALLOC -#define STBI_MALLOC(sz) malloc(sz) -#define STBI_REALLOC(p,newsz) realloc(p,newsz) -#define STBI_FREE(p) free(p) -#endif - -#ifndef STBI_REALLOC_SIZED -#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) -#endif - -// x86/x64 detection -#if defined(__x86_64__) || defined(_M_X64) -#define STBI__X64_TARGET -#elif defined(__i386) || defined(_M_IX86) -#define STBI__X86_TARGET -#endif - -#if defined(__GNUC__) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) -// NOTE: not clear do we actually need this for the 64-bit path? -// gcc doesn't support sse2 intrinsics unless you compile with -msse2, -// (but compiling with -msse2 allows the compiler to use SSE2 everywhere; -// this is just broken and gcc are jerks for not fixing it properly -// http://www.virtualdub.org/blog/pivot/entry.php?id=363 ) -#define STBI_NO_SIMD -#endif - -#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) -// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET -// -// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the -// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. -// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not -// simultaneously enabling "-mstackrealign". -// -// See https://github.com/nothings/stb/issues/81 for more information. -// -// So default to no SSE2 on 32-bit MinGW. If you've read this far and added -// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. -#define STBI_NO_SIMD -#endif - -#if !defined(STBI_NO_SIMD) && defined(STBI__X86_TARGET) -#define STBI_SSE2 -#include - -#ifdef _MSC_VER - -#if _MSC_VER >= 1400 // not VC6 -#include // __cpuid -static int stbi__cpuid3(void) -{ - int info[4]; - __cpuid(info,1); - return info[3]; -} -#else -static int stbi__cpuid3(void) -{ - int res; - __asm { - mov eax,1 - cpuid - mov res,edx - } - return res; -} -#endif - -#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name - -static int stbi__sse2_available() -{ - int info3 = stbi__cpuid3(); - return ((info3 >> 26) & 1) != 0; -} -#else // assume GCC-style if not VC++ -#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) - -static int stbi__sse2_available() -{ -#if defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 408 // GCC 4.8 or later - // GCC 4.8+ has a nice way to do this - return __builtin_cpu_supports("sse2"); -#else - // portable way to do this, preferably without using GCC inline ASM? - // just bail for now. - return 0; -#endif -} -#endif -#endif - -// ARM NEON -#if defined(STBI_NO_SIMD) && defined(STBI_NEON) -#undef STBI_NEON -#endif - -#ifdef STBI_NEON -#include -// assume GCC or Clang on ARM targets -#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) -#endif - -#ifndef STBI_SIMD_ALIGN -#define STBI_SIMD_ALIGN(type, name) type name -#endif - -/////////////////////////////////////////////// -// -// stbi__context struct and start_xxx functions - -// stbi__context structure is our basic context used by all images, so it -// contains all the IO context, plus some basic image information -typedef struct -{ - stbi__uint32 img_x, img_y; - int img_n, img_out_n; - - stbi_io_callbacks io; - void *io_user_data; - - int read_from_callbacks; - int buflen; - stbi_uc buffer_start[128]; - - stbi_uc *img_buffer, *img_buffer_end; - stbi_uc *img_buffer_original, *img_buffer_original_end; -} stbi__context; - - -static void stbi__refill_buffer(stbi__context *s); - -// initialize a memory-decode context -static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) -{ - s->io.read = NULL; - s->read_from_callbacks = 0; - s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; - s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; -} - -// initialize a callback-based context -static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) -{ - s->io = *c; - s->io_user_data = user; - s->buflen = sizeof(s->buffer_start); - s->read_from_callbacks = 1; - s->img_buffer_original = s->buffer_start; - stbi__refill_buffer(s); - s->img_buffer_original_end = s->img_buffer_end; -} - -#ifndef STBI_NO_STDIO - -static int stbi__stdio_read(void *user, char *data, int size) -{ - return (int) fread(data,1,size,(FILE*) user); -} - -static void stbi__stdio_skip(void *user, int n) -{ - fseek((FILE*) user, n, SEEK_CUR); -} - -static int stbi__stdio_eof(void *user) -{ - return feof((FILE*) user); -} - -static stbi_io_callbacks stbi__stdio_callbacks = -{ - stbi__stdio_read, - stbi__stdio_skip, - stbi__stdio_eof, -}; - -static void stbi__start_file(stbi__context *s, FILE *f) -{ - stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); -} - -//static void stop_file(stbi__context *s) { } - -#endif // !STBI_NO_STDIO - -static void stbi__rewind(stbi__context *s) -{ - // conceptually rewind SHOULD rewind to the beginning of the stream, - // but we just rewind to the beginning of the initial buffer, because - // we only use it after doing 'test', which only ever looks at at most 92 bytes - s->img_buffer = s->img_buffer_original; - s->img_buffer_end = s->img_buffer_original_end; -} - -#ifndef STBI_NO_JPEG -static int stbi__jpeg_test(stbi__context *s); -static stbi_uc *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); -static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PNG -static int stbi__png_test(stbi__context *s); -static stbi_uc *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); -static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_BMP -static int stbi__bmp_test(stbi__context *s); -static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); -static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_TGA -static int stbi__tga_test(stbi__context *s); -static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); -static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PSD -static int stbi__psd_test(stbi__context *s); -static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); -static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_HDR -static int stbi__hdr_test(stbi__context *s); -static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); -static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PIC -static int stbi__pic_test(stbi__context *s); -static stbi_uc *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); -static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_GIF -static int stbi__gif_test(stbi__context *s); -static stbi_uc *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); -static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PNM -static int stbi__pnm_test(stbi__context *s); -static stbi_uc *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); -static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -// this is not threadsafe -static const char *stbi__g_failure_reason; - -STBIDEF const char *stbi_failure_reason(void) -{ - return stbi__g_failure_reason; -} - -static int stbi__err(const char *str) -{ - stbi__g_failure_reason = str; - return 0; -} - -static void *stbi__malloc(size_t size) -{ - return STBI_MALLOC(size); -} - -// stbi__err - error -// stbi__errpf - error returning pointer to float -// stbi__errpuc - error returning pointer to unsigned char - -#ifdef STBI_NO_FAILURE_STRINGS - #define stbi__err(x,y) 0 -#elif defined(STBI_FAILURE_USERMSG) - #define stbi__err(x,y) stbi__err(y) -#else - #define stbi__err(x,y) stbi__err(x) -#endif - -#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) -#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) - -STBIDEF void stbi_image_free(void *retval_from_stbi_load) -{ - STBI_FREE(retval_from_stbi_load); -} - -#ifndef STBI_NO_LINEAR -static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); -#endif - -#ifndef STBI_NO_HDR -static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); -#endif - -static int stbi__vertically_flip_on_load = 0; - -STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) -{ - stbi__vertically_flip_on_load = flag_true_if_should_flip; -} - -static unsigned char *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - #ifndef STBI_NO_JPEG - if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp); - #endif - #ifndef STBI_NO_PNG - if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp); - #endif - #ifndef STBI_NO_BMP - if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp); - #endif - #ifndef STBI_NO_GIF - if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp); - #endif - #ifndef STBI_NO_PSD - if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp); - #endif - #ifndef STBI_NO_PIC - if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp); - #endif - #ifndef STBI_NO_PNM - if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp); - #endif - - #ifndef STBI_NO_HDR - if (stbi__hdr_test(s)) { - float *hdr = stbi__hdr_load(s, x,y,comp,req_comp); - return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); - } - #endif - - #ifndef STBI_NO_TGA - // test tga last because it's a crappy test! - if (stbi__tga_test(s)) - return stbi__tga_load(s,x,y,comp,req_comp); - #endif - - return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); -} - -static unsigned char *stbi__load_flip(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - unsigned char *result = stbi__load_main(s, x, y, comp, req_comp); - - if (stbi__vertically_flip_on_load && result != NULL) { - int w = *x, h = *y; - int depth = req_comp ? req_comp : *comp; - int row,col,z; - stbi_uc temp; - - // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once - for (row = 0; row < (h>>1); row++) { - for (col = 0; col < w; col++) { - for (z = 0; z < depth; z++) { - temp = result[(row * w + col) * depth + z]; - result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z]; - result[((h - row - 1) * w + col) * depth + z] = temp; - } - } - } - } - - return result; -} - -#ifndef STBI_NO_HDR -static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) -{ - if (stbi__vertically_flip_on_load && result != NULL) { - int w = *x, h = *y; - int depth = req_comp ? req_comp : *comp; - int row,col,z; - float temp; - - // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once - for (row = 0; row < (h>>1); row++) { - for (col = 0; col < w; col++) { - for (z = 0; z < depth; z++) { - temp = result[(row * w + col) * depth + z]; - result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z]; - result[((h - row - 1) * w + col) * depth + z] = temp; - } - } - } - } -} -#endif - -#ifndef STBI_NO_STDIO - -static FILE *stbi__fopen(char const *filename, char const *mode) -{ - FILE *f; -#if defined(_MSC_VER) && _MSC_VER >= 1400 - if (0 != fopen_s(&f, filename, mode)) - f=0; -#else - f = fopen(filename, mode); -#endif - return f; -} - - -STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - FILE *f = stbi__fopen(filename, "rb"); - unsigned char *result; - if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); - result = stbi_load_from_file(f,x,y,comp,req_comp); - fclose(f); - return result; -} - -STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - unsigned char *result; - stbi__context s; - stbi__start_file(&s,f); - result = stbi__load_flip(&s,x,y,comp,req_comp); - if (result) { - // need to 'unget' all the characters in the IO buffer - fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); - } - return result; -} -#endif //!STBI_NO_STDIO - -STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__load_flip(&s,x,y,comp,req_comp); -} - -STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__load_flip(&s,x,y,comp,req_comp); -} - -#ifndef STBI_NO_LINEAR -static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - unsigned char *data; - #ifndef STBI_NO_HDR - if (stbi__hdr_test(s)) { - float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp); - if (hdr_data) - stbi__float_postprocess(hdr_data,x,y,comp,req_comp); - return hdr_data; - } - #endif - data = stbi__load_flip(s, x, y, comp, req_comp); - if (data) - return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); - return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); -} - -STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__loadf_main(&s,x,y,comp,req_comp); -} - -STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__loadf_main(&s,x,y,comp,req_comp); -} - -#ifndef STBI_NO_STDIO -STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - float *result; - FILE *f = stbi__fopen(filename, "rb"); - if (!f) return stbi__errpf("can't fopen", "Unable to open file"); - result = stbi_loadf_from_file(f,x,y,comp,req_comp); - fclose(f); - return result; -} - -STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_file(&s,f); - return stbi__loadf_main(&s,x,y,comp,req_comp); -} -#endif // !STBI_NO_STDIO - -#endif // !STBI_NO_LINEAR - -// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is -// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always -// reports false! - -STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) -{ - #ifndef STBI_NO_HDR - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__hdr_test(&s); - #else - STBI_NOTUSED(buffer); - STBI_NOTUSED(len); - return 0; - #endif -} - -#ifndef STBI_NO_STDIO -STBIDEF int stbi_is_hdr (char const *filename) -{ - FILE *f = stbi__fopen(filename, "rb"); - int result=0; - if (f) { - result = stbi_is_hdr_from_file(f); - fclose(f); - } - return result; -} - -STBIDEF int stbi_is_hdr_from_file(FILE *f) -{ - #ifndef STBI_NO_HDR - stbi__context s; - stbi__start_file(&s,f); - return stbi__hdr_test(&s); - #else - STBI_NOTUSED(f); - return 0; - #endif -} -#endif // !STBI_NO_STDIO - -STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) -{ - #ifndef STBI_NO_HDR - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__hdr_test(&s); - #else - STBI_NOTUSED(clbk); - STBI_NOTUSED(user); - return 0; - #endif -} - -#ifndef STBI_NO_LINEAR -static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; - -STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } -STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } -#endif - -static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; - -STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } -STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } - - -////////////////////////////////////////////////////////////////////////////// -// -// Common code used by all image loaders -// - -enum -{ - STBI__SCAN_load=0, - STBI__SCAN_type, - STBI__SCAN_header -}; - -static void stbi__refill_buffer(stbi__context *s) -{ - int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); - if (n == 0) { - // at end of file, treat same as if from memory, but need to handle case - // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file - s->read_from_callbacks = 0; - s->img_buffer = s->buffer_start; - s->img_buffer_end = s->buffer_start+1; - *s->img_buffer = 0; - } else { - s->img_buffer = s->buffer_start; - s->img_buffer_end = s->buffer_start + n; - } -} - -stbi_inline static stbi_uc stbi__get8(stbi__context *s) -{ - if (s->img_buffer < s->img_buffer_end) - return *s->img_buffer++; - if (s->read_from_callbacks) { - stbi__refill_buffer(s); - return *s->img_buffer++; - } - return 0; -} - -stbi_inline static int stbi__at_eof(stbi__context *s) -{ - if (s->io.read) { - if (!(s->io.eof)(s->io_user_data)) return 0; - // if feof() is true, check if buffer = end - // special case: we've only got the special 0 character at the end - if (s->read_from_callbacks == 0) return 1; - } - - return s->img_buffer >= s->img_buffer_end; -} - -static void stbi__skip(stbi__context *s, int n) -{ - if (n < 0) { - s->img_buffer = s->img_buffer_end; - return; - } - if (s->io.read) { - int blen = (int) (s->img_buffer_end - s->img_buffer); - if (blen < n) { - s->img_buffer = s->img_buffer_end; - (s->io.skip)(s->io_user_data, n - blen); - return; - } - } - s->img_buffer += n; -} - -static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) -{ - if (s->io.read) { - int blen = (int) (s->img_buffer_end - s->img_buffer); - if (blen < n) { - int res, count; - - memcpy(buffer, s->img_buffer, blen); - - count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); - res = (count == (n-blen)); - s->img_buffer = s->img_buffer_end; - return res; - } - } - - if (s->img_buffer+n <= s->img_buffer_end) { - memcpy(buffer, s->img_buffer, n); - s->img_buffer += n; - return 1; - } else - return 0; -} - -static int stbi__get16be(stbi__context *s) -{ - int z = stbi__get8(s); - return (z << 8) + stbi__get8(s); -} - -static stbi__uint32 stbi__get32be(stbi__context *s) -{ - stbi__uint32 z = stbi__get16be(s); - return (z << 16) + stbi__get16be(s); -} - -#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) -// nothing -#else -static int stbi__get16le(stbi__context *s) -{ - int z = stbi__get8(s); - return z + (stbi__get8(s) << 8); -} -#endif - -#ifndef STBI_NO_BMP -static stbi__uint32 stbi__get32le(stbi__context *s) -{ - stbi__uint32 z = stbi__get16le(s); - return z + (stbi__get16le(s) << 16); -} -#endif - -#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings - - -////////////////////////////////////////////////////////////////////////////// -// -// generic converter from built-in img_n to req_comp -// individual types do this automatically as much as possible (e.g. jpeg -// does all cases internally since it needs to colorspace convert anyway, -// and it never has alpha, so very few cases ). png can automatically -// interleave an alpha=255 channel, but falls back to this for other cases -// -// assume data buffer is malloced, so malloc a new one and free that one -// only failure mode is malloc failing - -static stbi_uc stbi__compute_y(int r, int g, int b) -{ - return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); -} - -static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) -{ - int i,j; - unsigned char *good; - - if (req_comp == img_n) return data; - STBI_ASSERT(req_comp >= 1 && req_comp <= 4); - - good = (unsigned char *) stbi__malloc(req_comp * x * y); - if (good == NULL) { - STBI_FREE(data); - return stbi__errpuc("outofmem", "Out of memory"); - } - - for (j=0; j < (int) y; ++j) { - unsigned char *src = data + j * x * img_n ; - unsigned char *dest = good + j * x * req_comp; - - #define COMBO(a,b) ((a)*8+(b)) - #define CASE(a,b) case COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) - // convert source image with img_n components to one with req_comp components; - // avoid switch per pixel, so use switch per scanline and massive macros - switch (COMBO(img_n, req_comp)) { - CASE(1,2) dest[0]=src[0], dest[1]=255; break; - CASE(1,3) dest[0]=dest[1]=dest[2]=src[0]; break; - CASE(1,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; break; - CASE(2,1) dest[0]=src[0]; break; - CASE(2,3) dest[0]=dest[1]=dest[2]=src[0]; break; - CASE(2,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; break; - CASE(3,4) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; break; - CASE(3,1) dest[0]=stbi__compute_y(src[0],src[1],src[2]); break; - CASE(3,2) dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = 255; break; - CASE(4,1) dest[0]=stbi__compute_y(src[0],src[1],src[2]); break; - CASE(4,2) dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = src[3]; break; - CASE(4,3) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; break; - default: STBI_ASSERT(0); - } - #undef CASE - } - - STBI_FREE(data); - return good; -} - -#ifndef STBI_NO_LINEAR -static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) -{ - int i,k,n; - float *output = (float *) stbi__malloc(x * y * comp * sizeof(float)); - if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } - // compute number of non-alpha components - if (comp & 1) n = comp; else n = comp-1; - for (i=0; i < x*y; ++i) { - for (k=0; k < n; ++k) { - output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); - } - if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f; - } - STBI_FREE(data); - return output; -} -#endif - -#ifndef STBI_NO_HDR -#define stbi__float2int(x) ((int) (x)) -static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) -{ - int i,k,n; - stbi_uc *output = (stbi_uc *) stbi__malloc(x * y * comp); - if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } - // compute number of non-alpha components - if (comp & 1) n = comp; else n = comp-1; - for (i=0; i < x*y; ++i) { - for (k=0; k < n; ++k) { - float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; - if (z < 0) z = 0; - if (z > 255) z = 255; - output[i*comp + k] = (stbi_uc) stbi__float2int(z); - } - if (k < comp) { - float z = data[i*comp+k] * 255 + 0.5f; - if (z < 0) z = 0; - if (z > 255) z = 255; - output[i*comp + k] = (stbi_uc) stbi__float2int(z); - } - } - STBI_FREE(data); - return output; -} -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// "baseline" JPEG/JFIF decoder -// -// simple implementation -// - doesn't support delayed output of y-dimension -// - simple interface (only one output format: 8-bit interleaved RGB) -// - doesn't try to recover corrupt jpegs -// - doesn't allow partial loading, loading multiple at once -// - still fast on x86 (copying globals into locals doesn't help x86) -// - allocates lots of intermediate memory (full size of all components) -// - non-interleaved case requires this anyway -// - allows good upsampling (see next) -// high-quality -// - upsampled channels are bilinearly interpolated, even across blocks -// - quality integer IDCT derived from IJG's 'slow' -// performance -// - fast huffman; reasonable integer IDCT -// - some SIMD kernels for common paths on targets with SSE2/NEON -// - uses a lot of intermediate memory, could cache poorly - -#ifndef STBI_NO_JPEG - -// huffman decoding acceleration -#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache - -typedef struct -{ - stbi_uc fast[1 << FAST_BITS]; - // weirdly, repacking this into AoS is a 10% speed loss, instead of a win - stbi__uint16 code[256]; - stbi_uc values[256]; - stbi_uc size[257]; - unsigned int maxcode[18]; - int delta[17]; // old 'firstsymbol' - old 'firstcode' -} stbi__huffman; - -typedef struct -{ - stbi__context *s; - stbi__huffman huff_dc[4]; - stbi__huffman huff_ac[4]; - stbi_uc dequant[4][64]; - stbi__int16 fast_ac[4][1 << FAST_BITS]; - -// sizes for components, interleaved MCUs - int img_h_max, img_v_max; - int img_mcu_x, img_mcu_y; - int img_mcu_w, img_mcu_h; - -// definition of jpeg image component - struct - { - int id; - int h,v; - int tq; - int hd,ha; - int dc_pred; - - int x,y,w2,h2; - stbi_uc *data; - void *raw_data, *raw_coeff; - stbi_uc *linebuf; - short *coeff; // progressive only - int coeff_w, coeff_h; // number of 8x8 coefficient blocks - } img_comp[4]; - - stbi__uint32 code_buffer; // jpeg entropy-coded buffer - int code_bits; // number of valid bits - unsigned char marker; // marker seen while filling entropy buffer - int nomore; // flag if we saw a marker so must stop - - int progressive; - int spec_start; - int spec_end; - int succ_high; - int succ_low; - int eob_run; - - int scan_n, order[4]; - int restart_interval, todo; - -// kernels - void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); - void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); - stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); -} stbi__jpeg; - -static int stbi__build_huffman(stbi__huffman *h, int *count) -{ - int i,j,k=0,code; - // build size list for each symbol (from JPEG spec) - for (i=0; i < 16; ++i) - for (j=0; j < count[i]; ++j) - h->size[k++] = (stbi_uc) (i+1); - h->size[k] = 0; - - // compute actual symbols (from jpeg spec) - code = 0; - k = 0; - for(j=1; j <= 16; ++j) { - // compute delta to add to code to compute symbol id - h->delta[j] = k - code; - if (h->size[k] == j) { - while (h->size[k] == j) - h->code[k++] = (stbi__uint16) (code++); - if (code-1 >= (1 << j)) return stbi__err("bad code lengths","Corrupt JPEG"); - } - // compute largest code + 1 for this size, preshifted as needed later - h->maxcode[j] = code << (16-j); - code <<= 1; - } - h->maxcode[j] = 0xffffffff; - - // build non-spec acceleration table; 255 is flag for not-accelerated - memset(h->fast, 255, 1 << FAST_BITS); - for (i=0; i < k; ++i) { - int s = h->size[i]; - if (s <= FAST_BITS) { - int c = h->code[i] << (FAST_BITS-s); - int m = 1 << (FAST_BITS-s); - for (j=0; j < m; ++j) { - h->fast[c+j] = (stbi_uc) i; - } - } - } - return 1; -} - -// build a table that decodes both magnitude and value of small ACs in -// one go. -static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) -{ - int i; - for (i=0; i < (1 << FAST_BITS); ++i) { - stbi_uc fast = h->fast[i]; - fast_ac[i] = 0; - if (fast < 255) { - int rs = h->values[fast]; - int run = (rs >> 4) & 15; - int magbits = rs & 15; - int len = h->size[fast]; - - if (magbits && len + magbits <= FAST_BITS) { - // magnitude code followed by receive_extend code - int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); - int m = 1 << (magbits - 1); - if (k < m) k += (-1 << magbits) + 1; - // if the result is small enough, we can fit it in fast_ac table - if (k >= -128 && k <= 127) - fast_ac[i] = (stbi__int16) ((k << 8) + (run << 4) + (len + magbits)); - } - } - } -} - -static void stbi__grow_buffer_unsafe(stbi__jpeg *j) -{ - do { - int b = j->nomore ? 0 : stbi__get8(j->s); - if (b == 0xff) { - int c = stbi__get8(j->s); - if (c != 0) { - j->marker = (unsigned char) c; - j->nomore = 1; - return; - } - } - j->code_buffer |= b << (24 - j->code_bits); - j->code_bits += 8; - } while (j->code_bits <= 24); -} - -// (1 << n) - 1 -static stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; - -// decode a jpeg huffman value from the bitstream -stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) -{ - unsigned int temp; - int c,k; - - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - - // look at the top FAST_BITS and determine what symbol ID it is, - // if the code is <= FAST_BITS - c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); - k = h->fast[c]; - if (k < 255) { - int s = h->size[k]; - if (s > j->code_bits) - return -1; - j->code_buffer <<= s; - j->code_bits -= s; - return h->values[k]; - } - - // naive test is to shift the code_buffer down so k bits are - // valid, then test against maxcode. To speed this up, we've - // preshifted maxcode left so that it has (16-k) 0s at the - // end; in other words, regardless of the number of bits, it - // wants to be compared against something shifted to have 16; - // that way we don't need to shift inside the loop. - temp = j->code_buffer >> 16; - for (k=FAST_BITS+1 ; ; ++k) - if (temp < h->maxcode[k]) - break; - if (k == 17) { - // error! code not found - j->code_bits -= 16; - return -1; - } - - if (k > j->code_bits) - return -1; - - // convert the huffman code to the symbol id - c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; - STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); - - // convert the id to a symbol - j->code_bits -= k; - j->code_buffer <<= k; - return h->values[c]; -} - -// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); - - sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB - k = stbi_lrot(j->code_buffer, n); - STBI_ASSERT(n >= 0 && n < (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask))); - j->code_buffer = k & ~stbi__bmask[n]; - k &= stbi__bmask[n]; - j->code_bits -= n; - return k + (stbi__jbias[n] & ~sgn); -} - -// get some unsigned bits -stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) -{ - unsigned int k; - if (j->code_bits < n) stbi__grow_buffer_unsafe(j); - k = stbi_lrot(j->code_buffer, n); - j->code_buffer = k & ~stbi__bmask[n]; - k &= stbi__bmask[n]; - j->code_bits -= n; - return k; -} - -stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) -{ - unsigned int k; - if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); - k = j->code_buffer; - j->code_buffer <<= 1; - --j->code_bits; - return k & 0x80000000; -} - -// given a value that's at position X in the zigzag stream, -// where does it appear in the 8x8 matrix coded as row-major? -static stbi_uc stbi__jpeg_dezigzag[64+15] = -{ - 0, 1, 8, 16, 9, 2, 3, 10, - 17, 24, 32, 25, 18, 11, 4, 5, - 12, 19, 26, 33, 40, 48, 41, 34, - 27, 20, 13, 6, 7, 14, 21, 28, - 35, 42, 49, 56, 57, 50, 43, 36, - 29, 22, 15, 23, 30, 37, 44, 51, - 58, 59, 52, 45, 38, 31, 39, 46, - 53, 60, 61, 54, 47, 55, 62, 63, - // let corrupt input sample past end - 63, 63, 63, 63, 63, 63, 63, 63, - 63, 63, 63, 63, 63, 63, 63 -}; - -// decode one 64-entry block-- -static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi_uc *dequant) -{ - int diff,dc,k; - int t; - - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - t = stbi__jpeg_huff_decode(j, hdc); - if (t < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - - // 0 all the ac values now so we can do it 32-bits at a time - memset(data,0,64*sizeof(data[0])); - - diff = t ? stbi__extend_receive(j, t) : 0; - dc = j->img_comp[b].dc_pred + diff; - j->img_comp[b].dc_pred = dc; - data[0] = (short) (dc * dequant[0]); - - // decode AC components, see JPEG spec - k = 1; - do { - unsigned int zig; - int c,r,s; - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); - r = fac[c]; - if (r) { // fast-AC path - k += (r >> 4) & 15; // run - s = r & 15; // combined length - j->code_buffer <<= s; - j->code_bits -= s; - // decode into unzigzag'd location - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) ((r >> 8) * dequant[zig]); - } else { - int rs = stbi__jpeg_huff_decode(j, hac); - if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - s = rs & 15; - r = rs >> 4; - if (s == 0) { - if (rs != 0xf0) break; // end block - k += 16; - } else { - k += r; - // decode into unzigzag'd location - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); - } - } - } while (k < 64); - return 1; -} - -static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) -{ - int diff,dc; - int t; - if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); - - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - - if (j->succ_high == 0) { - // first scan for DC coefficient, must be first - memset(data,0,64*sizeof(data[0])); // 0 all the ac values now - t = stbi__jpeg_huff_decode(j, hdc); - diff = t ? stbi__extend_receive(j, t) : 0; - - dc = j->img_comp[b].dc_pred + diff; - j->img_comp[b].dc_pred = dc; - data[0] = (short) (dc << j->succ_low); - } else { - // refinement scan for DC coefficient - if (stbi__jpeg_get_bit(j)) - data[0] += (short) (1 << j->succ_low); - } - return 1; -} - -// @OPTIMIZE: store non-zigzagged during the decode passes, -// and only de-zigzag when dequantizing -static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) -{ - int k; - if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); - - if (j->succ_high == 0) { - int shift = j->succ_low; - - if (j->eob_run) { - --j->eob_run; - return 1; - } - - k = j->spec_start; - do { - unsigned int zig; - int c,r,s; - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); - r = fac[c]; - if (r) { // fast-AC path - k += (r >> 4) & 15; // run - s = r & 15; // combined length - j->code_buffer <<= s; - j->code_bits -= s; - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) ((r >> 8) << shift); - } else { - int rs = stbi__jpeg_huff_decode(j, hac); - if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - s = rs & 15; - r = rs >> 4; - if (s == 0) { - if (r < 15) { - j->eob_run = (1 << r); - if (r) - j->eob_run += stbi__jpeg_get_bits(j, r); - --j->eob_run; - break; - } - k += 16; - } else { - k += r; - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) (stbi__extend_receive(j,s) << shift); - } - } - } while (k <= j->spec_end); - } else { - // refinement scan for these AC coefficients - - short bit = (short) (1 << j->succ_low); - - if (j->eob_run) { - --j->eob_run; - for (k = j->spec_start; k <= j->spec_end; ++k) { - short *p = &data[stbi__jpeg_dezigzag[k]]; - if (*p != 0) - if (stbi__jpeg_get_bit(j)) - if ((*p & bit)==0) { - if (*p > 0) - *p += bit; - else - *p -= bit; - } - } - } else { - k = j->spec_start; - do { - int r,s; - int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh - if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - s = rs & 15; - r = rs >> 4; - if (s == 0) { - if (r < 15) { - j->eob_run = (1 << r) - 1; - if (r) - j->eob_run += stbi__jpeg_get_bits(j, r); - r = 64; // force end of block - } else { - // r=15 s=0 should write 16 0s, so we just do - // a run of 15 0s and then write s (which is 0), - // so we don't have to do anything special here - } - } else { - if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); - // sign bit - if (stbi__jpeg_get_bit(j)) - s = bit; - else - s = -bit; - } - - // advance by r - while (k <= j->spec_end) { - short *p = &data[stbi__jpeg_dezigzag[k++]]; - if (*p != 0) { - if (stbi__jpeg_get_bit(j)) - if ((*p & bit)==0) { - if (*p > 0) - *p += bit; - else - *p -= bit; - } - } else { - if (r == 0) { - *p = (short) s; - break; - } - --r; - } - } - } while (k <= j->spec_end); - } - } - return 1; -} - -// take a -128..127 value and stbi__clamp it and convert to 0..255 -stbi_inline static stbi_uc stbi__clamp(int x) -{ - // trick to use a single test to catch both cases - if ((unsigned int) x > 255) { - if (x < 0) return 0; - if (x > 255) return 255; - } - return (stbi_uc) x; -} - -#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) -#define stbi__fsh(x) ((x) << 12) - -// derived from jidctint -- DCT_ISLOW -#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ - int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ - p2 = s2; \ - p3 = s6; \ - p1 = (p2+p3) * stbi__f2f(0.5411961f); \ - t2 = p1 + p3*stbi__f2f(-1.847759065f); \ - t3 = p1 + p2*stbi__f2f( 0.765366865f); \ - p2 = s0; \ - p3 = s4; \ - t0 = stbi__fsh(p2+p3); \ - t1 = stbi__fsh(p2-p3); \ - x0 = t0+t3; \ - x3 = t0-t3; \ - x1 = t1+t2; \ - x2 = t1-t2; \ - t0 = s7; \ - t1 = s5; \ - t2 = s3; \ - t3 = s1; \ - p3 = t0+t2; \ - p4 = t1+t3; \ - p1 = t0+t3; \ - p2 = t1+t2; \ - p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ - t0 = t0*stbi__f2f( 0.298631336f); \ - t1 = t1*stbi__f2f( 2.053119869f); \ - t2 = t2*stbi__f2f( 3.072711026f); \ - t3 = t3*stbi__f2f( 1.501321110f); \ - p1 = p5 + p1*stbi__f2f(-0.899976223f); \ - p2 = p5 + p2*stbi__f2f(-2.562915447f); \ - p3 = p3*stbi__f2f(-1.961570560f); \ - p4 = p4*stbi__f2f(-0.390180644f); \ - t3 += p1+p4; \ - t2 += p2+p3; \ - t1 += p2+p4; \ - t0 += p1+p3; - -static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) -{ - int i,val[64],*v=val; - stbi_uc *o; - short *d = data; - - // columns - for (i=0; i < 8; ++i,++d, ++v) { - // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing - if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 - && d[40]==0 && d[48]==0 && d[56]==0) { - // no shortcut 0 seconds - // (1|2|3|4|5|6|7)==0 0 seconds - // all separate -0.047 seconds - // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds - int dcterm = d[0] << 2; - v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; - } else { - STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) - // constants scaled things up by 1<<12; let's bring them back - // down, but keep 2 extra bits of precision - x0 += 512; x1 += 512; x2 += 512; x3 += 512; - v[ 0] = (x0+t3) >> 10; - v[56] = (x0-t3) >> 10; - v[ 8] = (x1+t2) >> 10; - v[48] = (x1-t2) >> 10; - v[16] = (x2+t1) >> 10; - v[40] = (x2-t1) >> 10; - v[24] = (x3+t0) >> 10; - v[32] = (x3-t0) >> 10; - } - } - - for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { - // no fast case since the first 1D IDCT spread components out - STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) - // constants scaled things up by 1<<12, plus we had 1<<2 from first - // loop, plus horizontal and vertical each scale by sqrt(8) so together - // we've got an extra 1<<3, so 1<<17 total we need to remove. - // so we want to round that, which means adding 0.5 * 1<<17, - // aka 65536. Also, we'll end up with -128 to 127 that we want - // to encode as 0..255 by adding 128, so we'll add that before the shift - x0 += 65536 + (128<<17); - x1 += 65536 + (128<<17); - x2 += 65536 + (128<<17); - x3 += 65536 + (128<<17); - // tried computing the shifts into temps, or'ing the temps to see - // if any were out of range, but that was slower - o[0] = stbi__clamp((x0+t3) >> 17); - o[7] = stbi__clamp((x0-t3) >> 17); - o[1] = stbi__clamp((x1+t2) >> 17); - o[6] = stbi__clamp((x1-t2) >> 17); - o[2] = stbi__clamp((x2+t1) >> 17); - o[5] = stbi__clamp((x2-t1) >> 17); - o[3] = stbi__clamp((x3+t0) >> 17); - o[4] = stbi__clamp((x3-t0) >> 17); - } -} - -#ifdef STBI_SSE2 -// sse2 integer IDCT. not the fastest possible implementation but it -// produces bit-identical results to the generic C version so it's -// fully "transparent". -static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) -{ - // This is constructed to match our regular (generic) integer IDCT exactly. - __m128i row0, row1, row2, row3, row4, row5, row6, row7; - __m128i tmp; - - // dot product constant: even elems=x, odd elems=y - #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) - - // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) - // out(1) = c1[even]*x + c1[odd]*y - #define dct_rot(out0,out1, x,y,c0,c1) \ - __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ - __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ - __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ - __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ - __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ - __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) - - // out = in << 12 (in 16-bit, out 32-bit) - #define dct_widen(out, in) \ - __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ - __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) - - // wide add - #define dct_wadd(out, a, b) \ - __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ - __m128i out##_h = _mm_add_epi32(a##_h, b##_h) - - // wide sub - #define dct_wsub(out, a, b) \ - __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ - __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) - - // butterfly a/b, add bias, then shift by "s" and pack - #define dct_bfly32o(out0, out1, a,b,bias,s) \ - { \ - __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ - __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ - dct_wadd(sum, abiased, b); \ - dct_wsub(dif, abiased, b); \ - out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ - out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ - } - - // 8-bit interleave step (for transposes) - #define dct_interleave8(a, b) \ - tmp = a; \ - a = _mm_unpacklo_epi8(a, b); \ - b = _mm_unpackhi_epi8(tmp, b) - - // 16-bit interleave step (for transposes) - #define dct_interleave16(a, b) \ - tmp = a; \ - a = _mm_unpacklo_epi16(a, b); \ - b = _mm_unpackhi_epi16(tmp, b) - - #define dct_pass(bias,shift) \ - { \ - /* even part */ \ - dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ - __m128i sum04 = _mm_add_epi16(row0, row4); \ - __m128i dif04 = _mm_sub_epi16(row0, row4); \ - dct_widen(t0e, sum04); \ - dct_widen(t1e, dif04); \ - dct_wadd(x0, t0e, t3e); \ - dct_wsub(x3, t0e, t3e); \ - dct_wadd(x1, t1e, t2e); \ - dct_wsub(x2, t1e, t2e); \ - /* odd part */ \ - dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ - dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ - __m128i sum17 = _mm_add_epi16(row1, row7); \ - __m128i sum35 = _mm_add_epi16(row3, row5); \ - dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ - dct_wadd(x4, y0o, y4o); \ - dct_wadd(x5, y1o, y5o); \ - dct_wadd(x6, y2o, y5o); \ - dct_wadd(x7, y3o, y4o); \ - dct_bfly32o(row0,row7, x0,x7,bias,shift); \ - dct_bfly32o(row1,row6, x1,x6,bias,shift); \ - dct_bfly32o(row2,row5, x2,x5,bias,shift); \ - dct_bfly32o(row3,row4, x3,x4,bias,shift); \ - } - - __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); - __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); - __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); - __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); - __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); - __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); - __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); - __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); - - // rounding biases in column/row passes, see stbi__idct_block for explanation. - __m128i bias_0 = _mm_set1_epi32(512); - __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); - - // load - row0 = _mm_load_si128((const __m128i *) (data + 0*8)); - row1 = _mm_load_si128((const __m128i *) (data + 1*8)); - row2 = _mm_load_si128((const __m128i *) (data + 2*8)); - row3 = _mm_load_si128((const __m128i *) (data + 3*8)); - row4 = _mm_load_si128((const __m128i *) (data + 4*8)); - row5 = _mm_load_si128((const __m128i *) (data + 5*8)); - row6 = _mm_load_si128((const __m128i *) (data + 6*8)); - row7 = _mm_load_si128((const __m128i *) (data + 7*8)); - - // column pass - dct_pass(bias_0, 10); - - { - // 16bit 8x8 transpose pass 1 - dct_interleave16(row0, row4); - dct_interleave16(row1, row5); - dct_interleave16(row2, row6); - dct_interleave16(row3, row7); - - // transpose pass 2 - dct_interleave16(row0, row2); - dct_interleave16(row1, row3); - dct_interleave16(row4, row6); - dct_interleave16(row5, row7); - - // transpose pass 3 - dct_interleave16(row0, row1); - dct_interleave16(row2, row3); - dct_interleave16(row4, row5); - dct_interleave16(row6, row7); - } - - // row pass - dct_pass(bias_1, 17); - - { - // pack - __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 - __m128i p1 = _mm_packus_epi16(row2, row3); - __m128i p2 = _mm_packus_epi16(row4, row5); - __m128i p3 = _mm_packus_epi16(row6, row7); - - // 8bit 8x8 transpose pass 1 - dct_interleave8(p0, p2); // a0e0a1e1... - dct_interleave8(p1, p3); // c0g0c1g1... - - // transpose pass 2 - dct_interleave8(p0, p1); // a0c0e0g0... - dct_interleave8(p2, p3); // b0d0f0h0... - - // transpose pass 3 - dct_interleave8(p0, p2); // a0b0c0d0... - dct_interleave8(p1, p3); // a4b4c4d4... - - // store - _mm_storel_epi64((__m128i *) out, p0); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; - _mm_storel_epi64((__m128i *) out, p2); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; - _mm_storel_epi64((__m128i *) out, p1); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; - _mm_storel_epi64((__m128i *) out, p3); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); - } - -#undef dct_const -#undef dct_rot -#undef dct_widen -#undef dct_wadd -#undef dct_wsub -#undef dct_bfly32o -#undef dct_interleave8 -#undef dct_interleave16 -#undef dct_pass -} - -#endif // STBI_SSE2 - -#ifdef STBI_NEON - -// NEON integer IDCT. should produce bit-identical -// results to the generic C version. -static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) -{ - int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; - - int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); - int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); - int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); - int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); - int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); - int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); - int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); - int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); - int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); - int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); - int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); - int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); - -#define dct_long_mul(out, inq, coeff) \ - int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ - int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) - -#define dct_long_mac(out, acc, inq, coeff) \ - int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ - int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) - -#define dct_widen(out, inq) \ - int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ - int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) - -// wide add -#define dct_wadd(out, a, b) \ - int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ - int32x4_t out##_h = vaddq_s32(a##_h, b##_h) - -// wide sub -#define dct_wsub(out, a, b) \ - int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ - int32x4_t out##_h = vsubq_s32(a##_h, b##_h) - -// butterfly a/b, then shift using "shiftop" by "s" and pack -#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ - { \ - dct_wadd(sum, a, b); \ - dct_wsub(dif, a, b); \ - out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ - out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ - } - -#define dct_pass(shiftop, shift) \ - { \ - /* even part */ \ - int16x8_t sum26 = vaddq_s16(row2, row6); \ - dct_long_mul(p1e, sum26, rot0_0); \ - dct_long_mac(t2e, p1e, row6, rot0_1); \ - dct_long_mac(t3e, p1e, row2, rot0_2); \ - int16x8_t sum04 = vaddq_s16(row0, row4); \ - int16x8_t dif04 = vsubq_s16(row0, row4); \ - dct_widen(t0e, sum04); \ - dct_widen(t1e, dif04); \ - dct_wadd(x0, t0e, t3e); \ - dct_wsub(x3, t0e, t3e); \ - dct_wadd(x1, t1e, t2e); \ - dct_wsub(x2, t1e, t2e); \ - /* odd part */ \ - int16x8_t sum15 = vaddq_s16(row1, row5); \ - int16x8_t sum17 = vaddq_s16(row1, row7); \ - int16x8_t sum35 = vaddq_s16(row3, row5); \ - int16x8_t sum37 = vaddq_s16(row3, row7); \ - int16x8_t sumodd = vaddq_s16(sum17, sum35); \ - dct_long_mul(p5o, sumodd, rot1_0); \ - dct_long_mac(p1o, p5o, sum17, rot1_1); \ - dct_long_mac(p2o, p5o, sum35, rot1_2); \ - dct_long_mul(p3o, sum37, rot2_0); \ - dct_long_mul(p4o, sum15, rot2_1); \ - dct_wadd(sump13o, p1o, p3o); \ - dct_wadd(sump24o, p2o, p4o); \ - dct_wadd(sump23o, p2o, p3o); \ - dct_wadd(sump14o, p1o, p4o); \ - dct_long_mac(x4, sump13o, row7, rot3_0); \ - dct_long_mac(x5, sump24o, row5, rot3_1); \ - dct_long_mac(x6, sump23o, row3, rot3_2); \ - dct_long_mac(x7, sump14o, row1, rot3_3); \ - dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ - dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ - dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ - dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ - } - - // load - row0 = vld1q_s16(data + 0*8); - row1 = vld1q_s16(data + 1*8); - row2 = vld1q_s16(data + 2*8); - row3 = vld1q_s16(data + 3*8); - row4 = vld1q_s16(data + 4*8); - row5 = vld1q_s16(data + 5*8); - row6 = vld1q_s16(data + 6*8); - row7 = vld1q_s16(data + 7*8); - - // add DC bias - row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); - - // column pass - dct_pass(vrshrn_n_s32, 10); - - // 16bit 8x8 transpose - { -// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. -// whether compilers actually get this is another story, sadly. -#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } -#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } -#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } - - // pass 1 - dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 - dct_trn16(row2, row3); - dct_trn16(row4, row5); - dct_trn16(row6, row7); - - // pass 2 - dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 - dct_trn32(row1, row3); - dct_trn32(row4, row6); - dct_trn32(row5, row7); - - // pass 3 - dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 - dct_trn64(row1, row5); - dct_trn64(row2, row6); - dct_trn64(row3, row7); - -#undef dct_trn16 -#undef dct_trn32 -#undef dct_trn64 - } - - // row pass - // vrshrn_n_s32 only supports shifts up to 16, we need - // 17. so do a non-rounding shift of 16 first then follow - // up with a rounding shift by 1. - dct_pass(vshrn_n_s32, 16); - - { - // pack and round - uint8x8_t p0 = vqrshrun_n_s16(row0, 1); - uint8x8_t p1 = vqrshrun_n_s16(row1, 1); - uint8x8_t p2 = vqrshrun_n_s16(row2, 1); - uint8x8_t p3 = vqrshrun_n_s16(row3, 1); - uint8x8_t p4 = vqrshrun_n_s16(row4, 1); - uint8x8_t p5 = vqrshrun_n_s16(row5, 1); - uint8x8_t p6 = vqrshrun_n_s16(row6, 1); - uint8x8_t p7 = vqrshrun_n_s16(row7, 1); - - // again, these can translate into one instruction, but often don't. -#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } -#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } -#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } - - // sadly can't use interleaved stores here since we only write - // 8 bytes to each scan line! - - // 8x8 8-bit transpose pass 1 - dct_trn8_8(p0, p1); - dct_trn8_8(p2, p3); - dct_trn8_8(p4, p5); - dct_trn8_8(p6, p7); - - // pass 2 - dct_trn8_16(p0, p2); - dct_trn8_16(p1, p3); - dct_trn8_16(p4, p6); - dct_trn8_16(p5, p7); - - // pass 3 - dct_trn8_32(p0, p4); - dct_trn8_32(p1, p5); - dct_trn8_32(p2, p6); - dct_trn8_32(p3, p7); - - // store - vst1_u8(out, p0); out += out_stride; - vst1_u8(out, p1); out += out_stride; - vst1_u8(out, p2); out += out_stride; - vst1_u8(out, p3); out += out_stride; - vst1_u8(out, p4); out += out_stride; - vst1_u8(out, p5); out += out_stride; - vst1_u8(out, p6); out += out_stride; - vst1_u8(out, p7); - -#undef dct_trn8_8 -#undef dct_trn8_16 -#undef dct_trn8_32 - } - -#undef dct_long_mul -#undef dct_long_mac -#undef dct_widen -#undef dct_wadd -#undef dct_wsub -#undef dct_bfly32o -#undef dct_pass -} - -#endif // STBI_NEON - -#define STBI__MARKER_none 0xff -// if there's a pending marker from the entropy stream, return that -// otherwise, fetch from the stream and get a marker. if there's no -// marker, return 0xff, which is never a valid marker value -static stbi_uc stbi__get_marker(stbi__jpeg *j) -{ - stbi_uc x; - if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } - x = stbi__get8(j->s); - if (x != 0xff) return STBI__MARKER_none; - while (x == 0xff) - x = stbi__get8(j->s); - return x; -} - -// in each scan, we'll have scan_n components, and the order -// of the components is specified by order[] -#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) - -// after a restart interval, stbi__jpeg_reset the entropy decoder and -// the dc prediction -static void stbi__jpeg_reset(stbi__jpeg *j) -{ - j->code_bits = 0; - j->code_buffer = 0; - j->nomore = 0; - j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = 0; - j->marker = STBI__MARKER_none; - j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; - j->eob_run = 0; - // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, - // since we don't even allow 1<<30 pixels -} - -static int stbi__parse_entropy_coded_data(stbi__jpeg *z) -{ - stbi__jpeg_reset(z); - if (!z->progressive) { - if (z->scan_n == 1) { - int i,j; - STBI_SIMD_ALIGN(short, data[64]); - int n = z->order[0]; - // non-interleaved data, we just need to process one block at a time, - // in trivial scanline order - // number of blocks to do just depends on how many actual "pixels" this - // component has, independent of interleaved MCU blocking and such - int w = (z->img_comp[n].x+7) >> 3; - int h = (z->img_comp[n].y+7) >> 3; - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) { - int ha = z->img_comp[n].ha; - if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; - z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); - // every data block is an MCU, so countdown the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - // if it's NOT a restart, then just bail, so we get corrupt data - // rather than no data - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } else { // interleaved - int i,j,k,x,y; - STBI_SIMD_ALIGN(short, data[64]); - for (j=0; j < z->img_mcu_y; ++j) { - for (i=0; i < z->img_mcu_x; ++i) { - // scan an interleaved mcu... process scan_n components in order - for (k=0; k < z->scan_n; ++k) { - int n = z->order[k]; - // scan out an mcu's worth of this component; that's just determined - // by the basic H and V specified for the component - for (y=0; y < z->img_comp[n].v; ++y) { - for (x=0; x < z->img_comp[n].h; ++x) { - int x2 = (i*z->img_comp[n].h + x)*8; - int y2 = (j*z->img_comp[n].v + y)*8; - int ha = z->img_comp[n].ha; - if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; - z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); - } - } - } - // after all interleaved components, that's an interleaved MCU, - // so now count down the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } - } else { - if (z->scan_n == 1) { - int i,j; - int n = z->order[0]; - // non-interleaved data, we just need to process one block at a time, - // in trivial scanline order - // number of blocks to do just depends on how many actual "pixels" this - // component has, independent of interleaved MCU blocking and such - int w = (z->img_comp[n].x+7) >> 3; - int h = (z->img_comp[n].y+7) >> 3; - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) { - short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); - if (z->spec_start == 0) { - if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) - return 0; - } else { - int ha = z->img_comp[n].ha; - if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) - return 0; - } - // every data block is an MCU, so countdown the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } else { // interleaved - int i,j,k,x,y; - for (j=0; j < z->img_mcu_y; ++j) { - for (i=0; i < z->img_mcu_x; ++i) { - // scan an interleaved mcu... process scan_n components in order - for (k=0; k < z->scan_n; ++k) { - int n = z->order[k]; - // scan out an mcu's worth of this component; that's just determined - // by the basic H and V specified for the component - for (y=0; y < z->img_comp[n].v; ++y) { - for (x=0; x < z->img_comp[n].h; ++x) { - int x2 = (i*z->img_comp[n].h + x); - int y2 = (j*z->img_comp[n].v + y); - short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); - if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) - return 0; - } - } - } - // after all interleaved components, that's an interleaved MCU, - // so now count down the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } - } -} - -static void stbi__jpeg_dequantize(short *data, stbi_uc *dequant) -{ - int i; - for (i=0; i < 64; ++i) - data[i] *= dequant[i]; -} - -static void stbi__jpeg_finish(stbi__jpeg *z) -{ - if (z->progressive) { - // dequantize and idct the data - int i,j,n; - for (n=0; n < z->s->img_n; ++n) { - int w = (z->img_comp[n].x+7) >> 3; - int h = (z->img_comp[n].y+7) >> 3; - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) { - short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); - stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); - z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); - } - } - } - } -} - -static int stbi__process_marker(stbi__jpeg *z, int m) -{ - int L; - switch (m) { - case STBI__MARKER_none: // no marker found - return stbi__err("expected marker","Corrupt JPEG"); - - case 0xDD: // DRI - specify restart interval - if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); - z->restart_interval = stbi__get16be(z->s); - return 1; - - case 0xDB: // DQT - define quantization table - L = stbi__get16be(z->s)-2; - while (L > 0) { - int q = stbi__get8(z->s); - int p = q >> 4; - int t = q & 15,i; - if (p != 0) return stbi__err("bad DQT type","Corrupt JPEG"); - if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); - for (i=0; i < 64; ++i) - z->dequant[t][stbi__jpeg_dezigzag[i]] = stbi__get8(z->s); - L -= 65; - } - return L==0; - - case 0xC4: // DHT - define huffman table - L = stbi__get16be(z->s)-2; - while (L > 0) { - stbi_uc *v; - int sizes[16],i,n=0; - int q = stbi__get8(z->s); - int tc = q >> 4; - int th = q & 15; - if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); - for (i=0; i < 16; ++i) { - sizes[i] = stbi__get8(z->s); - n += sizes[i]; - } - L -= 17; - if (tc == 0) { - if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; - v = z->huff_dc[th].values; - } else { - if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; - v = z->huff_ac[th].values; - } - for (i=0; i < n; ++i) - v[i] = stbi__get8(z->s); - if (tc != 0) - stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); - L -= n; - } - return L==0; - } - // check for comment block or APP blocks - if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { - stbi__skip(z->s, stbi__get16be(z->s)-2); - return 1; - } - return 0; -} - -// after we see SOS -static int stbi__process_scan_header(stbi__jpeg *z) -{ - int i; - int Ls = stbi__get16be(z->s); - z->scan_n = stbi__get8(z->s); - if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); - if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); - for (i=0; i < z->scan_n; ++i) { - int id = stbi__get8(z->s), which; - int q = stbi__get8(z->s); - for (which = 0; which < z->s->img_n; ++which) - if (z->img_comp[which].id == id) - break; - if (which == z->s->img_n) return 0; // no match - z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); - z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); - z->order[i] = which; - } - - { - int aa; - z->spec_start = stbi__get8(z->s); - z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 - aa = stbi__get8(z->s); - z->succ_high = (aa >> 4); - z->succ_low = (aa & 15); - if (z->progressive) { - if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) - return stbi__err("bad SOS", "Corrupt JPEG"); - } else { - if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); - if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); - z->spec_end = 63; - } - } - - return 1; -} - -static int stbi__process_frame_header(stbi__jpeg *z, int scan) -{ - stbi__context *s = z->s; - int Lf,p,i,q, h_max=1,v_max=1,c; - Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG - p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline - s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG - s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires - c = stbi__get8(s); - if (c != 3 && c != 1) return stbi__err("bad component count","Corrupt JPEG"); // JFIF requires - s->img_n = c; - for (i=0; i < c; ++i) { - z->img_comp[i].data = NULL; - z->img_comp[i].linebuf = NULL; - } - - if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); - - for (i=0; i < s->img_n; ++i) { - z->img_comp[i].id = stbi__get8(s); - if (z->img_comp[i].id != i+1) // JFIF requires - if (z->img_comp[i].id != i) // some version of jpegtran outputs non-JFIF-compliant files! - return stbi__err("bad component ID","Corrupt JPEG"); - q = stbi__get8(s); - z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); - z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); - z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); - } - - if (scan != STBI__SCAN_load) return 1; - - if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); - - for (i=0; i < s->img_n; ++i) { - if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; - if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; - } - - // compute interleaved mcu info - z->img_h_max = h_max; - z->img_v_max = v_max; - z->img_mcu_w = h_max * 8; - z->img_mcu_h = v_max * 8; - z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; - z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; - - for (i=0; i < s->img_n; ++i) { - // number of effective pixels (e.g. for non-interleaved MCU) - z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; - z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; - // to simplify generation, we'll allocate enough memory to decode - // the bogus oversized data from using interleaved MCUs and their - // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't - // discard the extra data until colorspace conversion - z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; - z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; - z->img_comp[i].raw_data = stbi__malloc(z->img_comp[i].w2 * z->img_comp[i].h2+15); - - if (z->img_comp[i].raw_data == NULL) { - for(--i; i >= 0; --i) { - STBI_FREE(z->img_comp[i].raw_data); - z->img_comp[i].raw_data = NULL; - } - return stbi__err("outofmem", "Out of memory"); - } - // align blocks for idct using mmx/sse - z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); - z->img_comp[i].linebuf = NULL; - if (z->progressive) { - z->img_comp[i].coeff_w = (z->img_comp[i].w2 + 7) >> 3; - z->img_comp[i].coeff_h = (z->img_comp[i].h2 + 7) >> 3; - z->img_comp[i].raw_coeff = STBI_MALLOC(z->img_comp[i].coeff_w * z->img_comp[i].coeff_h * 64 * sizeof(short) + 15); - z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); - } else { - z->img_comp[i].coeff = 0; - z->img_comp[i].raw_coeff = 0; - } - } - - return 1; -} - -// use comparisons since in some cases we handle more than one case (e.g. SOF) -#define stbi__DNL(x) ((x) == 0xdc) -#define stbi__SOI(x) ((x) == 0xd8) -#define stbi__EOI(x) ((x) == 0xd9) -#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) -#define stbi__SOS(x) ((x) == 0xda) - -#define stbi__SOF_progressive(x) ((x) == 0xc2) - -static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) -{ - int m; - z->marker = STBI__MARKER_none; // initialize cached marker to empty - m = stbi__get_marker(z); - if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); - if (scan == STBI__SCAN_type) return 1; - m = stbi__get_marker(z); - while (!stbi__SOF(m)) { - if (!stbi__process_marker(z,m)) return 0; - m = stbi__get_marker(z); - while (m == STBI__MARKER_none) { - // some files have extra padding after their blocks, so ok, we'll scan - if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); - m = stbi__get_marker(z); - } - } - z->progressive = stbi__SOF_progressive(m); - if (!stbi__process_frame_header(z, scan)) return 0; - return 1; -} - -// decode image to YCbCr format -static int stbi__decode_jpeg_image(stbi__jpeg *j) -{ - int m; - for (m = 0; m < 4; m++) { - j->img_comp[m].raw_data = NULL; - j->img_comp[m].raw_coeff = NULL; - } - j->restart_interval = 0; - if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; - m = stbi__get_marker(j); - while (!stbi__EOI(m)) { - if (stbi__SOS(m)) { - if (!stbi__process_scan_header(j)) return 0; - if (!stbi__parse_entropy_coded_data(j)) return 0; - if (j->marker == STBI__MARKER_none ) { - // handle 0s at the end of image data from IP Kamera 9060 - while (!stbi__at_eof(j->s)) { - int x = stbi__get8(j->s); - if (x == 255) { - j->marker = stbi__get8(j->s); - break; - } else if (x != 0) { - return stbi__err("junk before marker", "Corrupt JPEG"); - } - } - // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 - } - } else { - if (!stbi__process_marker(j, m)) return 0; - } - m = stbi__get_marker(j); - } - if (j->progressive) - stbi__jpeg_finish(j); - return 1; -} - -// static jfif-centered resampling (across block boundaries) - -typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, - int w, int hs); - -#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) - -static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - STBI_NOTUSED(out); - STBI_NOTUSED(in_far); - STBI_NOTUSED(w); - STBI_NOTUSED(hs); - return in_near; -} - -static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate two samples vertically for every one in input - int i; - STBI_NOTUSED(hs); - for (i=0; i < w; ++i) - out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); - return out; -} - -static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate two samples horizontally for every one in input - int i; - stbi_uc *input = in_near; - - if (w == 1) { - // if only one sample, can't do any interpolation - out[0] = out[1] = input[0]; - return out; - } - - out[0] = input[0]; - out[1] = stbi__div4(input[0]*3 + input[1] + 2); - for (i=1; i < w-1; ++i) { - int n = 3*input[i]+2; - out[i*2+0] = stbi__div4(n+input[i-1]); - out[i*2+1] = stbi__div4(n+input[i+1]); - } - out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); - out[i*2+1] = input[w-1]; - - STBI_NOTUSED(in_far); - STBI_NOTUSED(hs); - - return out; -} - -#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) - -static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate 2x2 samples for every one in input - int i,t0,t1; - if (w == 1) { - out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); - return out; - } - - t1 = 3*in_near[0] + in_far[0]; - out[0] = stbi__div4(t1+2); - for (i=1; i < w; ++i) { - t0 = t1; - t1 = 3*in_near[i]+in_far[i]; - out[i*2-1] = stbi__div16(3*t0 + t1 + 8); - out[i*2 ] = stbi__div16(3*t1 + t0 + 8); - } - out[w*2-1] = stbi__div4(t1+2); - - STBI_NOTUSED(hs); - - return out; -} - -#if defined(STBI_SSE2) || defined(STBI_NEON) -static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate 2x2 samples for every one in input - int i=0,t0,t1; - - if (w == 1) { - out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); - return out; - } - - t1 = 3*in_near[0] + in_far[0]; - // process groups of 8 pixels for as long as we can. - // note we can't handle the last pixel in a row in this loop - // because we need to handle the filter boundary conditions. - for (; i < ((w-1) & ~7); i += 8) { -#if defined(STBI_SSE2) - // load and perform the vertical filtering pass - // this uses 3*x + y = 4*x + (y - x) - __m128i zero = _mm_setzero_si128(); - __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); - __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); - __m128i farw = _mm_unpacklo_epi8(farb, zero); - __m128i nearw = _mm_unpacklo_epi8(nearb, zero); - __m128i diff = _mm_sub_epi16(farw, nearw); - __m128i nears = _mm_slli_epi16(nearw, 2); - __m128i curr = _mm_add_epi16(nears, diff); // current row - - // horizontal filter works the same based on shifted vers of current - // row. "prev" is current row shifted right by 1 pixel; we need to - // insert the previous pixel value (from t1). - // "next" is current row shifted left by 1 pixel, with first pixel - // of next block of 8 pixels added in. - __m128i prv0 = _mm_slli_si128(curr, 2); - __m128i nxt0 = _mm_srli_si128(curr, 2); - __m128i prev = _mm_insert_epi16(prv0, t1, 0); - __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); - - // horizontal filter, polyphase implementation since it's convenient: - // even pixels = 3*cur + prev = cur*4 + (prev - cur) - // odd pixels = 3*cur + next = cur*4 + (next - cur) - // note the shared term. - __m128i bias = _mm_set1_epi16(8); - __m128i curs = _mm_slli_epi16(curr, 2); - __m128i prvd = _mm_sub_epi16(prev, curr); - __m128i nxtd = _mm_sub_epi16(next, curr); - __m128i curb = _mm_add_epi16(curs, bias); - __m128i even = _mm_add_epi16(prvd, curb); - __m128i odd = _mm_add_epi16(nxtd, curb); - - // interleave even and odd pixels, then undo scaling. - __m128i int0 = _mm_unpacklo_epi16(even, odd); - __m128i int1 = _mm_unpackhi_epi16(even, odd); - __m128i de0 = _mm_srli_epi16(int0, 4); - __m128i de1 = _mm_srli_epi16(int1, 4); - - // pack and write output - __m128i outv = _mm_packus_epi16(de0, de1); - _mm_storeu_si128((__m128i *) (out + i*2), outv); -#elif defined(STBI_NEON) - // load and perform the vertical filtering pass - // this uses 3*x + y = 4*x + (y - x) - uint8x8_t farb = vld1_u8(in_far + i); - uint8x8_t nearb = vld1_u8(in_near + i); - int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); - int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); - int16x8_t curr = vaddq_s16(nears, diff); // current row - - // horizontal filter works the same based on shifted vers of current - // row. "prev" is current row shifted right by 1 pixel; we need to - // insert the previous pixel value (from t1). - // "next" is current row shifted left by 1 pixel, with first pixel - // of next block of 8 pixels added in. - int16x8_t prv0 = vextq_s16(curr, curr, 7); - int16x8_t nxt0 = vextq_s16(curr, curr, 1); - int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); - int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); - - // horizontal filter, polyphase implementation since it's convenient: - // even pixels = 3*cur + prev = cur*4 + (prev - cur) - // odd pixels = 3*cur + next = cur*4 + (next - cur) - // note the shared term. - int16x8_t curs = vshlq_n_s16(curr, 2); - int16x8_t prvd = vsubq_s16(prev, curr); - int16x8_t nxtd = vsubq_s16(next, curr); - int16x8_t even = vaddq_s16(curs, prvd); - int16x8_t odd = vaddq_s16(curs, nxtd); - - // undo scaling and round, then store with even/odd phases interleaved - uint8x8x2_t o; - o.val[0] = vqrshrun_n_s16(even, 4); - o.val[1] = vqrshrun_n_s16(odd, 4); - vst2_u8(out + i*2, o); -#endif - - // "previous" value for next iter - t1 = 3*in_near[i+7] + in_far[i+7]; - } - - t0 = t1; - t1 = 3*in_near[i] + in_far[i]; - out[i*2] = stbi__div16(3*t1 + t0 + 8); - - for (++i; i < w; ++i) { - t0 = t1; - t1 = 3*in_near[i]+in_far[i]; - out[i*2-1] = stbi__div16(3*t0 + t1 + 8); - out[i*2 ] = stbi__div16(3*t1 + t0 + 8); - } - out[w*2-1] = stbi__div4(t1+2); - - STBI_NOTUSED(hs); - - return out; -} -#endif - -static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // resample with nearest-neighbor - int i,j; - STBI_NOTUSED(in_far); - for (i=0; i < w; ++i) - for (j=0; j < hs; ++j) - out[i*hs+j] = in_near[i]; - return out; -} - -#ifdef STBI_JPEG_OLD -// this is the same YCbCr-to-RGB calculation that stb_image has used -// historically before the algorithm changes in 1.49 -#define float2fixed(x) ((int) ((x) * 65536 + 0.5)) -static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) -{ - int i; - for (i=0; i < count; ++i) { - int y_fixed = (y[i] << 16) + 32768; // rounding - int r,g,b; - int cr = pcr[i] - 128; - int cb = pcb[i] - 128; - r = y_fixed + cr*float2fixed(1.40200f); - g = y_fixed - cr*float2fixed(0.71414f) - cb*float2fixed(0.34414f); - b = y_fixed + cb*float2fixed(1.77200f); - r >>= 16; - g >>= 16; - b >>= 16; - if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } - if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } - if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } - out[0] = (stbi_uc)r; - out[1] = (stbi_uc)g; - out[2] = (stbi_uc)b; - out[3] = 255; - out += step; - } -} -#else -// this is a reduced-precision calculation of YCbCr-to-RGB introduced -// to make sure the code produces the same results in both SIMD and scalar -#define float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) -static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) -{ - int i; - for (i=0; i < count; ++i) { - int y_fixed = (y[i] << 20) + (1<<19); // rounding - int r,g,b; - int cr = pcr[i] - 128; - int cb = pcb[i] - 128; - r = y_fixed + cr* float2fixed(1.40200f); - g = y_fixed + (cr*-float2fixed(0.71414f)) + ((cb*-float2fixed(0.34414f)) & 0xffff0000); - b = y_fixed + cb* float2fixed(1.77200f); - r >>= 20; - g >>= 20; - b >>= 20; - if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } - if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } - if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } - out[0] = (stbi_uc)r; - out[1] = (stbi_uc)g; - out[2] = (stbi_uc)b; - out[3] = 255; - out += step; - } -} -#endif - -#if defined(STBI_SSE2) || defined(STBI_NEON) -static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) -{ - int i = 0; - -#ifdef STBI_SSE2 - // step == 3 is pretty ugly on the final interleave, and i'm not convinced - // it's useful in practice (you wouldn't use it for textures, for example). - // so just accelerate step == 4 case. - if (step == 4) { - // this is a fairly straightforward implementation and not super-optimized. - __m128i signflip = _mm_set1_epi8(-0x80); - __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); - __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); - __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); - __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); - __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); - __m128i xw = _mm_set1_epi16(255); // alpha channel - - for (; i+7 < count; i += 8) { - // load - __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); - __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); - __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); - __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 - __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 - - // unpack to short (and left-shift cr, cb by 8) - __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); - __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); - __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); - - // color transform - __m128i yws = _mm_srli_epi16(yw, 4); - __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); - __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); - __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); - __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); - __m128i rws = _mm_add_epi16(cr0, yws); - __m128i gwt = _mm_add_epi16(cb0, yws); - __m128i bws = _mm_add_epi16(yws, cb1); - __m128i gws = _mm_add_epi16(gwt, cr1); - - // descale - __m128i rw = _mm_srai_epi16(rws, 4); - __m128i bw = _mm_srai_epi16(bws, 4); - __m128i gw = _mm_srai_epi16(gws, 4); - - // back to byte, set up for transpose - __m128i brb = _mm_packus_epi16(rw, bw); - __m128i gxb = _mm_packus_epi16(gw, xw); - - // transpose to interleave channels - __m128i t0 = _mm_unpacklo_epi8(brb, gxb); - __m128i t1 = _mm_unpackhi_epi8(brb, gxb); - __m128i o0 = _mm_unpacklo_epi16(t0, t1); - __m128i o1 = _mm_unpackhi_epi16(t0, t1); - - // store - _mm_storeu_si128((__m128i *) (out + 0), o0); - _mm_storeu_si128((__m128i *) (out + 16), o1); - out += 32; - } - } -#endif - -#ifdef STBI_NEON - // in this version, step=3 support would be easy to add. but is there demand? - if (step == 4) { - // this is a fairly straightforward implementation and not super-optimized. - uint8x8_t signflip = vdup_n_u8(0x80); - int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); - int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); - int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); - int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); - - for (; i+7 < count; i += 8) { - // load - uint8x8_t y_bytes = vld1_u8(y + i); - uint8x8_t cr_bytes = vld1_u8(pcr + i); - uint8x8_t cb_bytes = vld1_u8(pcb + i); - int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); - int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); - - // expand to s16 - int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); - int16x8_t crw = vshll_n_s8(cr_biased, 7); - int16x8_t cbw = vshll_n_s8(cb_biased, 7); - - // color transform - int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); - int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); - int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); - int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); - int16x8_t rws = vaddq_s16(yws, cr0); - int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); - int16x8_t bws = vaddq_s16(yws, cb1); - - // undo scaling, round, convert to byte - uint8x8x4_t o; - o.val[0] = vqrshrun_n_s16(rws, 4); - o.val[1] = vqrshrun_n_s16(gws, 4); - o.val[2] = vqrshrun_n_s16(bws, 4); - o.val[3] = vdup_n_u8(255); - - // store, interleaving r/g/b/a - vst4_u8(out, o); - out += 8*4; - } - } -#endif - - for (; i < count; ++i) { - int y_fixed = (y[i] << 20) + (1<<19); // rounding - int r,g,b; - int cr = pcr[i] - 128; - int cb = pcb[i] - 128; - r = y_fixed + cr* float2fixed(1.40200f); - g = y_fixed + cr*-float2fixed(0.71414f) + ((cb*-float2fixed(0.34414f)) & 0xffff0000); - b = y_fixed + cb* float2fixed(1.77200f); - r >>= 20; - g >>= 20; - b >>= 20; - if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } - if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } - if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } - out[0] = (stbi_uc)r; - out[1] = (stbi_uc)g; - out[2] = (stbi_uc)b; - out[3] = 255; - out += step; - } -} -#endif - -// set up the kernels -static void stbi__setup_jpeg(stbi__jpeg *j) -{ - j->idct_block_kernel = stbi__idct_block; - j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; - j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; - -#ifdef STBI_SSE2 - if (stbi__sse2_available()) { - j->idct_block_kernel = stbi__idct_simd; - #ifndef STBI_JPEG_OLD - j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; - #endif - j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; - } -#endif - -#ifdef STBI_NEON - j->idct_block_kernel = stbi__idct_simd; - #ifndef STBI_JPEG_OLD - j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; - #endif - j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; -#endif -} - -// clean up the temporary component buffers -static void stbi__cleanup_jpeg(stbi__jpeg *j) -{ - int i; - for (i=0; i < j->s->img_n; ++i) { - if (j->img_comp[i].raw_data) { - STBI_FREE(j->img_comp[i].raw_data); - j->img_comp[i].raw_data = NULL; - j->img_comp[i].data = NULL; - } - if (j->img_comp[i].raw_coeff) { - STBI_FREE(j->img_comp[i].raw_coeff); - j->img_comp[i].raw_coeff = 0; - j->img_comp[i].coeff = 0; - } - if (j->img_comp[i].linebuf) { - STBI_FREE(j->img_comp[i].linebuf); - j->img_comp[i].linebuf = NULL; - } - } -} - -typedef struct -{ - resample_row_func resample; - stbi_uc *line0,*line1; - int hs,vs; // expansion factor in each axis - int w_lores; // horizontal pixels pre-expansion - int ystep; // how far through vertical expansion we are - int ypos; // which pre-expansion row we're on -} stbi__resample; - -static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) -{ - int n, decode_n; - z->s->img_n = 0; // make stbi__cleanup_jpeg safe - - // validate req_comp - if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); - - // load a jpeg image from whichever source, but leave in YCbCr format - if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } - - // determine actual number of components to generate - n = req_comp ? req_comp : z->s->img_n; - - if (z->s->img_n == 3 && n < 3) - decode_n = 1; - else - decode_n = z->s->img_n; - - // resample and color-convert - { - int k; - unsigned int i,j; - stbi_uc *output; - stbi_uc *coutput[4]; - - stbi__resample res_comp[4]; - - for (k=0; k < decode_n; ++k) { - stbi__resample *r = &res_comp[k]; - - // allocate line buffer big enough for upsampling off the edges - // with upsample factor of 4 - z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); - if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } - - r->hs = z->img_h_max / z->img_comp[k].h; - r->vs = z->img_v_max / z->img_comp[k].v; - r->ystep = r->vs >> 1; - r->w_lores = (z->s->img_x + r->hs-1) / r->hs; - r->ypos = 0; - r->line0 = r->line1 = z->img_comp[k].data; - - if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; - else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; - else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; - else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; - else r->resample = stbi__resample_row_generic; - } - - // can't error after this so, this is safe - output = (stbi_uc *) stbi__malloc(n * z->s->img_x * z->s->img_y + 1); - if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } - - // now go ahead and resample - for (j=0; j < z->s->img_y; ++j) { - stbi_uc *out = output + n * z->s->img_x * j; - for (k=0; k < decode_n; ++k) { - stbi__resample *r = &res_comp[k]; - int y_bot = r->ystep >= (r->vs >> 1); - coutput[k] = r->resample(z->img_comp[k].linebuf, - y_bot ? r->line1 : r->line0, - y_bot ? r->line0 : r->line1, - r->w_lores, r->hs); - if (++r->ystep >= r->vs) { - r->ystep = 0; - r->line0 = r->line1; - if (++r->ypos < z->img_comp[k].y) - r->line1 += z->img_comp[k].w2; - } - } - if (n >= 3) { - stbi_uc *y = coutput[0]; - if (z->s->img_n == 3) { - z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); - } else - for (i=0; i < z->s->img_x; ++i) { - out[0] = out[1] = out[2] = y[i]; - out[3] = 255; // not used if n==3 - out += n; - } - } else { - stbi_uc *y = coutput[0]; - if (n == 1) - for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; - else - for (i=0; i < z->s->img_x; ++i) *out++ = y[i], *out++ = 255; - } - } - stbi__cleanup_jpeg(z); - *out_x = z->s->img_x; - *out_y = z->s->img_y; - if (comp) *comp = z->s->img_n; // report original components, not output - return output; - } -} - -static unsigned char *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - stbi__jpeg j; - j.s = s; - stbi__setup_jpeg(&j); - return load_jpeg_image(&j, x,y,comp,req_comp); -} - -static int stbi__jpeg_test(stbi__context *s) -{ - int r; - stbi__jpeg j; - j.s = s; - stbi__setup_jpeg(&j); - r = stbi__decode_jpeg_header(&j, STBI__SCAN_type); - stbi__rewind(s); - return r; -} - -static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) -{ - if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { - stbi__rewind( j->s ); - return 0; - } - if (x) *x = j->s->img_x; - if (y) *y = j->s->img_y; - if (comp) *comp = j->s->img_n; - return 1; -} - -static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) -{ - stbi__jpeg j; - j.s = s; - return stbi__jpeg_info_raw(&j, x, y, comp); -} -#endif - -// public domain zlib decode v0.2 Sean Barrett 2006-11-18 -// simple implementation -// - all input must be provided in an upfront buffer -// - all output is written to a single output buffer (can malloc/realloc) -// performance -// - fast huffman - -#ifndef STBI_NO_ZLIB - -// fast-way is faster to check than jpeg huffman, but slow way is slower -#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables -#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) - -// zlib-style huffman encoding -// (jpegs packs from left, zlib from right, so can't share code) -typedef struct -{ - stbi__uint16 fast[1 << STBI__ZFAST_BITS]; - stbi__uint16 firstcode[16]; - int maxcode[17]; - stbi__uint16 firstsymbol[16]; - stbi_uc size[288]; - stbi__uint16 value[288]; -} stbi__zhuffman; - -stbi_inline static int stbi__bitreverse16(int n) -{ - n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); - n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); - n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); - n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); - return n; -} - -stbi_inline static int stbi__bit_reverse(int v, int bits) -{ - STBI_ASSERT(bits <= 16); - // to bit reverse n bits, reverse 16 and shift - // e.g. 11 bits, bit reverse and shift away 5 - return stbi__bitreverse16(v) >> (16-bits); -} - -static int stbi__zbuild_huffman(stbi__zhuffman *z, stbi_uc *sizelist, int num) -{ - int i,k=0; - int code, next_code[16], sizes[17]; - - // DEFLATE spec for generating codes - memset(sizes, 0, sizeof(sizes)); - memset(z->fast, 0, sizeof(z->fast)); - for (i=0; i < num; ++i) - ++sizes[sizelist[i]]; - sizes[0] = 0; - for (i=1; i < 16; ++i) - if (sizes[i] > (1 << i)) - return stbi__err("bad sizes", "Corrupt PNG"); - code = 0; - for (i=1; i < 16; ++i) { - next_code[i] = code; - z->firstcode[i] = (stbi__uint16) code; - z->firstsymbol[i] = (stbi__uint16) k; - code = (code + sizes[i]); - if (sizes[i]) - if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); - z->maxcode[i] = code << (16-i); // preshift for inner loop - code <<= 1; - k += sizes[i]; - } - z->maxcode[16] = 0x10000; // sentinel - for (i=0; i < num; ++i) { - int s = sizelist[i]; - if (s) { - int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; - stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); - z->size [c] = (stbi_uc ) s; - z->value[c] = (stbi__uint16) i; - if (s <= STBI__ZFAST_BITS) { - int j = stbi__bit_reverse(next_code[s],s); - while (j < (1 << STBI__ZFAST_BITS)) { - z->fast[j] = fastv; - j += (1 << s); - } - } - ++next_code[s]; - } - } - return 1; -} - -// zlib-from-memory implementation for PNG reading -// because PNG allows splitting the zlib stream arbitrarily, -// and it's annoying structurally to have PNG call ZLIB call PNG, -// we require PNG read all the IDATs and combine them into a single -// memory buffer - -typedef struct -{ - stbi_uc *zbuffer, *zbuffer_end; - int num_bits; - stbi__uint32 code_buffer; - - char *zout; - char *zout_start; - char *zout_end; - int z_expandable; - - stbi__zhuffman z_length, z_distance; -} stbi__zbuf; - -stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) -{ - if (z->zbuffer >= z->zbuffer_end) return 0; - return *z->zbuffer++; -} - -static void stbi__fill_bits(stbi__zbuf *z) -{ - do { - STBI_ASSERT(z->code_buffer < (1U << z->num_bits)); - z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; - z->num_bits += 8; - } while (z->num_bits <= 24); -} - -stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) -{ - unsigned int k; - if (z->num_bits < n) stbi__fill_bits(z); - k = z->code_buffer & ((1 << n) - 1); - z->code_buffer >>= n; - z->num_bits -= n; - return k; -} - -static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) -{ - int b,s,k; - // not resolved by fast table, so compute it the slow way - // use jpeg approach, which requires MSbits at top - k = stbi__bit_reverse(a->code_buffer, 16); - for (s=STBI__ZFAST_BITS+1; ; ++s) - if (k < z->maxcode[s]) - break; - if (s == 16) return -1; // invalid code! - // code size is s, so: - b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; - STBI_ASSERT(z->size[b] == s); - a->code_buffer >>= s; - a->num_bits -= s; - return z->value[b]; -} - -stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) -{ - int b,s; - if (a->num_bits < 16) stbi__fill_bits(a); - b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; - if (b) { - s = b >> 9; - a->code_buffer >>= s; - a->num_bits -= s; - return b & 511; - } - return stbi__zhuffman_decode_slowpath(a, z); -} - -static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes -{ - char *q; - int cur, limit, old_limit; - z->zout = zout; - if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); - cur = (int) (z->zout - z->zout_start); - limit = old_limit = (int) (z->zout_end - z->zout_start); - while (cur + n > limit) - limit *= 2; - q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); - STBI_NOTUSED(old_limit); - if (q == NULL) return stbi__err("outofmem", "Out of memory"); - z->zout_start = q; - z->zout = q + cur; - z->zout_end = q + limit; - return 1; -} - -static int stbi__zlength_base[31] = { - 3,4,5,6,7,8,9,10,11,13, - 15,17,19,23,27,31,35,43,51,59, - 67,83,99,115,131,163,195,227,258,0,0 }; - -static int stbi__zlength_extra[31]= -{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; - -static int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, -257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; - -static int stbi__zdist_extra[32] = -{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; - -static int stbi__parse_huffman_block(stbi__zbuf *a) -{ - char *zout = a->zout; - for(;;) { - int z = stbi__zhuffman_decode(a, &a->z_length); - if (z < 256) { - if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes - if (zout >= a->zout_end) { - if (!stbi__zexpand(a, zout, 1)) return 0; - zout = a->zout; - } - *zout++ = (char) z; - } else { - stbi_uc *p; - int len,dist; - if (z == 256) { - a->zout = zout; - return 1; - } - z -= 257; - len = stbi__zlength_base[z]; - if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); - z = stbi__zhuffman_decode(a, &a->z_distance); - if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); - dist = stbi__zdist_base[z]; - if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); - if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); - if (zout + len > a->zout_end) { - if (!stbi__zexpand(a, zout, len)) return 0; - zout = a->zout; - } - p = (stbi_uc *) (zout - dist); - if (dist == 1) { // run of one byte; common in images. - stbi_uc v = *p; - if (len) { do *zout++ = v; while (--len); } - } else { - if (len) { do *zout++ = *p++; while (--len); } - } - } - } -} - -static int stbi__compute_huffman_codes(stbi__zbuf *a) -{ - static stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; - stbi__zhuffman z_codelength; - stbi_uc lencodes[286+32+137];//padding for maximum single op - stbi_uc codelength_sizes[19]; - int i,n; - - int hlit = stbi__zreceive(a,5) + 257; - int hdist = stbi__zreceive(a,5) + 1; - int hclen = stbi__zreceive(a,4) + 4; - - memset(codelength_sizes, 0, sizeof(codelength_sizes)); - for (i=0; i < hclen; ++i) { - int s = stbi__zreceive(a,3); - codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; - } - if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; - - n = 0; - while (n < hlit + hdist) { - int c = stbi__zhuffman_decode(a, &z_codelength); - if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); - if (c < 16) - lencodes[n++] = (stbi_uc) c; - else if (c == 16) { - c = stbi__zreceive(a,2)+3; - memset(lencodes+n, lencodes[n-1], c); - n += c; - } else if (c == 17) { - c = stbi__zreceive(a,3)+3; - memset(lencodes+n, 0, c); - n += c; - } else { - STBI_ASSERT(c == 18); - c = stbi__zreceive(a,7)+11; - memset(lencodes+n, 0, c); - n += c; - } - } - if (n != hlit+hdist) return stbi__err("bad codelengths","Corrupt PNG"); - if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; - if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; - return 1; -} - -static int stbi__parse_uncomperssed_block(stbi__zbuf *a) -{ - stbi_uc header[4]; - int len,nlen,k; - if (a->num_bits & 7) - stbi__zreceive(a, a->num_bits & 7); // discard - // drain the bit-packed data into header - k = 0; - while (a->num_bits > 0) { - header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check - a->code_buffer >>= 8; - a->num_bits -= 8; - } - STBI_ASSERT(a->num_bits == 0); - // now fill header the normal way - while (k < 4) - header[k++] = stbi__zget8(a); - len = header[1] * 256 + header[0]; - nlen = header[3] * 256 + header[2]; - if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); - if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); - if (a->zout + len > a->zout_end) - if (!stbi__zexpand(a, a->zout, len)) return 0; - memcpy(a->zout, a->zbuffer, len); - a->zbuffer += len; - a->zout += len; - return 1; -} - -static int stbi__parse_zlib_header(stbi__zbuf *a) -{ - int cmf = stbi__zget8(a); - int cm = cmf & 15; - /* int cinfo = cmf >> 4; */ - int flg = stbi__zget8(a); - if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec - if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png - if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png - // window = 1 << (8 + cinfo)... but who cares, we fully buffer output - return 1; -} - -// @TODO: should statically initialize these for optimal thread safety -static stbi_uc stbi__zdefault_length[288], stbi__zdefault_distance[32]; -static void stbi__init_zdefaults(void) -{ - int i; // use <= to match clearly with spec - for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; - for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; - for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; - for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; - - for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; -} - -static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) -{ - int final, type; - if (parse_header) - if (!stbi__parse_zlib_header(a)) return 0; - a->num_bits = 0; - a->code_buffer = 0; - do { - final = stbi__zreceive(a,1); - type = stbi__zreceive(a,2); - if (type == 0) { - if (!stbi__parse_uncomperssed_block(a)) return 0; - } else if (type == 3) { - return 0; - } else { - if (type == 1) { - // use fixed code lengths - if (!stbi__zdefault_distance[31]) stbi__init_zdefaults(); - if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , 288)) return 0; - if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; - } else { - if (!stbi__compute_huffman_codes(a)) return 0; - } - if (!stbi__parse_huffman_block(a)) return 0; - } - } while (!final); - return 1; -} - -static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) -{ - a->zout_start = obuf; - a->zout = obuf; - a->zout_end = obuf + olen; - a->z_expandable = exp; - - return stbi__parse_zlib(a, parse_header); -} - -STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) -{ - stbi__zbuf a; - char *p = (char *) stbi__malloc(initial_size); - if (p == NULL) return NULL; - a.zbuffer = (stbi_uc *) buffer; - a.zbuffer_end = (stbi_uc *) buffer + len; - if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { - if (outlen) *outlen = (int) (a.zout - a.zout_start); - return a.zout_start; - } else { - STBI_FREE(a.zout_start); - return NULL; - } -} - -STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) -{ - return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); -} - -STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) -{ - stbi__zbuf a; - char *p = (char *) stbi__malloc(initial_size); - if (p == NULL) return NULL; - a.zbuffer = (stbi_uc *) buffer; - a.zbuffer_end = (stbi_uc *) buffer + len; - if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { - if (outlen) *outlen = (int) (a.zout - a.zout_start); - return a.zout_start; - } else { - STBI_FREE(a.zout_start); - return NULL; - } -} - -STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) -{ - stbi__zbuf a; - a.zbuffer = (stbi_uc *) ibuffer; - a.zbuffer_end = (stbi_uc *) ibuffer + ilen; - if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) - return (int) (a.zout - a.zout_start); - else - return -1; -} - -STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) -{ - stbi__zbuf a; - char *p = (char *) stbi__malloc(16384); - if (p == NULL) return NULL; - a.zbuffer = (stbi_uc *) buffer; - a.zbuffer_end = (stbi_uc *) buffer+len; - if (stbi__do_zlib(&a, p, 16384, 1, 0)) { - if (outlen) *outlen = (int) (a.zout - a.zout_start); - return a.zout_start; - } else { - STBI_FREE(a.zout_start); - return NULL; - } -} - -STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) -{ - stbi__zbuf a; - a.zbuffer = (stbi_uc *) ibuffer; - a.zbuffer_end = (stbi_uc *) ibuffer + ilen; - if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) - return (int) (a.zout - a.zout_start); - else - return -1; -} -#endif - -// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 -// simple implementation -// - only 8-bit samples -// - no CRC checking -// - allocates lots of intermediate memory -// - avoids problem of streaming data between subsystems -// - avoids explicit window management -// performance -// - uses stb_zlib, a PD zlib implementation with fast huffman decoding - -#ifndef STBI_NO_PNG -typedef struct -{ - stbi__uint32 length; - stbi__uint32 type; -} stbi__pngchunk; - -static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) -{ - stbi__pngchunk c; - c.length = stbi__get32be(s); - c.type = stbi__get32be(s); - return c; -} - -static int stbi__check_png_header(stbi__context *s) -{ - static stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; - int i; - for (i=0; i < 8; ++i) - if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); - return 1; -} - -typedef struct -{ - stbi__context *s; - stbi_uc *idata, *expanded, *out; -} stbi__png; - - -enum { - STBI__F_none=0, - STBI__F_sub=1, - STBI__F_up=2, - STBI__F_avg=3, - STBI__F_paeth=4, - // synthetic filters used for first scanline to avoid needing a dummy row of 0s - STBI__F_avg_first, - STBI__F_paeth_first -}; - -static stbi_uc first_row_filter[5] = -{ - STBI__F_none, - STBI__F_sub, - STBI__F_none, - STBI__F_avg_first, - STBI__F_paeth_first -}; - -static int stbi__paeth(int a, int b, int c) -{ - int p = a + b - c; - int pa = abs(p-a); - int pb = abs(p-b); - int pc = abs(p-c); - if (pa <= pb && pa <= pc) return a; - if (pb <= pc) return b; - return c; -} - -static stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; - -// create the png data from post-deflated data -static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) -{ - stbi__context *s = a->s; - stbi__uint32 i,j,stride = x*out_n; - stbi__uint32 img_len, img_width_bytes; - int k; - int img_n = s->img_n; // copy it into a local for later - - STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); - a->out = (stbi_uc *) stbi__malloc(x * y * out_n); // extra bytes to write off the end into - if (!a->out) return stbi__err("outofmem", "Out of memory"); - - img_width_bytes = (((img_n * x * depth) + 7) >> 3); - img_len = (img_width_bytes + 1) * y; - if (s->img_x == x && s->img_y == y) { - if (raw_len != img_len) return stbi__err("not enough pixels","Corrupt PNG"); - } else { // interlaced: - if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); - } - - for (j=0; j < y; ++j) { - stbi_uc *cur = a->out + stride*j; - stbi_uc *prior = cur - stride; - int filter = *raw++; - int filter_bytes = img_n; - int width = x; - if (filter > 4) - return stbi__err("invalid filter","Corrupt PNG"); - - if (depth < 8) { - STBI_ASSERT(img_width_bytes <= x); - cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place - filter_bytes = 1; - width = img_width_bytes; - } - - // if first row, use special filter that doesn't sample previous row - if (j == 0) filter = first_row_filter[filter]; - - // handle first byte explicitly - for (k=0; k < filter_bytes; ++k) { - switch (filter) { - case STBI__F_none : cur[k] = raw[k]; break; - case STBI__F_sub : cur[k] = raw[k]; break; - case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; - case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; - case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; - case STBI__F_avg_first : cur[k] = raw[k]; break; - case STBI__F_paeth_first: cur[k] = raw[k]; break; - } - } - - if (depth == 8) { - if (img_n != out_n) - cur[img_n] = 255; // first pixel - raw += img_n; - cur += out_n; - prior += out_n; - } else { - raw += 1; - cur += 1; - prior += 1; - } - - // this is a little gross, so that we don't switch per-pixel or per-component - if (depth < 8 || img_n == out_n) { - int nk = (width - 1)*img_n; - #define CASE(f) \ - case f: \ - for (k=0; k < nk; ++k) - switch (filter) { - // "none" filter turns into a memcpy here; make that explicit. - case STBI__F_none: memcpy(cur, raw, nk); break; - CASE(STBI__F_sub) cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); break; - CASE(STBI__F_up) cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; - CASE(STBI__F_avg) cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); break; - CASE(STBI__F_paeth) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); break; - CASE(STBI__F_avg_first) cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); break; - CASE(STBI__F_paeth_first) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); break; - } - #undef CASE - raw += nk; - } else { - STBI_ASSERT(img_n+1 == out_n); - #define CASE(f) \ - case f: \ - for (i=x-1; i >= 1; --i, cur[img_n]=255,raw+=img_n,cur+=out_n,prior+=out_n) \ - for (k=0; k < img_n; ++k) - switch (filter) { - CASE(STBI__F_none) cur[k] = raw[k]; break; - CASE(STBI__F_sub) cur[k] = STBI__BYTECAST(raw[k] + cur[k-out_n]); break; - CASE(STBI__F_up) cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; - CASE(STBI__F_avg) cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-out_n])>>1)); break; - CASE(STBI__F_paeth) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-out_n],prior[k],prior[k-out_n])); break; - CASE(STBI__F_avg_first) cur[k] = STBI__BYTECAST(raw[k] + (cur[k-out_n] >> 1)); break; - CASE(STBI__F_paeth_first) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-out_n],0,0)); break; - } - #undef CASE - } - } - - // we make a separate pass to expand bits to pixels; for performance, - // this could run two scanlines behind the above code, so it won't - // intefere with filtering but will still be in the cache. - if (depth < 8) { - for (j=0; j < y; ++j) { - stbi_uc *cur = a->out + stride*j; - stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; - // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit - // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop - stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range - - // note that the final byte might overshoot and write more data than desired. - // we can allocate enough data that this never writes out of memory, but it - // could also overwrite the next scanline. can it overwrite non-empty data - // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. - // so we need to explicitly clamp the final ones - - if (depth == 4) { - for (k=x*img_n; k >= 2; k-=2, ++in) { - *cur++ = scale * ((*in >> 4) ); - *cur++ = scale * ((*in ) & 0x0f); - } - if (k > 0) *cur++ = scale * ((*in >> 4) ); - } else if (depth == 2) { - for (k=x*img_n; k >= 4; k-=4, ++in) { - *cur++ = scale * ((*in >> 6) ); - *cur++ = scale * ((*in >> 4) & 0x03); - *cur++ = scale * ((*in >> 2) & 0x03); - *cur++ = scale * ((*in ) & 0x03); - } - if (k > 0) *cur++ = scale * ((*in >> 6) ); - if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); - if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); - } else if (depth == 1) { - for (k=x*img_n; k >= 8; k-=8, ++in) { - *cur++ = scale * ((*in >> 7) ); - *cur++ = scale * ((*in >> 6) & 0x01); - *cur++ = scale * ((*in >> 5) & 0x01); - *cur++ = scale * ((*in >> 4) & 0x01); - *cur++ = scale * ((*in >> 3) & 0x01); - *cur++ = scale * ((*in >> 2) & 0x01); - *cur++ = scale * ((*in >> 1) & 0x01); - *cur++ = scale * ((*in ) & 0x01); - } - if (k > 0) *cur++ = scale * ((*in >> 7) ); - if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); - if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); - if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); - if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); - if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); - if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); - } - if (img_n != out_n) { - int q; - // insert alpha = 255 - cur = a->out + stride*j; - if (img_n == 1) { - for (q=x-1; q >= 0; --q) { - cur[q*2+1] = 255; - cur[q*2+0] = cur[q]; - } - } else { - STBI_ASSERT(img_n == 3); - for (q=x-1; q >= 0; --q) { - cur[q*4+3] = 255; - cur[q*4+2] = cur[q*3+2]; - cur[q*4+1] = cur[q*3+1]; - cur[q*4+0] = cur[q*3+0]; - } - } - } - } - } - - return 1; -} - -static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) -{ - stbi_uc *final; - int p; - if (!interlaced) - return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); - - // de-interlacing - final = (stbi_uc *) stbi__malloc(a->s->img_x * a->s->img_y * out_n); - for (p=0; p < 7; ++p) { - int xorig[] = { 0,4,0,2,0,1,0 }; - int yorig[] = { 0,0,4,0,2,0,1 }; - int xspc[] = { 8,8,4,4,2,2,1 }; - int yspc[] = { 8,8,8,4,4,2,2 }; - int i,j,x,y; - // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 - x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; - y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; - if (x && y) { - stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; - if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { - STBI_FREE(final); - return 0; - } - for (j=0; j < y; ++j) { - for (i=0; i < x; ++i) { - int out_y = j*yspc[p]+yorig[p]; - int out_x = i*xspc[p]+xorig[p]; - memcpy(final + out_y*a->s->img_x*out_n + out_x*out_n, - a->out + (j*x+i)*out_n, out_n); - } - } - STBI_FREE(a->out); - image_data += img_len; - image_data_len -= img_len; - } - } - a->out = final; - - return 1; -} - -static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) -{ - stbi__context *s = z->s; - stbi__uint32 i, pixel_count = s->img_x * s->img_y; - stbi_uc *p = z->out; - - // compute color-based transparency, assuming we've - // already got 255 as the alpha value in the output - STBI_ASSERT(out_n == 2 || out_n == 4); - - if (out_n == 2) { - for (i=0; i < pixel_count; ++i) { - p[1] = (p[0] == tc[0] ? 0 : 255); - p += 2; - } - } else { - for (i=0; i < pixel_count; ++i) { - if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) - p[3] = 0; - p += 4; - } - } - return 1; -} - -static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) -{ - stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; - stbi_uc *p, *temp_out, *orig = a->out; - - p = (stbi_uc *) stbi__malloc(pixel_count * pal_img_n); - if (p == NULL) return stbi__err("outofmem", "Out of memory"); - - // between here and free(out) below, exitting would leak - temp_out = p; - - if (pal_img_n == 3) { - for (i=0; i < pixel_count; ++i) { - int n = orig[i]*4; - p[0] = palette[n ]; - p[1] = palette[n+1]; - p[2] = palette[n+2]; - p += 3; - } - } else { - for (i=0; i < pixel_count; ++i) { - int n = orig[i]*4; - p[0] = palette[n ]; - p[1] = palette[n+1]; - p[2] = palette[n+2]; - p[3] = palette[n+3]; - p += 4; - } - } - STBI_FREE(a->out); - a->out = temp_out; - - STBI_NOTUSED(len); - - return 1; -} - -static int stbi__unpremultiply_on_load = 0; -static int stbi__de_iphone_flag = 0; - -STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) -{ - stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply; -} - -STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) -{ - stbi__de_iphone_flag = flag_true_if_should_convert; -} - -static void stbi__de_iphone(stbi__png *z) -{ - stbi__context *s = z->s; - stbi__uint32 i, pixel_count = s->img_x * s->img_y; - stbi_uc *p = z->out; - - if (s->img_out_n == 3) { // convert bgr to rgb - for (i=0; i < pixel_count; ++i) { - stbi_uc t = p[0]; - p[0] = p[2]; - p[2] = t; - p += 3; - } - } else { - STBI_ASSERT(s->img_out_n == 4); - if (stbi__unpremultiply_on_load) { - // convert bgr to rgb and unpremultiply - for (i=0; i < pixel_count; ++i) { - stbi_uc a = p[3]; - stbi_uc t = p[0]; - if (a) { - p[0] = p[2] * 255 / a; - p[1] = p[1] * 255 / a; - p[2] = t * 255 / a; - } else { - p[0] = p[2]; - p[2] = t; - } - p += 4; - } - } else { - // convert bgr to rgb - for (i=0; i < pixel_count; ++i) { - stbi_uc t = p[0]; - p[0] = p[2]; - p[2] = t; - p += 4; - } - } - } -} - -#define STBI__PNG_TYPE(a,b,c,d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) - -static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) -{ - stbi_uc palette[1024], pal_img_n=0; - stbi_uc has_trans=0, tc[3]; - stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; - int first=1,k,interlace=0, color=0, depth=0, is_iphone=0; - stbi__context *s = z->s; - - z->expanded = NULL; - z->idata = NULL; - z->out = NULL; - - if (!stbi__check_png_header(s)) return 0; - - if (scan == STBI__SCAN_type) return 1; - - for (;;) { - stbi__pngchunk c = stbi__get_chunk_header(s); - switch (c.type) { - case STBI__PNG_TYPE('C','g','B','I'): - is_iphone = 1; - stbi__skip(s, c.length); - break; - case STBI__PNG_TYPE('I','H','D','R'): { - int comp,filter; - if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); - first = 0; - if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); - s->img_x = stbi__get32be(s); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); - s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); - depth = stbi__get8(s); if (depth != 1 && depth != 2 && depth != 4 && depth != 8) return stbi__err("1/2/4/8-bit only","PNG not supported: 1/2/4/8-bit only"); - color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); - if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); - comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); - filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); - interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); - if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); - if (!pal_img_n) { - s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); - if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); - if (scan == STBI__SCAN_header) return 1; - } else { - // if paletted, then pal_n is our final components, and - // img_n is # components to decompress/filter. - s->img_n = 1; - if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); - // if SCAN_header, have to scan to see if we have a tRNS - } - break; - } - - case STBI__PNG_TYPE('P','L','T','E'): { - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); - pal_len = c.length / 3; - if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); - for (i=0; i < pal_len; ++i) { - palette[i*4+0] = stbi__get8(s); - palette[i*4+1] = stbi__get8(s); - palette[i*4+2] = stbi__get8(s); - palette[i*4+3] = 255; - } - break; - } - - case STBI__PNG_TYPE('t','R','N','S'): { - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); - if (pal_img_n) { - if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } - if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); - if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); - pal_img_n = 4; - for (i=0; i < c.length; ++i) - palette[i*4+3] = stbi__get8(s); - } else { - if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); - if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); - has_trans = 1; - for (k=0; k < s->img_n; ++k) - tc[k] = (stbi_uc) (stbi__get16be(s) & 255) * stbi__depth_scale_table[depth]; // non 8-bit images will be larger - } - break; - } - - case STBI__PNG_TYPE('I','D','A','T'): { - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); - if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } - if ((int)(ioff + c.length) < (int)ioff) return 0; - if (ioff + c.length > idata_limit) { - stbi__uint32 idata_limit_old = idata_limit; - stbi_uc *p; - if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; - while (ioff + c.length > idata_limit) - idata_limit *= 2; - STBI_NOTUSED(idata_limit_old); - p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); - z->idata = p; - } - if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); - ioff += c.length; - break; - } - - case STBI__PNG_TYPE('I','E','N','D'): { - stbi__uint32 raw_len, bpl; - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (scan != STBI__SCAN_load) return 1; - if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); - // initial guess for decoded data size to avoid unnecessary reallocs - bpl = (s->img_x * depth + 7) / 8; // bytes per line, per component - raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; - z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); - if (z->expanded == NULL) return 0; // zlib should set error - STBI_FREE(z->idata); z->idata = NULL; - if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) - s->img_out_n = s->img_n+1; - else - s->img_out_n = s->img_n; - if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, depth, color, interlace)) return 0; - if (has_trans) - if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; - if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) - stbi__de_iphone(z); - if (pal_img_n) { - // pal_img_n == 3 or 4 - s->img_n = pal_img_n; // record the actual colors we had - s->img_out_n = pal_img_n; - if (req_comp >= 3) s->img_out_n = req_comp; - if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) - return 0; - } - STBI_FREE(z->expanded); z->expanded = NULL; - return 1; - } - - default: - // if critical, fail - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if ((c.type & (1 << 29)) == 0) { - #ifndef STBI_NO_FAILURE_STRINGS - // not threadsafe - static char invalid_chunk[] = "XXXX PNG chunk not known"; - invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); - invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); - invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); - invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); - #endif - return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); - } - stbi__skip(s, c.length); - break; - } - // end of PNG chunk, read and skip CRC - stbi__get32be(s); - } -} - -static unsigned char *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp) -{ - unsigned char *result=NULL; - if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); - if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { - result = p->out; - p->out = NULL; - if (req_comp && req_comp != p->s->img_out_n) { - result = stbi__convert_format(result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); - p->s->img_out_n = req_comp; - if (result == NULL) return result; - } - *x = p->s->img_x; - *y = p->s->img_y; - if (n) *n = p->s->img_out_n; - } - STBI_FREE(p->out); p->out = NULL; - STBI_FREE(p->expanded); p->expanded = NULL; - STBI_FREE(p->idata); p->idata = NULL; - - return result; -} - -static unsigned char *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - stbi__png p; - p.s = s; - return stbi__do_png(&p, x,y,comp,req_comp); -} - -static int stbi__png_test(stbi__context *s) -{ - int r; - r = stbi__check_png_header(s); - stbi__rewind(s); - return r; -} - -static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) -{ - if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { - stbi__rewind( p->s ); - return 0; - } - if (x) *x = p->s->img_x; - if (y) *y = p->s->img_y; - if (comp) *comp = p->s->img_n; - return 1; -} - -static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) -{ - stbi__png p; - p.s = s; - return stbi__png_info_raw(&p, x, y, comp); -} -#endif - -// Microsoft/Windows BMP image - -#ifndef STBI_NO_BMP -static int stbi__bmp_test_raw(stbi__context *s) -{ - int r; - int sz; - if (stbi__get8(s) != 'B') return 0; - if (stbi__get8(s) != 'M') return 0; - stbi__get32le(s); // discard filesize - stbi__get16le(s); // discard reserved - stbi__get16le(s); // discard reserved - stbi__get32le(s); // discard data offset - sz = stbi__get32le(s); - r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); - return r; -} - -static int stbi__bmp_test(stbi__context *s) -{ - int r = stbi__bmp_test_raw(s); - stbi__rewind(s); - return r; -} - - -// returns 0..31 for the highest set bit -static int stbi__high_bit(unsigned int z) -{ - int n=0; - if (z == 0) return -1; - if (z >= 0x10000) n += 16, z >>= 16; - if (z >= 0x00100) n += 8, z >>= 8; - if (z >= 0x00010) n += 4, z >>= 4; - if (z >= 0x00004) n += 2, z >>= 2; - if (z >= 0x00002) n += 1, z >>= 1; - return n; -} - -static int stbi__bitcount(unsigned int a) -{ - a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 - a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 - a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits - a = (a + (a >> 8)); // max 16 per 8 bits - a = (a + (a >> 16)); // max 32 per 8 bits - return a & 0xff; -} - -static int stbi__shiftsigned(int v, int shift, int bits) -{ - int result; - int z=0; - - if (shift < 0) v <<= -shift; - else v >>= shift; - result = v; - - z = bits; - while (z < 8) { - result += v >> z; - z += bits; - } - return result; -} - -typedef struct -{ - int bpp, offset, hsz; - unsigned int mr,mg,mb,ma, all_a; -} stbi__bmp_data; - -static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) -{ - int hsz; - if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); - stbi__get32le(s); // discard filesize - stbi__get16le(s); // discard reserved - stbi__get16le(s); // discard reserved - info->offset = stbi__get32le(s); - info->hsz = hsz = stbi__get32le(s); - - if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); - if (hsz == 12) { - s->img_x = stbi__get16le(s); - s->img_y = stbi__get16le(s); - } else { - s->img_x = stbi__get32le(s); - s->img_y = stbi__get32le(s); - } - if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); - info->bpp = stbi__get16le(s); - if (info->bpp == 1) return stbi__errpuc("monochrome", "BMP type not supported: 1-bit"); - if (hsz != 12) { - int compress = stbi__get32le(s); - if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); - stbi__get32le(s); // discard sizeof - stbi__get32le(s); // discard hres - stbi__get32le(s); // discard vres - stbi__get32le(s); // discard colorsused - stbi__get32le(s); // discard max important - if (hsz == 40 || hsz == 56) { - if (hsz == 56) { - stbi__get32le(s); - stbi__get32le(s); - stbi__get32le(s); - stbi__get32le(s); - } - if (info->bpp == 16 || info->bpp == 32) { - info->mr = info->mg = info->mb = 0; - if (compress == 0) { - if (info->bpp == 32) { - info->mr = 0xffu << 16; - info->mg = 0xffu << 8; - info->mb = 0xffu << 0; - info->ma = 0xffu << 24; - info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 - } else { - info->mr = 31u << 10; - info->mg = 31u << 5; - info->mb = 31u << 0; - } - } else if (compress == 3) { - info->mr = stbi__get32le(s); - info->mg = stbi__get32le(s); - info->mb = stbi__get32le(s); - // not documented, but generated by photoshop and handled by mspaint - if (info->mr == info->mg && info->mg == info->mb) { - // ?!?!? - return stbi__errpuc("bad BMP", "bad BMP"); - } - } else - return stbi__errpuc("bad BMP", "bad BMP"); - } - } else { - int i; - if (hsz != 108 && hsz != 124) - return stbi__errpuc("bad BMP", "bad BMP"); - info->mr = stbi__get32le(s); - info->mg = stbi__get32le(s); - info->mb = stbi__get32le(s); - info->ma = stbi__get32le(s); - stbi__get32le(s); // discard color space - for (i=0; i < 12; ++i) - stbi__get32le(s); // discard color space parameters - if (hsz == 124) { - stbi__get32le(s); // discard rendering intent - stbi__get32le(s); // discard offset of profile data - stbi__get32le(s); // discard size of profile data - stbi__get32le(s); // discard reserved - } - } - } - return (void *) 1; -} - - -static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - stbi_uc *out; - unsigned int mr=0,mg=0,mb=0,ma=0, all_a; - stbi_uc pal[256][4]; - int psize=0,i,j,width; - int flip_vertically, pad, target; - stbi__bmp_data info; - - info.all_a = 255; - if (stbi__bmp_parse_header(s, &info) == NULL) - return NULL; // error code already set - - flip_vertically = ((int) s->img_y) > 0; - s->img_y = abs((int) s->img_y); - - mr = info.mr; - mg = info.mg; - mb = info.mb; - ma = info.ma; - all_a = info.all_a; - - if (info.hsz == 12) { - if (info.bpp < 24) - psize = (info.offset - 14 - 24) / 3; - } else { - if (info.bpp < 16) - psize = (info.offset - 14 - info.hsz) >> 2; - } - - s->img_n = ma ? 4 : 3; - if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 - target = req_comp; - else - target = s->img_n; // if they want monochrome, we'll post-convert - - out = (stbi_uc *) stbi__malloc(target * s->img_x * s->img_y); - if (!out) return stbi__errpuc("outofmem", "Out of memory"); - if (info.bpp < 16) { - int z=0; - if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } - for (i=0; i < psize; ++i) { - pal[i][2] = stbi__get8(s); - pal[i][1] = stbi__get8(s); - pal[i][0] = stbi__get8(s); - if (info.hsz != 12) stbi__get8(s); - pal[i][3] = 255; - } - stbi__skip(s, info.offset - 14 - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); - if (info.bpp == 4) width = (s->img_x + 1) >> 1; - else if (info.bpp == 8) width = s->img_x; - else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } - pad = (-width)&3; - for (j=0; j < (int) s->img_y; ++j) { - for (i=0; i < (int) s->img_x; i += 2) { - int v=stbi__get8(s),v2=0; - if (info.bpp == 4) { - v2 = v & 15; - v >>= 4; - } - out[z++] = pal[v][0]; - out[z++] = pal[v][1]; - out[z++] = pal[v][2]; - if (target == 4) out[z++] = 255; - if (i+1 == (int) s->img_x) break; - v = (info.bpp == 8) ? stbi__get8(s) : v2; - out[z++] = pal[v][0]; - out[z++] = pal[v][1]; - out[z++] = pal[v][2]; - if (target == 4) out[z++] = 255; - } - stbi__skip(s, pad); - } - } else { - int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; - int z = 0; - int easy=0; - stbi__skip(s, info.offset - 14 - info.hsz); - if (info.bpp == 24) width = 3 * s->img_x; - else if (info.bpp == 16) width = 2*s->img_x; - else /* bpp = 32 and pad = 0 */ width=0; - pad = (-width) & 3; - if (info.bpp == 24) { - easy = 1; - } else if (info.bpp == 32) { - if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) - easy = 2; - } - if (!easy) { - if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } - // right shift amt to put high bit in position #7 - rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); - gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); - bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); - ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); - } - for (j=0; j < (int) s->img_y; ++j) { - if (easy) { - for (i=0; i < (int) s->img_x; ++i) { - unsigned char a; - out[z+2] = stbi__get8(s); - out[z+1] = stbi__get8(s); - out[z+0] = stbi__get8(s); - z += 3; - a = (easy == 2 ? stbi__get8(s) : 255); - all_a |= a; - if (target == 4) out[z++] = a; - } - } else { - int bpp = info.bpp; - for (i=0; i < (int) s->img_x; ++i) { - stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); - int a; - out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); - out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); - out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); - a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); - all_a |= a; - if (target == 4) out[z++] = STBI__BYTECAST(a); - } - } - stbi__skip(s, pad); - } - } - - // if alpha channel is all 0s, replace with all 255s - if (target == 4 && all_a == 0) - for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) - out[i] = 255; - - if (flip_vertically) { - stbi_uc t; - for (j=0; j < (int) s->img_y>>1; ++j) { - stbi_uc *p1 = out + j *s->img_x*target; - stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; - for (i=0; i < (int) s->img_x*target; ++i) { - t = p1[i], p1[i] = p2[i], p2[i] = t; - } - } - } - - if (req_comp && req_comp != target) { - out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); - if (out == NULL) return out; // stbi__convert_format frees input on failure - } - - *x = s->img_x; - *y = s->img_y; - if (comp) *comp = s->img_n; - return out; -} -#endif - -// Targa Truevision - TGA -// by Jonathan Dummer -#ifndef STBI_NO_TGA -// returns STBI_rgb or whatever, 0 on error -static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) -{ - // only RGB or RGBA (incl. 16bit) or grey allowed - if(is_rgb16) *is_rgb16 = 0; - switch(bits_per_pixel) { - case 8: return STBI_grey; - case 16: if(is_grey) return STBI_grey_alpha; - // else: fall-through - case 15: if(is_rgb16) *is_rgb16 = 1; - return STBI_rgb; - case 24: // fall-through - case 32: return bits_per_pixel/8; - default: return 0; - } -} - -static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) -{ - int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; - int sz, tga_colormap_type; - stbi__get8(s); // discard Offset - tga_colormap_type = stbi__get8(s); // colormap type - if( tga_colormap_type > 1 ) { - stbi__rewind(s); - return 0; // only RGB or indexed allowed - } - tga_image_type = stbi__get8(s); // image type - if ( tga_colormap_type == 1 ) { // colormapped (paletted) image - if (tga_image_type != 1 && tga_image_type != 9) { - stbi__rewind(s); - return 0; - } - stbi__skip(s,4); // skip index of first colormap entry and number of entries - sz = stbi__get8(s); // check bits per palette color entry - if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { - stbi__rewind(s); - return 0; - } - stbi__skip(s,4); // skip image x and y origin - tga_colormap_bpp = sz; - } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE - if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { - stbi__rewind(s); - return 0; // only RGB or grey allowed, +/- RLE - } - stbi__skip(s,9); // skip colormap specification and image x/y origin - tga_colormap_bpp = 0; - } - tga_w = stbi__get16le(s); - if( tga_w < 1 ) { - stbi__rewind(s); - return 0; // test width - } - tga_h = stbi__get16le(s); - if( tga_h < 1 ) { - stbi__rewind(s); - return 0; // test height - } - tga_bits_per_pixel = stbi__get8(s); // bits per pixel - stbi__get8(s); // ignore alpha bits - if (tga_colormap_bpp != 0) { - if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { - // when using a colormap, tga_bits_per_pixel is the size of the indexes - // I don't think anything but 8 or 16bit indexes makes sense - stbi__rewind(s); - return 0; - } - tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); - } else { - tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); - } - if(!tga_comp) { - stbi__rewind(s); - return 0; - } - if (x) *x = tga_w; - if (y) *y = tga_h; - if (comp) *comp = tga_comp; - return 1; // seems to have passed everything -} - -static int stbi__tga_test(stbi__context *s) -{ - int res = 0; - int sz, tga_color_type; - stbi__get8(s); // discard Offset - tga_color_type = stbi__get8(s); // color type - if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed - sz = stbi__get8(s); // image type - if ( tga_color_type == 1 ) { // colormapped (paletted) image - if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 - stbi__skip(s,4); // skip index of first colormap entry and number of entries - sz = stbi__get8(s); // check bits per palette color entry - if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; - stbi__skip(s,4); // skip image x and y origin - } else { // "normal" image w/o colormap - if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE - stbi__skip(s,9); // skip colormap specification and image x/y origin - } - if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width - if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height - sz = stbi__get8(s); // bits per pixel - if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index - if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; - - res = 1; // if we got this far, everything's good and we can return 1 instead of 0 - -errorEnd: - stbi__rewind(s); - return res; -} - -// read 16bit value and convert to 24bit RGB -void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) -{ - stbi__uint16 px = stbi__get16le(s); - stbi__uint16 fiveBitMask = 31; - // we have 3 channels with 5bits each - int r = (px >> 10) & fiveBitMask; - int g = (px >> 5) & fiveBitMask; - int b = px & fiveBitMask; - // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later - out[0] = (r * 255)/31; - out[1] = (g * 255)/31; - out[2] = (b * 255)/31; - - // some people claim that the most significant bit might be used for alpha - // (possibly if an alpha-bit is set in the "image descriptor byte") - // but that only made 16bit test images completely translucent.. - // so let's treat all 15 and 16bit TGAs as RGB with no alpha. -} - -static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - // read in the TGA header stuff - int tga_offset = stbi__get8(s); - int tga_indexed = stbi__get8(s); - int tga_image_type = stbi__get8(s); - int tga_is_RLE = 0; - int tga_palette_start = stbi__get16le(s); - int tga_palette_len = stbi__get16le(s); - int tga_palette_bits = stbi__get8(s); - int tga_x_origin = stbi__get16le(s); - int tga_y_origin = stbi__get16le(s); - int tga_width = stbi__get16le(s); - int tga_height = stbi__get16le(s); - int tga_bits_per_pixel = stbi__get8(s); - int tga_comp, tga_rgb16=0; - int tga_inverted = stbi__get8(s); - // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) - // image data - unsigned char *tga_data; - unsigned char *tga_palette = NULL; - int i, j; - unsigned char raw_data[4]; - int RLE_count = 0; - int RLE_repeating = 0; - int read_next_pixel = 1; - - // do a tiny bit of precessing - if ( tga_image_type >= 8 ) - { - tga_image_type -= 8; - tga_is_RLE = 1; - } - tga_inverted = 1 - ((tga_inverted >> 5) & 1); - - // If I'm paletted, then I'll use the number of bits from the palette - if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); - else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); - - if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency - return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); - - // tga info - *x = tga_width; - *y = tga_height; - if (comp) *comp = tga_comp; - - tga_data = (unsigned char*)stbi__malloc( (size_t)tga_width * tga_height * tga_comp ); - if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); - - // skip to the data's starting position (offset usually = 0) - stbi__skip(s, tga_offset ); - - if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { - for (i=0; i < tga_height; ++i) { - int row = tga_inverted ? tga_height -i - 1 : i; - stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; - stbi__getn(s, tga_row, tga_width * tga_comp); - } - } else { - // do I need to load a palette? - if ( tga_indexed) - { - // any data to skip? (offset usually = 0) - stbi__skip(s, tga_palette_start ); - // load the palette - tga_palette = (unsigned char*)stbi__malloc( tga_palette_len * tga_comp ); - if (!tga_palette) { - STBI_FREE(tga_data); - return stbi__errpuc("outofmem", "Out of memory"); - } - if (tga_rgb16) { - stbi_uc *pal_entry = tga_palette; - STBI_ASSERT(tga_comp == STBI_rgb); - for (i=0; i < tga_palette_len; ++i) { - stbi__tga_read_rgb16(s, pal_entry); - pal_entry += tga_comp; - } - } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { - STBI_FREE(tga_data); - STBI_FREE(tga_palette); - return stbi__errpuc("bad palette", "Corrupt TGA"); - } - } - // load the data - for (i=0; i < tga_width * tga_height; ++i) - { - // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? - if ( tga_is_RLE ) - { - if ( RLE_count == 0 ) - { - // yep, get the next byte as a RLE command - int RLE_cmd = stbi__get8(s); - RLE_count = 1 + (RLE_cmd & 127); - RLE_repeating = RLE_cmd >> 7; - read_next_pixel = 1; - } else if ( !RLE_repeating ) - { - read_next_pixel = 1; - } - } else - { - read_next_pixel = 1; - } - // OK, if I need to read a pixel, do it now - if ( read_next_pixel ) - { - // load however much data we did have - if ( tga_indexed ) - { - // read in index, then perform the lookup - int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); - if ( pal_idx >= tga_palette_len ) { - // invalid index - pal_idx = 0; - } - pal_idx *= tga_comp; - for (j = 0; j < tga_comp; ++j) { - raw_data[j] = tga_palette[pal_idx+j]; - } - } else if(tga_rgb16) { - STBI_ASSERT(tga_comp == STBI_rgb); - stbi__tga_read_rgb16(s, raw_data); - } else { - // read in the data raw - for (j = 0; j < tga_comp; ++j) { - raw_data[j] = stbi__get8(s); - } - } - // clear the reading flag for the next pixel - read_next_pixel = 0; - } // end of reading a pixel - - // copy data - for (j = 0; j < tga_comp; ++j) - tga_data[i*tga_comp+j] = raw_data[j]; - - // in case we're in RLE mode, keep counting down - --RLE_count; - } - // do I need to invert the image? - if ( tga_inverted ) - { - for (j = 0; j*2 < tga_height; ++j) - { - int index1 = j * tga_width * tga_comp; - int index2 = (tga_height - 1 - j) * tga_width * tga_comp; - for (i = tga_width * tga_comp; i > 0; --i) - { - unsigned char temp = tga_data[index1]; - tga_data[index1] = tga_data[index2]; - tga_data[index2] = temp; - ++index1; - ++index2; - } - } - } - // clear my palette, if I had one - if ( tga_palette != NULL ) - { - STBI_FREE( tga_palette ); - } - } - - // swap RGB - if the source data was RGB16, it already is in the right order - if (tga_comp >= 3 && !tga_rgb16) - { - unsigned char* tga_pixel = tga_data; - for (i=0; i < tga_width * tga_height; ++i) - { - unsigned char temp = tga_pixel[0]; - tga_pixel[0] = tga_pixel[2]; - tga_pixel[2] = temp; - tga_pixel += tga_comp; - } - } - - // convert to target component count - if (req_comp && req_comp != tga_comp) - tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); - - // the things I do to get rid of an error message, and yet keep - // Microsoft's C compilers happy... [8^( - tga_palette_start = tga_palette_len = tga_palette_bits = - tga_x_origin = tga_y_origin = 0; - // OK, done - return tga_data; -} -#endif - -// ************************************************************************************************* -// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB - -#ifndef STBI_NO_PSD -static int stbi__psd_test(stbi__context *s) -{ - int r = (stbi__get32be(s) == 0x38425053); - stbi__rewind(s); - return r; -} - -static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - int pixelCount; - int channelCount, compression; - int channel, i, count, len; - int bitdepth; - int w,h; - stbi_uc *out; - - // Check identifier - if (stbi__get32be(s) != 0x38425053) // "8BPS" - return stbi__errpuc("not PSD", "Corrupt PSD image"); - - // Check file type version. - if (stbi__get16be(s) != 1) - return stbi__errpuc("wrong version", "Unsupported version of PSD image"); - - // Skip 6 reserved bytes. - stbi__skip(s, 6 ); - - // Read the number of channels (R, G, B, A, etc). - channelCount = stbi__get16be(s); - if (channelCount < 0 || channelCount > 16) - return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); - - // Read the rows and columns of the image. - h = stbi__get32be(s); - w = stbi__get32be(s); - - // Make sure the depth is 8 bits. - bitdepth = stbi__get16be(s); - if (bitdepth != 8 && bitdepth != 16) - return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); - - // Make sure the color mode is RGB. - // Valid options are: - // 0: Bitmap - // 1: Grayscale - // 2: Indexed color - // 3: RGB color - // 4: CMYK color - // 7: Multichannel - // 8: Duotone - // 9: Lab color - if (stbi__get16be(s) != 3) - return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); - - // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) - stbi__skip(s,stbi__get32be(s) ); - - // Skip the image resources. (resolution, pen tool paths, etc) - stbi__skip(s, stbi__get32be(s) ); - - // Skip the reserved data. - stbi__skip(s, stbi__get32be(s) ); - - // Find out if the data is compressed. - // Known values: - // 0: no compression - // 1: RLE compressed - compression = stbi__get16be(s); - if (compression > 1) - return stbi__errpuc("bad compression", "PSD has an unknown compression format"); - - // Create the destination image. - out = (stbi_uc *) stbi__malloc(4 * w*h); - if (!out) return stbi__errpuc("outofmem", "Out of memory"); - pixelCount = w*h; - - // Initialize the data to zero. - //memset( out, 0, pixelCount * 4 ); - - // Finally, the image data. - if (compression) { - // RLE as used by .PSD and .TIFF - // Loop until you get the number of unpacked bytes you are expecting: - // Read the next source byte into n. - // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. - // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. - // Else if n is 128, noop. - // Endloop - - // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data, - // which we're going to just skip. - stbi__skip(s, h * channelCount * 2 ); - - // Read the RLE data by channel. - for (channel = 0; channel < 4; channel++) { - stbi_uc *p; - - p = out+channel; - if (channel >= channelCount) { - // Fill this channel with default data. - for (i = 0; i < pixelCount; i++, p += 4) - *p = (channel == 3 ? 255 : 0); - } else { - // Read the RLE data. - count = 0; - while (count < pixelCount) { - len = stbi__get8(s); - if (len == 128) { - // No-op. - } else if (len < 128) { - // Copy next len+1 bytes literally. - len++; - count += len; - while (len) { - *p = stbi__get8(s); - p += 4; - len--; - } - } else if (len > 128) { - stbi_uc val; - // Next -len+1 bytes in the dest are replicated from next source byte. - // (Interpret len as a negative 8-bit int.) - len ^= 0x0FF; - len += 2; - val = stbi__get8(s); - count += len; - while (len) { - *p = val; - p += 4; - len--; - } - } - } - } - } - - } else { - // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) - // where each channel consists of an 8-bit value for each pixel in the image. - - // Read the data by channel. - for (channel = 0; channel < 4; channel++) { - stbi_uc *p; - - p = out + channel; - if (channel >= channelCount) { - // Fill this channel with default data. - stbi_uc val = channel == 3 ? 255 : 0; - for (i = 0; i < pixelCount; i++, p += 4) - *p = val; - } else { - // Read the data. - if (bitdepth == 16) { - for (i = 0; i < pixelCount; i++, p += 4) - *p = (stbi_uc) (stbi__get16be(s) >> 8); - } else { - for (i = 0; i < pixelCount; i++, p += 4) - *p = stbi__get8(s); - } - } - } - } - - if (req_comp && req_comp != 4) { - out = stbi__convert_format(out, 4, req_comp, w, h); - if (out == NULL) return out; // stbi__convert_format frees input on failure - } - - if (comp) *comp = 4; - *y = h; - *x = w; - - return out; -} -#endif - -// ************************************************************************************************* -// Softimage PIC loader -// by Tom Seddon -// -// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format -// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ - -#ifndef STBI_NO_PIC -static int stbi__pic_is4(stbi__context *s,const char *str) -{ - int i; - for (i=0; i<4; ++i) - if (stbi__get8(s) != (stbi_uc)str[i]) - return 0; - - return 1; -} - -static int stbi__pic_test_core(stbi__context *s) -{ - int i; - - if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) - return 0; - - for(i=0;i<84;++i) - stbi__get8(s); - - if (!stbi__pic_is4(s,"PICT")) - return 0; - - return 1; -} - -typedef struct -{ - stbi_uc size,type,channel; -} stbi__pic_packet; - -static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) -{ - int mask=0x80, i; - - for (i=0; i<4; ++i, mask>>=1) { - if (channel & mask) { - if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); - dest[i]=stbi__get8(s); - } - } - - return dest; -} - -static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) -{ - int mask=0x80,i; - - for (i=0;i<4; ++i, mask>>=1) - if (channel&mask) - dest[i]=src[i]; -} - -static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) -{ - int act_comp=0,num_packets=0,y,chained; - stbi__pic_packet packets[10]; - - // this will (should...) cater for even some bizarre stuff like having data - // for the same channel in multiple packets. - do { - stbi__pic_packet *packet; - - if (num_packets==sizeof(packets)/sizeof(packets[0])) - return stbi__errpuc("bad format","too many packets"); - - packet = &packets[num_packets++]; - - chained = stbi__get8(s); - packet->size = stbi__get8(s); - packet->type = stbi__get8(s); - packet->channel = stbi__get8(s); - - act_comp |= packet->channel; - - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); - if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); - } while (chained); - - *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? - - for(y=0; ytype) { - default: - return stbi__errpuc("bad format","packet has bad compression type"); - - case 0: {//uncompressed - int x; - - for(x=0;xchannel,dest)) - return 0; - break; - } - - case 1://Pure RLE - { - int left=width, i; - - while (left>0) { - stbi_uc count,value[4]; - - count=stbi__get8(s); - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); - - if (count > left) - count = (stbi_uc) left; - - if (!stbi__readval(s,packet->channel,value)) return 0; - - for(i=0; ichannel,dest,value); - left -= count; - } - } - break; - - case 2: {//Mixed RLE - int left=width; - while (left>0) { - int count = stbi__get8(s), i; - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); - - if (count >= 128) { // Repeated - stbi_uc value[4]; - - if (count==128) - count = stbi__get16be(s); - else - count -= 127; - if (count > left) - return stbi__errpuc("bad file","scanline overrun"); - - if (!stbi__readval(s,packet->channel,value)) - return 0; - - for(i=0;ichannel,dest,value); - } else { // Raw - ++count; - if (count>left) return stbi__errpuc("bad file","scanline overrun"); - - for(i=0;ichannel,dest)) - return 0; - } - left-=count; - } - break; - } - } - } - } - - return result; -} - -static stbi_uc *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp) -{ - stbi_uc *result; - int i, x,y; - - for (i=0; i<92; ++i) - stbi__get8(s); - - x = stbi__get16be(s); - y = stbi__get16be(s); - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); - if ((1 << 28) / x < y) return stbi__errpuc("too large", "Image too large to decode"); - - stbi__get32be(s); //skip `ratio' - stbi__get16be(s); //skip `fields' - stbi__get16be(s); //skip `pad' - - // intermediate buffer is RGBA - result = (stbi_uc *) stbi__malloc(x*y*4); - memset(result, 0xff, x*y*4); - - if (!stbi__pic_load_core(s,x,y,comp, result)) { - STBI_FREE(result); - result=0; - } - *px = x; - *py = y; - if (req_comp == 0) req_comp = *comp; - result=stbi__convert_format(result,4,req_comp,x,y); - - return result; -} - -static int stbi__pic_test(stbi__context *s) -{ - int r = stbi__pic_test_core(s); - stbi__rewind(s); - return r; -} -#endif - -// ************************************************************************************************* -// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb - -#ifndef STBI_NO_GIF -typedef struct -{ - stbi__int16 prefix; - stbi_uc first; - stbi_uc suffix; -} stbi__gif_lzw; - -typedef struct -{ - int w,h; - stbi_uc *out, *old_out; // output buffer (always 4 components) - int flags, bgindex, ratio, transparent, eflags, delay; - stbi_uc pal[256][4]; - stbi_uc lpal[256][4]; - stbi__gif_lzw codes[4096]; - stbi_uc *color_table; - int parse, step; - int lflags; - int start_x, start_y; - int max_x, max_y; - int cur_x, cur_y; - int line_size; -} stbi__gif; - -static int stbi__gif_test_raw(stbi__context *s) -{ - int sz; - if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; - sz = stbi__get8(s); - if (sz != '9' && sz != '7') return 0; - if (stbi__get8(s) != 'a') return 0; - return 1; -} - -static int stbi__gif_test(stbi__context *s) -{ - int r = stbi__gif_test_raw(s); - stbi__rewind(s); - return r; -} - -static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) -{ - int i; - for (i=0; i < num_entries; ++i) { - pal[i][2] = stbi__get8(s); - pal[i][1] = stbi__get8(s); - pal[i][0] = stbi__get8(s); - pal[i][3] = transp == i ? 0 : 255; - } -} - -static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) -{ - stbi_uc version; - if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') - return stbi__err("not GIF", "Corrupt GIF"); - - version = stbi__get8(s); - if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); - if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); - - stbi__g_failure_reason = ""; - g->w = stbi__get16le(s); - g->h = stbi__get16le(s); - g->flags = stbi__get8(s); - g->bgindex = stbi__get8(s); - g->ratio = stbi__get8(s); - g->transparent = -1; - - if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments - - if (is_info) return 1; - - if (g->flags & 0x80) - stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); - - return 1; -} - -static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) -{ - stbi__gif g; - if (!stbi__gif_header(s, &g, comp, 1)) { - stbi__rewind( s ); - return 0; - } - if (x) *x = g.w; - if (y) *y = g.h; - return 1; -} - -static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) -{ - stbi_uc *p, *c; - - // recurse to decode the prefixes, since the linked-list is backwards, - // and working backwards through an interleaved image would be nasty - if (g->codes[code].prefix >= 0) - stbi__out_gif_code(g, g->codes[code].prefix); - - if (g->cur_y >= g->max_y) return; - - p = &g->out[g->cur_x + g->cur_y]; - c = &g->color_table[g->codes[code].suffix * 4]; - - if (c[3] >= 128) { - p[0] = c[2]; - p[1] = c[1]; - p[2] = c[0]; - p[3] = c[3]; - } - g->cur_x += 4; - - if (g->cur_x >= g->max_x) { - g->cur_x = g->start_x; - g->cur_y += g->step; - - while (g->cur_y >= g->max_y && g->parse > 0) { - g->step = (1 << g->parse) * g->line_size; - g->cur_y = g->start_y + (g->step >> 1); - --g->parse; - } - } -} - -static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) -{ - stbi_uc lzw_cs; - stbi__int32 len, init_code; - stbi__uint32 first; - stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; - stbi__gif_lzw *p; - - lzw_cs = stbi__get8(s); - if (lzw_cs > 12) return NULL; - clear = 1 << lzw_cs; - first = 1; - codesize = lzw_cs + 1; - codemask = (1 << codesize) - 1; - bits = 0; - valid_bits = 0; - for (init_code = 0; init_code < clear; init_code++) { - g->codes[init_code].prefix = -1; - g->codes[init_code].first = (stbi_uc) init_code; - g->codes[init_code].suffix = (stbi_uc) init_code; - } - - // support no starting clear code - avail = clear+2; - oldcode = -1; - - len = 0; - for(;;) { - if (valid_bits < codesize) { - if (len == 0) { - len = stbi__get8(s); // start new block - if (len == 0) - return g->out; - } - --len; - bits |= (stbi__int32) stbi__get8(s) << valid_bits; - valid_bits += 8; - } else { - stbi__int32 code = bits & codemask; - bits >>= codesize; - valid_bits -= codesize; - // @OPTIMIZE: is there some way we can accelerate the non-clear path? - if (code == clear) { // clear code - codesize = lzw_cs + 1; - codemask = (1 << codesize) - 1; - avail = clear + 2; - oldcode = -1; - first = 0; - } else if (code == clear + 1) { // end of stream code - stbi__skip(s, len); - while ((len = stbi__get8(s)) > 0) - stbi__skip(s,len); - return g->out; - } else if (code <= avail) { - if (first) return stbi__errpuc("no clear code", "Corrupt GIF"); - - if (oldcode >= 0) { - p = &g->codes[avail++]; - if (avail > 4096) return stbi__errpuc("too many codes", "Corrupt GIF"); - p->prefix = (stbi__int16) oldcode; - p->first = g->codes[oldcode].first; - p->suffix = (code == avail) ? p->first : g->codes[code].first; - } else if (code == avail) - return stbi__errpuc("illegal code in raster", "Corrupt GIF"); - - stbi__out_gif_code(g, (stbi__uint16) code); - - if ((avail & codemask) == 0 && avail <= 0x0FFF) { - codesize++; - codemask = (1 << codesize) - 1; - } - - oldcode = code; - } else { - return stbi__errpuc("illegal code in raster", "Corrupt GIF"); - } - } - } -} - -static void stbi__fill_gif_background(stbi__gif *g, int x0, int y0, int x1, int y1) -{ - int x, y; - stbi_uc *c = g->pal[g->bgindex]; - for (y = y0; y < y1; y += 4 * g->w) { - for (x = x0; x < x1; x += 4) { - stbi_uc *p = &g->out[y + x]; - p[0] = c[2]; - p[1] = c[1]; - p[2] = c[0]; - p[3] = 0; - } - } -} - -// this function is designed to support animated gifs, although stb_image doesn't support it -static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp) -{ - int i; - stbi_uc *prev_out = 0; - - if (g->out == 0 && !stbi__gif_header(s, g, comp,0)) - return 0; // stbi__g_failure_reason set by stbi__gif_header - - prev_out = g->out; - g->out = (stbi_uc *) stbi__malloc(4 * g->w * g->h); - if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory"); - - switch ((g->eflags & 0x1C) >> 2) { - case 0: // unspecified (also always used on 1st frame) - stbi__fill_gif_background(g, 0, 0, 4 * g->w, 4 * g->w * g->h); - break; - case 1: // do not dispose - if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h); - g->old_out = prev_out; - break; - case 2: // dispose to background - if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h); - stbi__fill_gif_background(g, g->start_x, g->start_y, g->max_x, g->max_y); - break; - case 3: // dispose to previous - if (g->old_out) { - for (i = g->start_y; i < g->max_y; i += 4 * g->w) - memcpy(&g->out[i + g->start_x], &g->old_out[i + g->start_x], g->max_x - g->start_x); - } - break; - } - - for (;;) { - switch (stbi__get8(s)) { - case 0x2C: /* Image Descriptor */ - { - int prev_trans = -1; - stbi__int32 x, y, w, h; - stbi_uc *o; - - x = stbi__get16le(s); - y = stbi__get16le(s); - w = stbi__get16le(s); - h = stbi__get16le(s); - if (((x + w) > (g->w)) || ((y + h) > (g->h))) - return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); - - g->line_size = g->w * 4; - g->start_x = x * 4; - g->start_y = y * g->line_size; - g->max_x = g->start_x + w * 4; - g->max_y = g->start_y + h * g->line_size; - g->cur_x = g->start_x; - g->cur_y = g->start_y; - - g->lflags = stbi__get8(s); - - if (g->lflags & 0x40) { - g->step = 8 * g->line_size; // first interlaced spacing - g->parse = 3; - } else { - g->step = g->line_size; - g->parse = 0; - } - - if (g->lflags & 0x80) { - stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); - g->color_table = (stbi_uc *) g->lpal; - } else if (g->flags & 0x80) { - if (g->transparent >= 0 && (g->eflags & 0x01)) { - prev_trans = g->pal[g->transparent][3]; - g->pal[g->transparent][3] = 0; - } - g->color_table = (stbi_uc *) g->pal; - } else - return stbi__errpuc("missing color table", "Corrupt GIF"); - - o = stbi__process_gif_raster(s, g); - if (o == NULL) return NULL; - - if (prev_trans != -1) - g->pal[g->transparent][3] = (stbi_uc) prev_trans; - - return o; - } - - case 0x21: // Comment Extension. - { - int len; - if (stbi__get8(s) == 0xF9) { // Graphic Control Extension. - len = stbi__get8(s); - if (len == 4) { - g->eflags = stbi__get8(s); - g->delay = stbi__get16le(s); - g->transparent = stbi__get8(s); - } else { - stbi__skip(s, len); - break; - } - } - while ((len = stbi__get8(s)) != 0) - stbi__skip(s, len); - break; - } - - case 0x3B: // gif stream termination code - return (stbi_uc *) s; // using '1' causes warning on some compilers - - default: - return stbi__errpuc("unknown code", "Corrupt GIF"); - } - } - - STBI_NOTUSED(req_comp); -} - -static stbi_uc *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - stbi_uc *u = 0; - stbi__gif g; - memset(&g, 0, sizeof(g)); - - u = stbi__gif_load_next(s, &g, comp, req_comp); - if (u == (stbi_uc *) s) u = 0; // end of animated gif marker - if (u) { - *x = g.w; - *y = g.h; - if (req_comp && req_comp != 4) - u = stbi__convert_format(u, 4, req_comp, g.w, g.h); - } - else if (g.out) - STBI_FREE(g.out); - - return u; -} - -static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) -{ - return stbi__gif_info_raw(s,x,y,comp); -} -#endif - -// ************************************************************************************************* -// Radiance RGBE HDR loader -// originally by Nicolas Schulz -#ifndef STBI_NO_HDR -static int stbi__hdr_test_core(stbi__context *s) -{ - const char *signature = "#?RADIANCE\n"; - int i; - for (i=0; signature[i]; ++i) - if (stbi__get8(s) != signature[i]) - return 0; - return 1; -} - -static int stbi__hdr_test(stbi__context* s) -{ - int r = stbi__hdr_test_core(s); - stbi__rewind(s); - return r; -} - -#define STBI__HDR_BUFLEN 1024 -static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) -{ - int len=0; - char c = '\0'; - - c = (char) stbi__get8(z); - - while (!stbi__at_eof(z) && c != '\n') { - buffer[len++] = c; - if (len == STBI__HDR_BUFLEN-1) { - // flush to end of line - while (!stbi__at_eof(z) && stbi__get8(z) != '\n') - ; - break; - } - c = (char) stbi__get8(z); - } - - buffer[len] = 0; - return buffer; -} - -static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) -{ - if ( input[3] != 0 ) { - float f1; - // Exponent - f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); - if (req_comp <= 2) - output[0] = (input[0] + input[1] + input[2]) * f1 / 3; - else { - output[0] = input[0] * f1; - output[1] = input[1] * f1; - output[2] = input[2] * f1; - } - if (req_comp == 2) output[1] = 1; - if (req_comp == 4) output[3] = 1; - } else { - switch (req_comp) { - case 4: output[3] = 1; /* fallthrough */ - case 3: output[0] = output[1] = output[2] = 0; - break; - case 2: output[1] = 1; /* fallthrough */ - case 1: output[0] = 0; - break; - } - } -} - -static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - char buffer[STBI__HDR_BUFLEN]; - char *token; - int valid = 0; - int width, height; - stbi_uc *scanline; - float *hdr_data; - int len; - unsigned char count, value; - int i, j, k, c1,c2, z; - - - // Check identifier - if (strcmp(stbi__hdr_gettoken(s,buffer), "#?RADIANCE") != 0) - return stbi__errpf("not HDR", "Corrupt HDR image"); - - // Parse header - for(;;) { - token = stbi__hdr_gettoken(s,buffer); - if (token[0] == 0) break; - if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; - } - - if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); - - // Parse width and height - // can't use sscanf() if we're not using stdio! - token = stbi__hdr_gettoken(s,buffer); - if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); - token += 3; - height = (int) strtol(token, &token, 10); - while (*token == ' ') ++token; - if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); - token += 3; - width = (int) strtol(token, NULL, 10); - - *x = width; - *y = height; - - if (comp) *comp = 3; - if (req_comp == 0) req_comp = 3; - - // Read data - hdr_data = (float *) stbi__malloc(height * width * req_comp * sizeof(float)); - - // Load image data - // image data is stored as some number of sca - if ( width < 8 || width >= 32768) { - // Read flat data - for (j=0; j < height; ++j) { - for (i=0; i < width; ++i) { - stbi_uc rgbe[4]; - main_decode_loop: - stbi__getn(s, rgbe, 4); - stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); - } - } - } else { - // Read RLE-encoded data - scanline = NULL; - - for (j = 0; j < height; ++j) { - c1 = stbi__get8(s); - c2 = stbi__get8(s); - len = stbi__get8(s); - if (c1 != 2 || c2 != 2 || (len & 0x80)) { - // not run-length encoded, so we have to actually use THIS data as a decoded - // pixel (note this can't be a valid pixel--one of RGB must be >= 128) - stbi_uc rgbe[4]; - rgbe[0] = (stbi_uc) c1; - rgbe[1] = (stbi_uc) c2; - rgbe[2] = (stbi_uc) len; - rgbe[3] = (stbi_uc) stbi__get8(s); - stbi__hdr_convert(hdr_data, rgbe, req_comp); - i = 1; - j = 0; - STBI_FREE(scanline); - goto main_decode_loop; // yes, this makes no sense - } - len <<= 8; - len |= stbi__get8(s); - if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } - if (scanline == NULL) scanline = (stbi_uc *) stbi__malloc(width * 4); - - for (k = 0; k < 4; ++k) { - i = 0; - while (i < width) { - count = stbi__get8(s); - if (count > 128) { - // Run - value = stbi__get8(s); - count -= 128; - for (z = 0; z < count; ++z) - scanline[i++ * 4 + k] = value; - } else { - // Dump - for (z = 0; z < count; ++z) - scanline[i++ * 4 + k] = stbi__get8(s); - } - } - } - for (i=0; i < width; ++i) - stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); - } - STBI_FREE(scanline); - } - - return hdr_data; -} - -static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) -{ - char buffer[STBI__HDR_BUFLEN]; - char *token; - int valid = 0; - - if (stbi__hdr_test(s) == 0) { - stbi__rewind( s ); - return 0; - } - - for(;;) { - token = stbi__hdr_gettoken(s,buffer); - if (token[0] == 0) break; - if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; - } - - if (!valid) { - stbi__rewind( s ); - return 0; - } - token = stbi__hdr_gettoken(s,buffer); - if (strncmp(token, "-Y ", 3)) { - stbi__rewind( s ); - return 0; - } - token += 3; - *y = (int) strtol(token, &token, 10); - while (*token == ' ') ++token; - if (strncmp(token, "+X ", 3)) { - stbi__rewind( s ); - return 0; - } - token += 3; - *x = (int) strtol(token, NULL, 10); - *comp = 3; - return 1; -} -#endif // STBI_NO_HDR - -#ifndef STBI_NO_BMP -static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) -{ - void *p; - stbi__bmp_data info; - - info.all_a = 255; - p = stbi__bmp_parse_header(s, &info); - stbi__rewind( s ); - if (p == NULL) - return 0; - *x = s->img_x; - *y = s->img_y; - *comp = info.ma ? 4 : 3; - return 1; -} -#endif - -#ifndef STBI_NO_PSD -static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) -{ - int channelCount; - if (stbi__get32be(s) != 0x38425053) { - stbi__rewind( s ); - return 0; - } - if (stbi__get16be(s) != 1) { - stbi__rewind( s ); - return 0; - } - stbi__skip(s, 6); - channelCount = stbi__get16be(s); - if (channelCount < 0 || channelCount > 16) { - stbi__rewind( s ); - return 0; - } - *y = stbi__get32be(s); - *x = stbi__get32be(s); - if (stbi__get16be(s) != 8) { - stbi__rewind( s ); - return 0; - } - if (stbi__get16be(s) != 3) { - stbi__rewind( s ); - return 0; - } - *comp = 4; - return 1; -} -#endif - -#ifndef STBI_NO_PIC -static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) -{ - int act_comp=0,num_packets=0,chained; - stbi__pic_packet packets[10]; - - if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { - stbi__rewind(s); - return 0; - } - - stbi__skip(s, 88); - - *x = stbi__get16be(s); - *y = stbi__get16be(s); - if (stbi__at_eof(s)) { - stbi__rewind( s); - return 0; - } - if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { - stbi__rewind( s ); - return 0; - } - - stbi__skip(s, 8); - - do { - stbi__pic_packet *packet; - - if (num_packets==sizeof(packets)/sizeof(packets[0])) - return 0; - - packet = &packets[num_packets++]; - chained = stbi__get8(s); - packet->size = stbi__get8(s); - packet->type = stbi__get8(s); - packet->channel = stbi__get8(s); - act_comp |= packet->channel; - - if (stbi__at_eof(s)) { - stbi__rewind( s ); - return 0; - } - if (packet->size != 8) { - stbi__rewind( s ); - return 0; - } - } while (chained); - - *comp = (act_comp & 0x10 ? 4 : 3); - - return 1; -} -#endif - -// ************************************************************************************************* -// Portable Gray Map and Portable Pixel Map loader -// by Ken Miller -// -// PGM: http://netpbm.sourceforge.net/doc/pgm.html -// PPM: http://netpbm.sourceforge.net/doc/ppm.html -// -// Known limitations: -// Does not support comments in the header section -// Does not support ASCII image data (formats P2 and P3) -// Does not support 16-bit-per-channel - -#ifndef STBI_NO_PNM - -static int stbi__pnm_test(stbi__context *s) -{ - char p, t; - p = (char) stbi__get8(s); - t = (char) stbi__get8(s); - if (p != 'P' || (t != '5' && t != '6')) { - stbi__rewind( s ); - return 0; - } - return 1; -} - -static stbi_uc *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - stbi_uc *out; - if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n)) - return 0; - *x = s->img_x; - *y = s->img_y; - *comp = s->img_n; - - out = (stbi_uc *) stbi__malloc(s->img_n * s->img_x * s->img_y); - if (!out) return stbi__errpuc("outofmem", "Out of memory"); - stbi__getn(s, out, s->img_n * s->img_x * s->img_y); - - if (req_comp && req_comp != s->img_n) { - out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); - if (out == NULL) return out; // stbi__convert_format frees input on failure - } - return out; -} - -static int stbi__pnm_isspace(char c) -{ - return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; -} - -static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) -{ - for (;;) { - while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) - *c = (char) stbi__get8(s); - - if (stbi__at_eof(s) || *c != '#') - break; - - while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) - *c = (char) stbi__get8(s); - } -} - -static int stbi__pnm_isdigit(char c) -{ - return c >= '0' && c <= '9'; -} - -static int stbi__pnm_getinteger(stbi__context *s, char *c) -{ - int value = 0; - - while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { - value = value*10 + (*c - '0'); - *c = (char) stbi__get8(s); - } - - return value; -} - -static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) -{ - int maxv; - char c, p, t; - - stbi__rewind( s ); - - // Get identifier - p = (char) stbi__get8(s); - t = (char) stbi__get8(s); - if (p != 'P' || (t != '5' && t != '6')) { - stbi__rewind( s ); - return 0; - } - - *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm - - c = (char) stbi__get8(s); - stbi__pnm_skip_whitespace(s, &c); - - *x = stbi__pnm_getinteger(s, &c); // read width - stbi__pnm_skip_whitespace(s, &c); - - *y = stbi__pnm_getinteger(s, &c); // read height - stbi__pnm_skip_whitespace(s, &c); - - maxv = stbi__pnm_getinteger(s, &c); // read max value - - if (maxv > 255) - return stbi__err("max value > 255", "PPM image not 8-bit"); - else - return 1; -} -#endif - -static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) -{ - #ifndef STBI_NO_JPEG - if (stbi__jpeg_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PNG - if (stbi__png_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_GIF - if (stbi__gif_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_BMP - if (stbi__bmp_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PSD - if (stbi__psd_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PIC - if (stbi__pic_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PNM - if (stbi__pnm_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_HDR - if (stbi__hdr_info(s, x, y, comp)) return 1; - #endif - - // test tga last because it's a crappy test! - #ifndef STBI_NO_TGA - if (stbi__tga_info(s, x, y, comp)) - return 1; - #endif - return stbi__err("unknown image type", "Image not of any known type, or corrupt"); -} - -#ifndef STBI_NO_STDIO -STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) -{ - FILE *f = stbi__fopen(filename, "rb"); - int result; - if (!f) return stbi__err("can't fopen", "Unable to open file"); - result = stbi_info_from_file(f, x, y, comp); - fclose(f); - return result; -} - -STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) -{ - int r; - stbi__context s; - long pos = ftell(f); - stbi__start_file(&s, f); - r = stbi__info_main(&s,x,y,comp); - fseek(f,pos,SEEK_SET); - return r; -} -#endif // !STBI_NO_STDIO - -STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__info_main(&s,x,y,comp); -} - -STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); - return stbi__info_main(&s,x,y,comp); -} - -#endif // STB_IMAGE_IMPLEMENTATION - -/* - revision history: - 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED - 2.09 (2016-01-16) allow comments in PNM files - 16-bit-per-pixel TGA (not bit-per-component) - info() for TGA could break due to .hdr handling - info() for BMP to shares code instead of sloppy parse - can use STBI_REALLOC_SIZED if allocator doesn't support realloc - code cleanup - 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA - 2.07 (2015-09-13) fix compiler warnings - partial animated GIF support - limited 16-bpc PSD support - #ifdef unused functions - bug with < 92 byte PIC,PNM,HDR,TGA - 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value - 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning - 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit - 2.03 (2015-04-12) extra corruption checking (mmozeiko) - stbi_set_flip_vertically_on_load (nguillemot) - fix NEON support; fix mingw support - 2.02 (2015-01-19) fix incorrect assert, fix warning - 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 - 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG - 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) - progressive JPEG (stb) - PGM/PPM support (Ken Miller) - STBI_MALLOC,STBI_REALLOC,STBI_FREE - GIF bugfix -- seemingly never worked - STBI_NO_*, STBI_ONLY_* - 1.48 (2014-12-14) fix incorrectly-named assert() - 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) - optimize PNG (ryg) - fix bug in interlaced PNG with user-specified channel count (stb) - 1.46 (2014-08-26) - fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG - 1.45 (2014-08-16) - fix MSVC-ARM internal compiler error by wrapping malloc - 1.44 (2014-08-07) - various warning fixes from Ronny Chevalier - 1.43 (2014-07-15) - fix MSVC-only compiler problem in code changed in 1.42 - 1.42 (2014-07-09) - don't define _CRT_SECURE_NO_WARNINGS (affects user code) - fixes to stbi__cleanup_jpeg path - added STBI_ASSERT to avoid requiring assert.h - 1.41 (2014-06-25) - fix search&replace from 1.36 that messed up comments/error messages - 1.40 (2014-06-22) - fix gcc struct-initialization warning - 1.39 (2014-06-15) - fix to TGA optimization when req_comp != number of components in TGA; - fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) - add support for BMP version 5 (more ignored fields) - 1.38 (2014-06-06) - suppress MSVC warnings on integer casts truncating values - fix accidental rename of 'skip' field of I/O - 1.37 (2014-06-04) - remove duplicate typedef - 1.36 (2014-06-03) - convert to header file single-file library - if de-iphone isn't set, load iphone images color-swapped instead of returning NULL - 1.35 (2014-05-27) - various warnings - fix broken STBI_SIMD path - fix bug where stbi_load_from_file no longer left file pointer in correct place - fix broken non-easy path for 32-bit BMP (possibly never used) - TGA optimization by Arseny Kapoulkine - 1.34 (unknown) - use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case - 1.33 (2011-07-14) - make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements - 1.32 (2011-07-13) - support for "info" function for all supported filetypes (SpartanJ) - 1.31 (2011-06-20) - a few more leak fixes, bug in PNG handling (SpartanJ) - 1.30 (2011-06-11) - added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) - removed deprecated format-specific test/load functions - removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway - error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) - fix inefficiency in decoding 32-bit BMP (David Woo) - 1.29 (2010-08-16) - various warning fixes from Aurelien Pocheville - 1.28 (2010-08-01) - fix bug in GIF palette transparency (SpartanJ) - 1.27 (2010-08-01) - cast-to-stbi_uc to fix warnings - 1.26 (2010-07-24) - fix bug in file buffering for PNG reported by SpartanJ - 1.25 (2010-07-17) - refix trans_data warning (Won Chun) - 1.24 (2010-07-12) - perf improvements reading from files on platforms with lock-heavy fgetc() - minor perf improvements for jpeg - deprecated type-specific functions so we'll get feedback if they're needed - attempt to fix trans_data warning (Won Chun) - 1.23 fixed bug in iPhone support - 1.22 (2010-07-10) - removed image *writing* support - stbi_info support from Jetro Lauha - GIF support from Jean-Marc Lienher - iPhone PNG-extensions from James Brown - warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) - 1.21 fix use of 'stbi_uc' in header (reported by jon blow) - 1.20 added support for Softimage PIC, by Tom Seddon - 1.19 bug in interlaced PNG corruption check (found by ryg) - 1.18 (2008-08-02) - fix a threading bug (local mutable static) - 1.17 support interlaced PNG - 1.16 major bugfix - stbi__convert_format converted one too many pixels - 1.15 initialize some fields for thread safety - 1.14 fix threadsafe conversion bug - header-file-only version (#define STBI_HEADER_FILE_ONLY before including) - 1.13 threadsafe - 1.12 const qualifiers in the API - 1.11 Support installable IDCT, colorspace conversion routines - 1.10 Fixes for 64-bit (don't use "unsigned long") - optimized upsampling by Fabian "ryg" Giesen - 1.09 Fix format-conversion for PSD code (bad global variables!) - 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz - 1.07 attempt to fix C++ warning/errors again - 1.06 attempt to fix C++ warning/errors again - 1.05 fix TGA loading to return correct *comp and use good luminance calc - 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free - 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR - 1.02 support for (subset of) HDR files, float interface for preferred access to them - 1.01 fix bug: possible bug in handling right-side up bmps... not sure - fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all - 1.00 interface to zlib that skips zlib header - 0.99 correct handling of alpha in palette - 0.98 TGA loader by lonesock; dynamically add loaders (untested) - 0.97 jpeg errors on too large a file; also catch another malloc failure - 0.96 fix detection of invalid v value - particleman@mollyrocket forum - 0.95 during header scan, seek to markers in case of padding - 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same - 0.93 handle jpegtran output; verbose errors - 0.92 read 4,8,16,24,32-bit BMP files of several formats - 0.91 output 24-bit Windows 3.0 BMP files - 0.90 fix a few more warnings; bump version number to approach 1.0 - 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd - 0.60 fix compiling as c++ - 0.59 fix warnings: merge Dave Moore's -Wall fixes - 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian - 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available - 0.56 fix bug: zlib uncompressed mode len vs. nlen - 0.55 fix bug: restart_interval not initialized to 0 - 0.54 allow NULL for 'int *comp' - 0.53 fix bug in png 3->4; speedup png decoding - 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments - 0.51 obey req_comp requests, 1-component jpegs return as 1-component, - on 'test' only check type, not whether we support this variant - 0.50 (2006-11-19) - first released version -*/ diff --git a/third_party/nanovg/stb_truetype.h b/third_party/nanovg/stb_truetype.h deleted file mode 100644 index 62595a15f..000000000 --- a/third_party/nanovg/stb_truetype.h +++ /dev/null @@ -1,5011 +0,0 @@ -// stb_truetype.h - v1.24 - public domain -// authored from 2009-2020 by Sean Barrett / RAD Game Tools -// -// ======================================================================= -// -// NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES -// -// This library does no range checking of the offsets found in the file, -// meaning an attacker can use it to read arbitrary memory. -// -// ======================================================================= -// -// This library processes TrueType files: -// parse files -// extract glyph metrics -// extract glyph shapes -// render glyphs to one-channel bitmaps with antialiasing (box filter) -// render glyphs to one-channel SDF bitmaps (signed-distance field/function) -// -// Todo: -// non-MS cmaps -// crashproof on bad data -// hinting? (no longer patented) -// cleartype-style AA? -// optimize: use simple memory allocator for intermediates -// optimize: build edge-list directly from curves -// optimize: rasterize directly from curves? -// -// ADDITIONAL CONTRIBUTORS -// -// Mikko Mononen: compound shape support, more cmap formats -// Tor Andersson: kerning, subpixel rendering -// Dougall Johnson: OpenType / Type 2 font handling -// Daniel Ribeiro Maciel: basic GPOS-based kerning -// -// Misc other: -// Ryan Gordon -// Simon Glass -// github:IntellectualKitty -// Imanol Celaya -// Daniel Ribeiro Maciel -// -// Bug/warning reports/fixes: -// "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe -// Cass Everitt Martins Mozeiko github:aloucks -// stoiko (Haemimont Games) Cap Petschulat github:oyvindjam -// Brian Hook Omar Cornut github:vassvik -// Walter van Niftrik Ryan Griege -// David Gow Peter LaValle -// David Given Sergey Popov -// Ivan-Assen Ivanov Giumo X. Clanjor -// Anthony Pesch Higor Euripedes -// Johan Duparc Thomas Fields -// Hou Qiming Derek Vinyard -// Rob Loach Cort Stratton -// Kenney Phillis Jr. Brian Costabile -// Ken Voskuil (kaesve) -// -// VERSION HISTORY -// -// 1.24 (2020-02-05) fix warning -// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) -// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined -// 1.21 (2019-02-25) fix warning -// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() -// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod -// 1.18 (2018-01-29) add missing function -// 1.17 (2017-07-23) make more arguments const; doc fix -// 1.16 (2017-07-12) SDF support -// 1.15 (2017-03-03) make more arguments const -// 1.14 (2017-01-16) num-fonts-in-TTC function -// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts -// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual -// 1.11 (2016-04-02) fix unused-variable warning -// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef -// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly -// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges -// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; -// variant PackFontRanges to pack and render in separate phases; -// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); -// fixed an assert() bug in the new rasterizer -// replace assert() with STBTT_assert() in new rasterizer -// -// Full history can be found at the end of this file. -// -// LICENSE -// -// See end of file for license information. -// -// USAGE -// -// Include this file in whatever places need to refer to it. In ONE C/C++ -// file, write: -// #define STB_TRUETYPE_IMPLEMENTATION -// before the #include of this file. This expands out the actual -// implementation into that C/C++ file. -// -// To make the implementation private to the file that generates the implementation, -// #define STBTT_STATIC -// -// Simple 3D API (don't ship this, but it's fine for tools and quick start) -// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture -// stbtt_GetBakedQuad() -- compute quad to draw for a given char -// -// Improved 3D API (more shippable): -// #include "stb_rect_pack.h" -- optional, but you really want it -// stbtt_PackBegin() -// stbtt_PackSetOversampling() -- for improved quality on small fonts -// stbtt_PackFontRanges() -- pack and renders -// stbtt_PackEnd() -// stbtt_GetPackedQuad() -// -// "Load" a font file from a memory buffer (you have to keep the buffer loaded) -// stbtt_InitFont() -// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections -// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections -// -// Render a unicode codepoint to a bitmap -// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap -// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide -// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be -// -// Character advance/positioning -// stbtt_GetCodepointHMetrics() -// stbtt_GetFontVMetrics() -// stbtt_GetFontVMetricsOS2() -// stbtt_GetCodepointKernAdvance() -// -// Starting with version 1.06, the rasterizer was replaced with a new, -// faster and generally-more-precise rasterizer. The new rasterizer more -// accurately measures pixel coverage for anti-aliasing, except in the case -// where multiple shapes overlap, in which case it overestimates the AA pixel -// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If -// this turns out to be a problem, you can re-enable the old rasterizer with -// #define STBTT_RASTERIZER_VERSION 1 -// which will incur about a 15% speed hit. -// -// ADDITIONAL DOCUMENTATION -// -// Immediately after this block comment are a series of sample programs. -// -// After the sample programs is the "header file" section. This section -// includes documentation for each API function. -// -// Some important concepts to understand to use this library: -// -// Codepoint -// Characters are defined by unicode codepoints, e.g. 65 is -// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is -// the hiragana for "ma". -// -// Glyph -// A visual character shape (every codepoint is rendered as -// some glyph) -// -// Glyph index -// A font-specific integer ID representing a glyph -// -// Baseline -// Glyph shapes are defined relative to a baseline, which is the -// bottom of uppercase characters. Characters extend both above -// and below the baseline. -// -// Current Point -// As you draw text to the screen, you keep track of a "current point" -// which is the origin of each character. The current point's vertical -// position is the baseline. Even "baked fonts" use this model. -// -// Vertical Font Metrics -// The vertical qualities of the font, used to vertically position -// and space the characters. See docs for stbtt_GetFontVMetrics. -// -// Font Size in Pixels or Points -// The preferred interface for specifying font sizes in stb_truetype -// is to specify how tall the font's vertical extent should be in pixels. -// If that sounds good enough, skip the next paragraph. -// -// Most font APIs instead use "points", which are a common typographic -// measurement for describing font size, defined as 72 points per inch. -// stb_truetype provides a point API for compatibility. However, true -// "per inch" conventions don't make much sense on computer displays -// since different monitors have different number of pixels per -// inch. For example, Windows traditionally uses a convention that -// there are 96 pixels per inch, thus making 'inch' measurements have -// nothing to do with inches, and thus effectively defining a point to -// be 1.333 pixels. Additionally, the TrueType font data provides -// an explicit scale factor to scale a given font's glyphs to points, -// but the author has observed that this scale factor is often wrong -// for non-commercial fonts, thus making fonts scaled in points -// according to the TrueType spec incoherently sized in practice. -// -// DETAILED USAGE: -// -// Scale: -// Select how high you want the font to be, in points or pixels. -// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute -// a scale factor SF that will be used by all other functions. -// -// Baseline: -// You need to select a y-coordinate that is the baseline of where -// your text will appear. Call GetFontBoundingBox to get the baseline-relative -// bounding box for all characters. SF*-y0 will be the distance in pixels -// that the worst-case character could extend above the baseline, so if -// you want the top edge of characters to appear at the top of the -// screen where y=0, then you would set the baseline to SF*-y0. -// -// Current point: -// Set the current point where the first character will appear. The -// first character could extend left of the current point; this is font -// dependent. You can either choose a current point that is the leftmost -// point and hope, or add some padding, or check the bounding box or -// left-side-bearing of the first character to be displayed and set -// the current point based on that. -// -// Displaying a character: -// Compute the bounding box of the character. It will contain signed values -// relative to . I.e. if it returns x0,y0,x1,y1, -// then the character should be displayed in the rectangle from -// to = 32 && *text < 128) { - stbtt_aligned_quad q; - stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 - glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); - glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); - glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); - glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); - } - ++text; - } - glEnd(); -} -#endif -// -// -////////////////////////////////////////////////////////////////////////////// -// -// Complete program (this compiles): get a single bitmap, print as ASCII art -// -#if 0 -#include -#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation -#include "stb_truetype.h" - -char ttf_buffer[1<<25]; - -int main(int argc, char **argv) -{ - stbtt_fontinfo font; - unsigned char *bitmap; - int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); - - fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); - - stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); - bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); - - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) - putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); - putchar('\n'); - } - return 0; -} -#endif -// -// Output: -// -// .ii. -// @@@@@@. -// V@Mio@@o -// :i. V@V -// :oM@@M -// :@@@MM@M -// @@o o@M -// :@@. M@M -// @@@o@@@@ -// :M@@V:@@. -// -////////////////////////////////////////////////////////////////////////////// -// -// Complete program: print "Hello World!" banner, with bugs -// -#if 0 -char buffer[24<<20]; -unsigned char screen[20][79]; - -int main(int arg, char **argv) -{ - stbtt_fontinfo font; - int i,j,ascent,baseline,ch=0; - float scale, xpos=2; // leave a little padding in case the character extends left - char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness - - fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); - stbtt_InitFont(&font, buffer, 0); - - scale = stbtt_ScaleForPixelHeight(&font, 15); - stbtt_GetFontVMetrics(&font, &ascent,0,0); - baseline = (int) (ascent*scale); - - while (text[ch]) { - int advance,lsb,x0,y0,x1,y1; - float x_shift = xpos - (float) floor(xpos); - stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); - stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); - stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); - // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong - // because this API is really for baking character bitmaps into textures. if you want to render - // a sequence of characters, you really need to render each bitmap to a temp buffer, then - // "alpha blend" that into the working buffer - xpos += (advance * scale); - if (text[ch+1]) - xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); - ++ch; - } - - for (j=0; j < 20; ++j) { - for (i=0; i < 78; ++i) - putchar(" .:ioVM@"[screen[j][i]>>5]); - putchar('\n'); - } - - return 0; -} -#endif - - -////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////// -//// -//// INTEGRATION WITH YOUR CODEBASE -//// -//// The following sections allow you to supply alternate definitions -//// of C library functions used by stb_truetype, e.g. if you don't -//// link with the C runtime library. - -#ifdef STB_TRUETYPE_IMPLEMENTATION - // #define your own (u)stbtt_int8/16/32 before including to override this - #ifndef stbtt_uint8 - typedef unsigned char stbtt_uint8; - typedef signed char stbtt_int8; - typedef unsigned short stbtt_uint16; - typedef signed short stbtt_int16; - typedef unsigned int stbtt_uint32; - typedef signed int stbtt_int32; - #endif - - typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; - typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; - - // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h - #ifndef STBTT_ifloor - #include - #define STBTT_ifloor(x) ((int) floor(x)) - #define STBTT_iceil(x) ((int) ceil(x)) - #endif - - #ifndef STBTT_sqrt - #include - #define STBTT_sqrt(x) sqrt(x) - #define STBTT_pow(x,y) pow(x,y) - #endif - - #ifndef STBTT_fmod - #include - #define STBTT_fmod(x,y) fmod(x,y) - #endif - - #ifndef STBTT_cos - #include - #define STBTT_cos(x) cos(x) - #define STBTT_acos(x) acos(x) - #endif - - #ifndef STBTT_fabs - #include - #define STBTT_fabs(x) fabs(x) - #endif - - // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h - #ifndef STBTT_malloc - #include - #define STBTT_malloc(x,u) ((void)(u),malloc(x)) - #define STBTT_free(x,u) ((void)(u),free(x)) - #endif - - #ifndef STBTT_assert - #include - #define STBTT_assert(x) assert(x) - #endif - - #ifndef STBTT_strlen - #include - #define STBTT_strlen(x) strlen(x) - #endif - - #ifndef STBTT_memcpy - #include - #define STBTT_memcpy memcpy - #define STBTT_memset memset - #endif -#endif - -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -//// -//// INTERFACE -//// -//// - -#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ -#define __STB_INCLUDE_STB_TRUETYPE_H__ - -#ifdef STBTT_STATIC -#define STBTT_DEF static -#else -#define STBTT_DEF extern -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -// private structure -typedef struct -{ - unsigned char *data; - int cursor; - int size; -} stbtt__buf; - -////////////////////////////////////////////////////////////////////////////// -// -// TEXTURE BAKING API -// -// If you use this API, you only have to call two functions ever. -// - -typedef struct -{ - unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap - float xoff,yoff,xadvance; -} stbtt_bakedchar; - -STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) - float pixel_height, // height of font in pixels - unsigned char *pixels, int pw, int ph, // bitmap to be filled in - int first_char, int num_chars, // characters to bake - stbtt_bakedchar *chardata); // you allocate this, it's num_chars long -// if return is positive, the first unused row of the bitmap -// if return is negative, returns the negative of the number of characters that fit -// if return is 0, no characters fit and no rows were used -// This uses a very crappy packing. - -typedef struct -{ - float x0,y0,s0,t0; // top-left - float x1,y1,s1,t1; // bottom-right -} stbtt_aligned_quad; - -STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above - int char_index, // character to display - float *xpos, float *ypos, // pointers to current position in screen pixel space - stbtt_aligned_quad *q, // output: quad to draw - int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier -// Call GetBakedQuad with char_index = 'character - first_char', and it -// creates the quad you need to draw and advances the current position. -// -// The coordinate system used assumes y increases downwards. -// -// Characters will extend both above and below the current position; -// see discussion of "BASELINE" above. -// -// It's inefficient; you might want to c&p it and optimize it. - -STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); -// Query the font vertical metrics without having to create a font first. - - -////////////////////////////////////////////////////////////////////////////// -// -// NEW TEXTURE BAKING API -// -// This provides options for packing multiple fonts into one atlas, not -// perfectly but better than nothing. - -typedef struct -{ - unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap - float xoff,yoff,xadvance; - float xoff2,yoff2; -} stbtt_packedchar; - -typedef struct stbtt_pack_context stbtt_pack_context; -typedef struct stbtt_fontinfo stbtt_fontinfo; -#ifndef STB_RECT_PACK_VERSION -typedef struct stbrp_rect stbrp_rect; -#endif - -STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); -// Initializes a packing context stored in the passed-in stbtt_pack_context. -// Future calls using this context will pack characters into the bitmap passed -// in here: a 1-channel bitmap that is width * height. stride_in_bytes is -// the distance from one row to the next (or 0 to mean they are packed tightly -// together). "padding" is the amount of padding to leave between each -// character (normally you want '1' for bitmaps you'll use as textures with -// bilinear filtering). -// -// Returns 0 on failure, 1 on success. - -STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); -// Cleans up the packing context and frees all memory. - -#define STBTT_POINT_SIZE(x) (-(x)) - -STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, - int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); -// Creates character bitmaps from the font_index'th font found in fontdata (use -// font_index=0 if you don't know what that is). It creates num_chars_in_range -// bitmaps for characters with unicode values starting at first_unicode_char_in_range -// and increasing. Data for how to render them is stored in chardata_for_range; -// pass these to stbtt_GetPackedQuad to get back renderable quads. -// -// font_size is the full height of the character from ascender to descender, -// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed -// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() -// and pass that result as 'font_size': -// ..., 20 , ... // font max minus min y is 20 pixels tall -// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall - -typedef struct -{ - float font_size; - int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint - int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints - int num_chars; - stbtt_packedchar *chardata_for_range; // output - unsigned char h_oversample, v_oversample; // don't set these, they're used internally -} stbtt_pack_range; - -STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); -// Creates character bitmaps from multiple ranges of characters stored in -// ranges. This will usually create a better-packed bitmap than multiple -// calls to stbtt_PackFontRange. Note that you can call this multiple -// times within a single PackBegin/PackEnd. - -STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); -// Oversampling a font increases the quality by allowing higher-quality subpixel -// positioning, and is especially valuable at smaller text sizes. -// -// This function sets the amount of oversampling for all following calls to -// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given -// pack context. The default (no oversampling) is achieved by h_oversample=1 -// and v_oversample=1. The total number of pixels required is -// h_oversample*v_oversample larger than the default; for example, 2x2 -// oversampling requires 4x the storage of 1x1. For best results, render -// oversampled textures with bilinear filtering. Look at the readme in -// stb/tests/oversample for information about oversampled fonts -// -// To use with PackFontRangesGather etc., you must set it before calls -// call to PackFontRangesGatherRects. - -STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); -// If skip != 0, this tells stb_truetype to skip any codepoints for which -// there is no corresponding glyph. If skip=0, which is the default, then -// codepoints without a glyph recived the font's "missing character" glyph, -// typically an empty box by convention. - -STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above - int char_index, // character to display - float *xpos, float *ypos, // pointers to current position in screen pixel space - stbtt_aligned_quad *q, // output: quad to draw - int align_to_integer); - -STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); -STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); -STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); -// Calling these functions in sequence is roughly equivalent to calling -// stbtt_PackFontRanges(). If you more control over the packing of multiple -// fonts, or if you want to pack custom data into a font texture, take a look -// at the source to of stbtt_PackFontRanges() and create a custom version -// using these functions, e.g. call GatherRects multiple times, -// building up a single array of rects, then call PackRects once, -// then call RenderIntoRects repeatedly. This may result in a -// better packing than calling PackFontRanges multiple times -// (or it may not). - -// this is an opaque structure that you shouldn't mess with which holds -// all the context needed from PackBegin to PackEnd. -struct stbtt_pack_context { - void *user_allocator_context; - void *pack_info; - int width; - int height; - int stride_in_bytes; - int padding; - int skip_missing; - unsigned int h_oversample, v_oversample; - unsigned char *pixels; - void *nodes; -}; - -////////////////////////////////////////////////////////////////////////////// -// -// FONT LOADING -// -// - -STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); -// This function will determine the number of fonts in a font file. TrueType -// collection (.ttc) files may contain multiple fonts, while TrueType font -// (.ttf) files only contain one font. The number of fonts can be used for -// indexing with the previous function where the index is between zero and one -// less than the total fonts. If an error occurs, -1 is returned. - -STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); -// Each .ttf/.ttc file may have more than one font. Each font has a sequential -// index number starting from 0. Call this function to get the font offset for -// a given index; it returns -1 if the index is out of range. A regular .ttf -// file will only define one font and it always be at offset 0, so it will -// return '0' for index 0, and -1 for all other indices. - -// The following structure is defined publicly so you can declare one on -// the stack or as a global or etc, but you should treat it as opaque. -struct stbtt_fontinfo -{ - void * userdata; - unsigned char * data; // pointer to .ttf file - int fontstart; // offset of start of font - - int numGlyphs; // number of glyphs, needed for range checking - - int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf - int index_map; // a cmap mapping for our chosen character encoding - int indexToLocFormat; // format needed to map from glyph index to glyph - - stbtt__buf cff; // cff font data - stbtt__buf charstrings; // the charstring index - stbtt__buf gsubrs; // global charstring subroutines index - stbtt__buf subrs; // private charstring subroutines index - stbtt__buf fontdicts; // array of font dicts - stbtt__buf fdselect; // map from glyph to fontdict -}; - -STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); -// Given an offset into the file that defines a font, this function builds -// the necessary cached info for the rest of the system. You must allocate -// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't -// need to do anything special to free it, because the contents are pure -// value data with no additional data structures. Returns 0 on failure. - - -////////////////////////////////////////////////////////////////////////////// -// -// CHARACTER TO GLYPH-INDEX CONVERSIOn - -STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); -// If you're going to perform multiple operations on the same character -// and you want a speed-up, call this function with the character you're -// going to process, then use glyph-based functions instead of the -// codepoint-based functions. -// Returns 0 if the character codepoint is not defined in the font. - - -////////////////////////////////////////////////////////////////////////////// -// -// CHARACTER PROPERTIES -// - -STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); -// computes a scale factor to produce a font whose "height" is 'pixels' tall. -// Height is measured as the distance from the highest ascender to the lowest -// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics -// and computing: -// scale = pixels / (ascent - descent) -// so if you prefer to measure height by the ascent only, use a similar calculation. - -STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); -// computes a scale factor to produce a font whose EM size is mapped to -// 'pixels' tall. This is probably what traditional APIs compute, but -// I'm not positive. - -STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); -// ascent is the coordinate above the baseline the font extends; descent -// is the coordinate below the baseline the font extends (i.e. it is typically negative) -// lineGap is the spacing between one row's descent and the next row's ascent... -// so you should advance the vertical position by "*ascent - *descent + *lineGap" -// these are expressed in unscaled coordinates, so you must multiply by -// the scale factor for a given size - -STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); -// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 -// table (specific to MS/Windows TTF files). -// -// Returns 1 on success (table present), 0 on failure. - -STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); -// the bounding box around all possible characters - -STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); -// leftSideBearing is the offset from the current horizontal position to the left edge of the character -// advanceWidth is the offset from the current horizontal position to the next horizontal position -// these are expressed in unscaled coordinates - -STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); -// an additional amount to add to the 'advance' value between ch1 and ch2 - -STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); -// Gets the bounding box of the visible part of the glyph, in unscaled coordinates - -STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); -STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); -STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); -// as above, but takes one or more glyph indices for greater efficiency - -typedef struct stbtt_kerningentry -{ - int glyph1; // use stbtt_FindGlyphIndex - int glyph2; - int advance; -} stbtt_kerningentry; - -STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info); -STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length); -// Retrieves a complete list of all of the kerning pairs provided by the font -// stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write. -// The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1) - -////////////////////////////////////////////////////////////////////////////// -// -// GLYPH SHAPES (you probably don't need these, but they have to go before -// the bitmaps for C declaration-order reasons) -// - -#ifndef STBTT_vmove // you can predefine these to use different values (but why?) - enum { - STBTT_vmove=1, - STBTT_vline, - STBTT_vcurve, - STBTT_vcubic - }; -#endif - -#ifndef stbtt_vertex // you can predefine this to use different values - // (we share this with other code at RAD) - #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file - typedef struct - { - stbtt_vertex_type x,y,cx,cy,cx1,cy1; - unsigned char type,padding; - } stbtt_vertex; -#endif - -STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); -// returns non-zero if nothing is drawn for this glyph - -STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); -STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); -// returns # of vertices and fills *vertices with the pointer to them -// these are expressed in "unscaled" coordinates -// -// The shape is a series of contours. Each one starts with -// a STBTT_moveto, then consists of a series of mixed -// STBTT_lineto and STBTT_curveto segments. A lineto -// draws a line from previous endpoint to its x,y; a curveto -// draws a quadratic bezier from previous endpoint to -// its x,y, using cx,cy as the bezier control point. - -STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); -// frees the data allocated above - -STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg); -STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg); -// fills svg with the character's SVG data. -// returns data size or 0 if SVG not found. - -////////////////////////////////////////////////////////////////////////////// -// -// BITMAP RENDERING -// - -STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); -// frees the bitmap allocated below - -STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); -// allocates a large-enough single-channel 8bpp bitmap and renders the -// specified character/glyph at the specified scale into it, with -// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). -// *width & *height are filled out with the width & height of the bitmap, -// which is stored left-to-right, top-to-bottom. -// -// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap - -STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); -// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel -// shift for the character - -STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); -// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap -// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap -// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the -// width and height and positioning info for it first. - -STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); -// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel -// shift for the character - -STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); -// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering -// is performed (see stbtt_PackSetOversampling) - -STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); -// get the bbox of the bitmap centered around the glyph origin; so the -// bitmap width is ix1-ix0, height is iy1-iy0, and location to place -// the bitmap top left is (leftSideBearing*scale,iy0). -// (Note that the bitmap uses y-increases-down, but the shape uses -// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) - -STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); -// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel -// shift for the character - -// the following functions are equivalent to the above functions, but operate -// on glyph indices instead of Unicode codepoints (for efficiency) -STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); -STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); -STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); -STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); -STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); -STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); -STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); - - -// @TODO: don't expose this structure -typedef struct -{ - int w,h,stride; - unsigned char *pixels; -} stbtt__bitmap; - -// rasterize a shape with quadratic beziers into a bitmap -STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into - float flatness_in_pixels, // allowable error of curve in pixels - stbtt_vertex *vertices, // array of vertices defining shape - int num_verts, // number of vertices in above array - float scale_x, float scale_y, // scale applied to input vertices - float shift_x, float shift_y, // translation applied to input vertices - int x_off, int y_off, // another translation applied to input - int invert, // if non-zero, vertically flip shape - void *userdata); // context for to STBTT_MALLOC - -////////////////////////////////////////////////////////////////////////////// -// -// Signed Distance Function (or Field) rendering - -STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); -// frees the SDF bitmap allocated below - -STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); -STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); -// These functions compute a discretized SDF field for a single character, suitable for storing -// in a single-channel texture, sampling with bilinear filtering, and testing against -// larger than some threshold to produce scalable fonts. -// info -- the font -// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap -// glyph/codepoint -- the character to generate the SDF for -// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), -// which allows effects like bit outlines -// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) -// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) -// if positive, > onedge_value is inside; if negative, < onedge_value is inside -// width,height -- output height & width of the SDF bitmap (including padding) -// xoff,yoff -- output origin of the character -// return value -- a 2D array of bytes 0..255, width*height in size -// -// pixel_dist_scale & onedge_value are a scale & bias that allows you to make -// optimal use of the limited 0..255 for your application, trading off precision -// and special effects. SDF values outside the range 0..255 are clamped to 0..255. -// -// Example: -// scale = stbtt_ScaleForPixelHeight(22) -// padding = 5 -// onedge_value = 180 -// pixel_dist_scale = 180/5.0 = 36.0 -// -// This will create an SDF bitmap in which the character is about 22 pixels -// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled -// shape, sample the SDF at each pixel and fill the pixel if the SDF value -// is greater than or equal to 180/255. (You'll actually want to antialias, -// which is beyond the scope of this example.) Additionally, you can compute -// offset outlines (e.g. to stroke the character border inside & outside, -// or only outside). For example, to fill outside the character up to 3 SDF -// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above -// choice of variables maps a range from 5 pixels outside the shape to -// 2 pixels inside the shape to 0..255; this is intended primarily for apply -// outside effects only (the interior range is needed to allow proper -// antialiasing of the font at *smaller* sizes) -// -// The function computes the SDF analytically at each SDF pixel, not by e.g. -// building a higher-res bitmap and approximating it. In theory the quality -// should be as high as possible for an SDF of this size & representation, but -// unclear if this is true in practice (perhaps building a higher-res bitmap -// and computing from that can allow drop-out prevention). -// -// The algorithm has not been optimized at all, so expect it to be slow -// if computing lots of characters or very large sizes. - - - -////////////////////////////////////////////////////////////////////////////// -// -// Finding the right font... -// -// You should really just solve this offline, keep your own tables -// of what font is what, and don't try to get it out of the .ttf file. -// That's because getting it out of the .ttf file is really hard, because -// the names in the file can appear in many possible encodings, in many -// possible languages, and e.g. if you need a case-insensitive comparison, -// the details of that depend on the encoding & language in a complex way -// (actually underspecified in truetype, but also gigantic). -// -// But you can use the provided functions in two possible ways: -// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on -// unicode-encoded names to try to find the font you want; -// you can run this before calling stbtt_InitFont() -// -// stbtt_GetFontNameString() lets you get any of the various strings -// from the file yourself and do your own comparisons on them. -// You have to have called stbtt_InitFont() first. - - -STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); -// returns the offset (not index) of the font that matches, or -1 if none -// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". -// if you use any other flag, use a font name like "Arial"; this checks -// the 'macStyle' header field; i don't know if fonts set this consistently -#define STBTT_MACSTYLE_DONTCARE 0 -#define STBTT_MACSTYLE_BOLD 1 -#define STBTT_MACSTYLE_ITALIC 2 -#define STBTT_MACSTYLE_UNDERSCORE 4 -#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 - -STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); -// returns 1/0 whether the first string interpreted as utf8 is identical to -// the second string interpreted as big-endian utf16... useful for strings from next func - -STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); -// returns the string (which may be big-endian double byte, e.g. for unicode) -// and puts the length in bytes in *length. -// -// some of the values for the IDs are below; for more see the truetype spec: -// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html -// http://www.microsoft.com/typography/otspec/name.htm - -enum { // platformID - STBTT_PLATFORM_ID_UNICODE =0, - STBTT_PLATFORM_ID_MAC =1, - STBTT_PLATFORM_ID_ISO =2, - STBTT_PLATFORM_ID_MICROSOFT =3 -}; - -enum { // encodingID for STBTT_PLATFORM_ID_UNICODE - STBTT_UNICODE_EID_UNICODE_1_0 =0, - STBTT_UNICODE_EID_UNICODE_1_1 =1, - STBTT_UNICODE_EID_ISO_10646 =2, - STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, - STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 -}; - -enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT - STBTT_MS_EID_SYMBOL =0, - STBTT_MS_EID_UNICODE_BMP =1, - STBTT_MS_EID_SHIFTJIS =2, - STBTT_MS_EID_UNICODE_FULL =10 -}; - -enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes - STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, - STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, - STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, - STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 -}; - -enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... - // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs - STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, - STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, - STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, - STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, - STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, - STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D -}; - -enum { // languageID for STBTT_PLATFORM_ID_MAC - STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, - STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, - STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, - STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , - STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , - STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, - STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 -}; - -#ifdef __cplusplus -} -#endif - -#endif // __STB_INCLUDE_STB_TRUETYPE_H__ - -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -//// -//// IMPLEMENTATION -//// -//// - -#ifdef STB_TRUETYPE_IMPLEMENTATION - -#ifndef STBTT_MAX_OVERSAMPLE -#define STBTT_MAX_OVERSAMPLE 8 -#endif - -#if STBTT_MAX_OVERSAMPLE > 255 -#error "STBTT_MAX_OVERSAMPLE cannot be > 255" -#endif - -typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; - -#ifndef STBTT_RASTERIZER_VERSION -#define STBTT_RASTERIZER_VERSION 2 -#endif - -#ifdef _MSC_VER -#define STBTT__NOTUSED(v) (void)(v) -#else -#define STBTT__NOTUSED(v) (void)sizeof(v) -#endif - -////////////////////////////////////////////////////////////////////////// -// -// stbtt__buf helpers to parse data from file -// - -static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) -{ - if (b->cursor >= b->size) - return 0; - return b->data[b->cursor++]; -} - -static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) -{ - if (b->cursor >= b->size) - return 0; - return b->data[b->cursor]; -} - -static void stbtt__buf_seek(stbtt__buf *b, int o) -{ - STBTT_assert(!(o > b->size || o < 0)); - b->cursor = (o > b->size || o < 0) ? b->size : o; -} - -static void stbtt__buf_skip(stbtt__buf *b, int o) -{ - stbtt__buf_seek(b, b->cursor + o); -} - -static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) -{ - stbtt_uint32 v = 0; - int i; - STBTT_assert(n >= 1 && n <= 4); - for (i = 0; i < n; i++) - v = (v << 8) | stbtt__buf_get8(b); - return v; -} - -static stbtt__buf stbtt__new_buf(const void *p, size_t size) -{ - stbtt__buf r; - STBTT_assert(size < 0x40000000); - r.data = (stbtt_uint8*) p; - r.size = (int) size; - r.cursor = 0; - return r; -} - -#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) -#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) - -static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) -{ - stbtt__buf r = stbtt__new_buf(NULL, 0); - if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; - r.data = b->data + o; - r.size = s; - return r; -} - -static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) -{ - int count, start, offsize; - start = b->cursor; - count = stbtt__buf_get16(b); - if (count) { - offsize = stbtt__buf_get8(b); - STBTT_assert(offsize >= 1 && offsize <= 4); - stbtt__buf_skip(b, offsize * count); - stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); - } - return stbtt__buf_range(b, start, b->cursor - start); -} - -static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) -{ - int b0 = stbtt__buf_get8(b); - if (b0 >= 32 && b0 <= 246) return b0 - 139; - else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; - else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; - else if (b0 == 28) return stbtt__buf_get16(b); - else if (b0 == 29) return stbtt__buf_get32(b); - STBTT_assert(0); - return 0; -} - -static void stbtt__cff_skip_operand(stbtt__buf *b) { - int v, b0 = stbtt__buf_peek8(b); - STBTT_assert(b0 >= 28); - if (b0 == 30) { - stbtt__buf_skip(b, 1); - while (b->cursor < b->size) { - v = stbtt__buf_get8(b); - if ((v & 0xF) == 0xF || (v >> 4) == 0xF) - break; - } - } else { - stbtt__cff_int(b); - } -} - -static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) -{ - stbtt__buf_seek(b, 0); - while (b->cursor < b->size) { - int start = b->cursor, end, op; - while (stbtt__buf_peek8(b) >= 28) - stbtt__cff_skip_operand(b); - end = b->cursor; - op = stbtt__buf_get8(b); - if (op == 12) op = stbtt__buf_get8(b) | 0x100; - if (op == key) return stbtt__buf_range(b, start, end-start); - } - return stbtt__buf_range(b, 0, 0); -} - -static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) -{ - int i; - stbtt__buf operands = stbtt__dict_get(b, key); - for (i = 0; i < outcount && operands.cursor < operands.size; i++) - out[i] = stbtt__cff_int(&operands); -} - -static int stbtt__cff_index_count(stbtt__buf *b) -{ - stbtt__buf_seek(b, 0); - return stbtt__buf_get16(b); -} - -static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) -{ - int count, offsize, start, end; - stbtt__buf_seek(&b, 0); - count = stbtt__buf_get16(&b); - offsize = stbtt__buf_get8(&b); - STBTT_assert(i >= 0 && i < count); - STBTT_assert(offsize >= 1 && offsize <= 4); - stbtt__buf_skip(&b, i*offsize); - start = stbtt__buf_get(&b, offsize); - end = stbtt__buf_get(&b, offsize); - return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); -} - -////////////////////////////////////////////////////////////////////////// -// -// accessors to parse data from file -// - -// on platforms that don't allow misaligned reads, if we want to allow -// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE - -#define ttBYTE(p) (* (stbtt_uint8 *) (p)) -#define ttCHAR(p) (* (stbtt_int8 *) (p)) -#define ttFixed(p) ttLONG(p) - -static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } -static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } -static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } -static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } - -#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) -#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) - -static int stbtt__isfont(stbtt_uint8 *font) -{ - // check the version number - if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 - if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! - if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF - if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 - if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts - return 0; -} - -// @OPTIMIZE: binary search -static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) -{ - stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); - stbtt_uint32 tabledir = fontstart + 12; - stbtt_int32 i; - for (i=0; i < num_tables; ++i) { - stbtt_uint32 loc = tabledir + 16*i; - if (stbtt_tag(data+loc+0, tag)) - return ttULONG(data+loc+8); - } - return 0; -} - -static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) -{ - // if it's just a font, there's only one valid index - if (stbtt__isfont(font_collection)) - return index == 0 ? 0 : -1; - - // check if it's a TTC - if (stbtt_tag(font_collection, "ttcf")) { - // version 1? - if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { - stbtt_int32 n = ttLONG(font_collection+8); - if (index >= n) - return -1; - return ttULONG(font_collection+12+index*4); - } - } - return -1; -} - -static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) -{ - // if it's just a font, there's only one valid font - if (stbtt__isfont(font_collection)) - return 1; - - // check if it's a TTC - if (stbtt_tag(font_collection, "ttcf")) { - // version 1? - if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { - return ttLONG(font_collection+8); - } - } - return 0; -} - -static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) -{ - stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; - stbtt__buf pdict; - stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); - if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); - pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); - stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); - if (!subrsoff) return stbtt__new_buf(NULL, 0); - stbtt__buf_seek(&cff, private_loc[1]+subrsoff); - return stbtt__cff_get_index(&cff); -} - -// since most people won't use this, find this table the first time it's needed -static int stbtt__get_svg(stbtt_fontinfo *info) -{ - stbtt_uint32 t; - if (info->svg < 0) { - t = stbtt__find_table(info->data, info->fontstart, "SVG "); - if (t) { - stbtt_uint32 offset = ttULONG(info->data + t + 2); - info->svg = t + offset; - } else { - info->svg = 0; - } - } - return info->svg; -} - -static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) -{ - stbtt_uint32 cmap, t; - stbtt_int32 i,numTables; - - info->data = data; - info->fontstart = fontstart; - info->cff = stbtt__new_buf(NULL, 0); - - cmap = stbtt__find_table(data, fontstart, "cmap"); // required - info->loca = stbtt__find_table(data, fontstart, "loca"); // required - info->head = stbtt__find_table(data, fontstart, "head"); // required - info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required - info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required - info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required - info->kern = stbtt__find_table(data, fontstart, "kern"); // not required - info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required - - if (!cmap || !info->head || !info->hhea || !info->hmtx) - return 0; - if (info->glyf) { - // required for truetype - if (!info->loca) return 0; - } else { - // initialization for CFF / Type2 fonts (OTF) - stbtt__buf b, topdict, topdictidx; - stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; - stbtt_uint32 cff; - - cff = stbtt__find_table(data, fontstart, "CFF "); - if (!cff) return 0; - - info->fontdicts = stbtt__new_buf(NULL, 0); - info->fdselect = stbtt__new_buf(NULL, 0); - - // @TODO this should use size from table (not 512MB) - info->cff = stbtt__new_buf(data+cff, 512*1024*1024); - b = info->cff; - - // read the header - stbtt__buf_skip(&b, 2); - stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize - - // @TODO the name INDEX could list multiple fonts, - // but we just use the first one. - stbtt__cff_get_index(&b); // name INDEX - topdictidx = stbtt__cff_get_index(&b); - topdict = stbtt__cff_index_get(topdictidx, 0); - stbtt__cff_get_index(&b); // string INDEX - info->gsubrs = stbtt__cff_get_index(&b); - - stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); - stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); - stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); - stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); - info->subrs = stbtt__get_subrs(b, topdict); - - // we only support Type 2 charstrings - if (cstype != 2) return 0; - if (charstrings == 0) return 0; - - if (fdarrayoff) { - // looks like a CID font - if (!fdselectoff) return 0; - stbtt__buf_seek(&b, fdarrayoff); - info->fontdicts = stbtt__cff_get_index(&b); - info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); - } - - stbtt__buf_seek(&b, charstrings); - info->charstrings = stbtt__cff_get_index(&b); - } - - t = stbtt__find_table(data, fontstart, "maxp"); - if (t) - info->numGlyphs = ttUSHORT(data+t+4); - else - info->numGlyphs = 0xffff; - - info->svg = -1; - - // find a cmap encoding table we understand *now* to avoid searching - // later. (todo: could make this installable) - // the same regardless of glyph. - numTables = ttUSHORT(data + cmap + 2); - info->index_map = 0; - for (i=0; i < numTables; ++i) { - stbtt_uint32 encoding_record = cmap + 4 + 8 * i; - // find an encoding we understand: - switch(ttUSHORT(data+encoding_record)) { - case STBTT_PLATFORM_ID_MICROSOFT: - switch (ttUSHORT(data+encoding_record+2)) { - case STBTT_MS_EID_UNICODE_BMP: - case STBTT_MS_EID_UNICODE_FULL: - // MS/Unicode - info->index_map = cmap + ttULONG(data+encoding_record+4); - break; - } - break; - case STBTT_PLATFORM_ID_UNICODE: - // Mac/iOS has these - // all the encodingIDs are unicode, so we don't bother to check it - info->index_map = cmap + ttULONG(data+encoding_record+4); - break; - } - } - if (info->index_map == 0) - return 0; - - info->indexToLocFormat = ttUSHORT(data+info->head + 50); - return 1; -} - -STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) -{ - stbtt_uint8 *data = info->data; - stbtt_uint32 index_map = info->index_map; - - stbtt_uint16 format = ttUSHORT(data + index_map + 0); - if (format == 0) { // apple byte encoding - stbtt_int32 bytes = ttUSHORT(data + index_map + 2); - if (unicode_codepoint < bytes-6) - return ttBYTE(data + index_map + 6 + unicode_codepoint); - return 0; - } else if (format == 6) { - stbtt_uint32 first = ttUSHORT(data + index_map + 6); - stbtt_uint32 count = ttUSHORT(data + index_map + 8); - if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) - return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); - return 0; - } else if (format == 2) { - STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean - return 0; - } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges - stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; - stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; - stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); - stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; - - // do a binary search of the segments - stbtt_uint32 endCount = index_map + 14; - stbtt_uint32 search = endCount; - - if (unicode_codepoint > 0xffff) - return 0; - - // they lie from endCount .. endCount + segCount - // but searchRange is the nearest power of two, so... - if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) - search += rangeShift*2; - - // now decrement to bias correctly to find smallest - search -= 2; - while (entrySelector) { - stbtt_uint16 end; - searchRange >>= 1; - end = ttUSHORT(data + search + searchRange*2); - if (unicode_codepoint > end) - search += searchRange*2; - --entrySelector; - } - search += 2; - - { - stbtt_uint16 offset, start; - stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); - - STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); - start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); - if (unicode_codepoint < start) - return 0; - - offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); - if (offset == 0) - return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); - - return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); - } - } else if (format == 12 || format == 13) { - stbtt_uint32 ngroups = ttULONG(data+index_map+12); - stbtt_int32 low,high; - low = 0; high = (stbtt_int32)ngroups; - // Binary search the right group. - while (low < high) { - stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high - stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); - stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); - if ((stbtt_uint32) unicode_codepoint < start_char) - high = mid; - else if ((stbtt_uint32) unicode_codepoint > end_char) - low = mid+1; - else { - stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); - if (format == 12) - return start_glyph + unicode_codepoint-start_char; - else // format == 13 - return start_glyph; - } - } - return 0; // not found - } - // @TODO - STBTT_assert(0); - return 0; -} - -STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) -{ - return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); -} - -static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) -{ - v->type = type; - v->x = (stbtt_int16) x; - v->y = (stbtt_int16) y; - v->cx = (stbtt_int16) cx; - v->cy = (stbtt_int16) cy; -} - -static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) -{ - int g1,g2; - - STBTT_assert(!info->cff.size); - - if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range - if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format - - if (info->indexToLocFormat == 0) { - g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; - g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; - } else { - g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); - g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); - } - - return g1==g2 ? -1 : g1; // if length is 0, return -1 -} - -static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); - -STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) -{ - if (info->cff.size) { - stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); - } else { - int g = stbtt__GetGlyfOffset(info, glyph_index); - if (g < 0) return 0; - - if (x0) *x0 = ttSHORT(info->data + g + 2); - if (y0) *y0 = ttSHORT(info->data + g + 4); - if (x1) *x1 = ttSHORT(info->data + g + 6); - if (y1) *y1 = ttSHORT(info->data + g + 8); - } - return 1; -} - -STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) -{ - return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); -} - -STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) -{ - stbtt_int16 numberOfContours; - int g; - if (info->cff.size) - return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; - g = stbtt__GetGlyfOffset(info, glyph_index); - if (g < 0) return 1; - numberOfContours = ttSHORT(info->data + g); - return numberOfContours == 0; -} - -static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, - stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) -{ - if (start_off) { - if (was_off) - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); - } else { - if (was_off) - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); - else - stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); - } - return num_vertices; -} - -static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) -{ - stbtt_int16 numberOfContours; - stbtt_uint8 *endPtsOfContours; - stbtt_uint8 *data = info->data; - stbtt_vertex *vertices=0; - int num_vertices=0; - int g = stbtt__GetGlyfOffset(info, glyph_index); - - *pvertices = NULL; - - if (g < 0) return 0; - - numberOfContours = ttSHORT(data + g); - - if (numberOfContours > 0) { - stbtt_uint8 flags=0,flagcount; - stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; - stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; - stbtt_uint8 *points; - endPtsOfContours = (data + g + 10); - ins = ttUSHORT(data + g + 10 + numberOfContours * 2); - points = data + g + 10 + numberOfContours * 2 + 2 + ins; - - n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); - - m = n + 2*numberOfContours; // a loose bound on how many vertices we might need - vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); - if (vertices == 0) - return 0; - - next_move = 0; - flagcount=0; - - // in first pass, we load uninterpreted data into the allocated array - // above, shifted to the end of the array so we won't overwrite it when - // we create our final data starting from the front - - off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated - - // first load flags - - for (i=0; i < n; ++i) { - if (flagcount == 0) { - flags = *points++; - if (flags & 8) - flagcount = *points++; - } else - --flagcount; - vertices[off+i].type = flags; - } - - // now load x coordinates - x=0; - for (i=0; i < n; ++i) { - flags = vertices[off+i].type; - if (flags & 2) { - stbtt_int16 dx = *points++; - x += (flags & 16) ? dx : -dx; // ??? - } else { - if (!(flags & 16)) { - x = x + (stbtt_int16) (points[0]*256 + points[1]); - points += 2; - } - } - vertices[off+i].x = (stbtt_int16) x; - } - - // now load y coordinates - y=0; - for (i=0; i < n; ++i) { - flags = vertices[off+i].type; - if (flags & 4) { - stbtt_int16 dy = *points++; - y += (flags & 32) ? dy : -dy; // ??? - } else { - if (!(flags & 32)) { - y = y + (stbtt_int16) (points[0]*256 + points[1]); - points += 2; - } - } - vertices[off+i].y = (stbtt_int16) y; - } - - // now convert them to our format - num_vertices=0; - sx = sy = cx = cy = scx = scy = 0; - for (i=0; i < n; ++i) { - flags = vertices[off+i].type; - x = (stbtt_int16) vertices[off+i].x; - y = (stbtt_int16) vertices[off+i].y; - - if (next_move == i) { - if (i != 0) - num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); - - // now start the new one - start_off = !(flags & 1); - if (start_off) { - // if we start off with an off-curve point, then when we need to find a point on the curve - // where we can start, and we need to save some state for when we wraparound. - scx = x; - scy = y; - if (!(vertices[off+i+1].type & 1)) { - // next point is also a curve point, so interpolate an on-point curve - sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; - sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; - } else { - // otherwise just use the next point as our start point - sx = (stbtt_int32) vertices[off+i+1].x; - sy = (stbtt_int32) vertices[off+i+1].y; - ++i; // we're using point i+1 as the starting point, so skip it - } - } else { - sx = x; - sy = y; - } - stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); - was_off = 0; - next_move = 1 + ttUSHORT(endPtsOfContours+j*2); - ++j; - } else { - if (!(flags & 1)) { // if it's a curve - if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); - cx = x; - cy = y; - was_off = 1; - } else { - if (was_off) - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); - else - stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); - was_off = 0; - } - } - } - num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); - } else if (numberOfContours < 0) { - // Compound shapes. - int more = 1; - stbtt_uint8 *comp = data + g + 10; - num_vertices = 0; - vertices = 0; - while (more) { - stbtt_uint16 flags, gidx; - int comp_num_verts = 0, i; - stbtt_vertex *comp_verts = 0, *tmp = 0; - float mtx[6] = {1,0,0,1,0,0}, m, n; - - flags = ttSHORT(comp); comp+=2; - gidx = ttSHORT(comp); comp+=2; - - if (flags & 2) { // XY values - if (flags & 1) { // shorts - mtx[4] = ttSHORT(comp); comp+=2; - mtx[5] = ttSHORT(comp); comp+=2; - } else { - mtx[4] = ttCHAR(comp); comp+=1; - mtx[5] = ttCHAR(comp); comp+=1; - } - } - else { - // @TODO handle matching point - STBTT_assert(0); - } - if (flags & (1<<3)) { // WE_HAVE_A_SCALE - mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[1] = mtx[2] = 0; - } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE - mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[1] = mtx[2] = 0; - mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; - } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO - mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; - } - - // Find transformation scales. - m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); - n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); - - // Get indexed glyph. - comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); - if (comp_num_verts > 0) { - // Transform vertices. - for (i = 0; i < comp_num_verts; ++i) { - stbtt_vertex* v = &comp_verts[i]; - stbtt_vertex_type x,y; - x=v->x; y=v->y; - v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); - v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); - x=v->cx; y=v->cy; - v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); - v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); - } - // Append vertices. - tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); - if (!tmp) { - if (vertices) STBTT_free(vertices, info->userdata); - if (comp_verts) STBTT_free(comp_verts, info->userdata); - return 0; - } - if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); - STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); - if (vertices) STBTT_free(vertices, info->userdata); - vertices = tmp; - STBTT_free(comp_verts, info->userdata); - num_vertices += comp_num_verts; - } - // More components ? - more = flags & (1<<5); - } - } else { - // numberOfCounters == 0, do nothing - } - - *pvertices = vertices; - return num_vertices; -} - -typedef struct -{ - int bounds; - int started; - float first_x, first_y; - float x, y; - stbtt_int32 min_x, max_x, min_y, max_y; - - stbtt_vertex *pvertices; - int num_vertices; -} stbtt__csctx; - -#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} - -static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) -{ - if (x > c->max_x || !c->started) c->max_x = x; - if (y > c->max_y || !c->started) c->max_y = y; - if (x < c->min_x || !c->started) c->min_x = x; - if (y < c->min_y || !c->started) c->min_y = y; - c->started = 1; -} - -static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) -{ - if (c->bounds) { - stbtt__track_vertex(c, x, y); - if (type == STBTT_vcubic) { - stbtt__track_vertex(c, cx, cy); - stbtt__track_vertex(c, cx1, cy1); - } - } else { - stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); - c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; - c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; - } - c->num_vertices++; -} - -static void stbtt__csctx_close_shape(stbtt__csctx *ctx) -{ - if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) - stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); -} - -static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) -{ - stbtt__csctx_close_shape(ctx); - ctx->first_x = ctx->x = ctx->x + dx; - ctx->first_y = ctx->y = ctx->y + dy; - stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); -} - -static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) -{ - ctx->x += dx; - ctx->y += dy; - stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); -} - -static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) -{ - float cx1 = ctx->x + dx1; - float cy1 = ctx->y + dy1; - float cx2 = cx1 + dx2; - float cy2 = cy1 + dy2; - ctx->x = cx2 + dx3; - ctx->y = cy2 + dy3; - stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); -} - -static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) -{ - int count = stbtt__cff_index_count(&idx); - int bias = 107; - if (count >= 33900) - bias = 32768; - else if (count >= 1240) - bias = 1131; - n += bias; - if (n < 0 || n >= count) - return stbtt__new_buf(NULL, 0); - return stbtt__cff_index_get(idx, n); -} - -static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) -{ - stbtt__buf fdselect = info->fdselect; - int nranges, start, end, v, fmt, fdselector = -1, i; - - stbtt__buf_seek(&fdselect, 0); - fmt = stbtt__buf_get8(&fdselect); - if (fmt == 0) { - // untested - stbtt__buf_skip(&fdselect, glyph_index); - fdselector = stbtt__buf_get8(&fdselect); - } else if (fmt == 3) { - nranges = stbtt__buf_get16(&fdselect); - start = stbtt__buf_get16(&fdselect); - for (i = 0; i < nranges; i++) { - v = stbtt__buf_get8(&fdselect); - end = stbtt__buf_get16(&fdselect); - if (glyph_index >= start && glyph_index < end) { - fdselector = v; - break; - } - start = end; - } - } - if (fdselector == -1) stbtt__new_buf(NULL, 0); - return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); -} - -static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) -{ - int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; - int has_subrs = 0, clear_stack; - float s[48]; - stbtt__buf subr_stack[10], subrs = info->subrs, b; - float f; - -#define STBTT__CSERR(s) (0) - - // this currently ignores the initial width value, which isn't needed if we have hmtx - b = stbtt__cff_index_get(info->charstrings, glyph_index); - while (b.cursor < b.size) { - i = 0; - clear_stack = 1; - b0 = stbtt__buf_get8(&b); - switch (b0) { - // @TODO implement hinting - case 0x13: // hintmask - case 0x14: // cntrmask - if (in_header) - maskbits += (sp / 2); // implicit "vstem" - in_header = 0; - stbtt__buf_skip(&b, (maskbits + 7) / 8); - break; - - case 0x01: // hstem - case 0x03: // vstem - case 0x12: // hstemhm - case 0x17: // vstemhm - maskbits += (sp / 2); - break; - - case 0x15: // rmoveto - in_header = 0; - if (sp < 2) return STBTT__CSERR("rmoveto stack"); - stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); - break; - case 0x04: // vmoveto - in_header = 0; - if (sp < 1) return STBTT__CSERR("vmoveto stack"); - stbtt__csctx_rmove_to(c, 0, s[sp-1]); - break; - case 0x16: // hmoveto - in_header = 0; - if (sp < 1) return STBTT__CSERR("hmoveto stack"); - stbtt__csctx_rmove_to(c, s[sp-1], 0); - break; - - case 0x05: // rlineto - if (sp < 2) return STBTT__CSERR("rlineto stack"); - for (; i + 1 < sp; i += 2) - stbtt__csctx_rline_to(c, s[i], s[i+1]); - break; - - // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical - // starting from a different place. - - case 0x07: // vlineto - if (sp < 1) return STBTT__CSERR("vlineto stack"); - goto vlineto; - case 0x06: // hlineto - if (sp < 1) return STBTT__CSERR("hlineto stack"); - for (;;) { - if (i >= sp) break; - stbtt__csctx_rline_to(c, s[i], 0); - i++; - vlineto: - if (i >= sp) break; - stbtt__csctx_rline_to(c, 0, s[i]); - i++; - } - break; - - case 0x1F: // hvcurveto - if (sp < 4) return STBTT__CSERR("hvcurveto stack"); - goto hvcurveto; - case 0x1E: // vhcurveto - if (sp < 4) return STBTT__CSERR("vhcurveto stack"); - for (;;) { - if (i + 3 >= sp) break; - stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); - i += 4; - hvcurveto: - if (i + 3 >= sp) break; - stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); - i += 4; - } - break; - - case 0x08: // rrcurveto - if (sp < 6) return STBTT__CSERR("rcurveline stack"); - for (; i + 5 < sp; i += 6) - stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); - break; - - case 0x18: // rcurveline - if (sp < 8) return STBTT__CSERR("rcurveline stack"); - for (; i + 5 < sp - 2; i += 6) - stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); - if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); - stbtt__csctx_rline_to(c, s[i], s[i+1]); - break; - - case 0x19: // rlinecurve - if (sp < 8) return STBTT__CSERR("rlinecurve stack"); - for (; i + 1 < sp - 6; i += 2) - stbtt__csctx_rline_to(c, s[i], s[i+1]); - if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); - stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); - break; - - case 0x1A: // vvcurveto - case 0x1B: // hhcurveto - if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); - f = 0.0; - if (sp & 1) { f = s[i]; i++; } - for (; i + 3 < sp; i += 4) { - if (b0 == 0x1B) - stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); - else - stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); - f = 0.0; - } - break; - - case 0x0A: // callsubr - if (!has_subrs) { - if (info->fdselect.size) - subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); - has_subrs = 1; - } - // fallthrough - case 0x1D: // callgsubr - if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); - v = (int) s[--sp]; - if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); - subr_stack[subr_stack_height++] = b; - b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); - if (b.size == 0) return STBTT__CSERR("subr not found"); - b.cursor = 0; - clear_stack = 0; - break; - - case 0x0B: // return - if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); - b = subr_stack[--subr_stack_height]; - clear_stack = 0; - break; - - case 0x0E: // endchar - stbtt__csctx_close_shape(c); - return 1; - - case 0x0C: { // two-byte escape - float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; - float dx, dy; - int b1 = stbtt__buf_get8(&b); - switch (b1) { - // @TODO These "flex" implementations ignore the flex-depth and resolution, - // and always draw beziers. - case 0x22: // hflex - if (sp < 7) return STBTT__CSERR("hflex stack"); - dx1 = s[0]; - dx2 = s[1]; - dy2 = s[2]; - dx3 = s[3]; - dx4 = s[4]; - dx5 = s[5]; - dx6 = s[6]; - stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); - stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); - break; - - case 0x23: // flex - if (sp < 13) return STBTT__CSERR("flex stack"); - dx1 = s[0]; - dy1 = s[1]; - dx2 = s[2]; - dy2 = s[3]; - dx3 = s[4]; - dy3 = s[5]; - dx4 = s[6]; - dy4 = s[7]; - dx5 = s[8]; - dy5 = s[9]; - dx6 = s[10]; - dy6 = s[11]; - //fd is s[12] - stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); - stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); - break; - - case 0x24: // hflex1 - if (sp < 9) return STBTT__CSERR("hflex1 stack"); - dx1 = s[0]; - dy1 = s[1]; - dx2 = s[2]; - dy2 = s[3]; - dx3 = s[4]; - dx4 = s[5]; - dx5 = s[6]; - dy5 = s[7]; - dx6 = s[8]; - stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); - stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); - break; - - case 0x25: // flex1 - if (sp < 11) return STBTT__CSERR("flex1 stack"); - dx1 = s[0]; - dy1 = s[1]; - dx2 = s[2]; - dy2 = s[3]; - dx3 = s[4]; - dy3 = s[5]; - dx4 = s[6]; - dy4 = s[7]; - dx5 = s[8]; - dy5 = s[9]; - dx6 = dy6 = s[10]; - dx = dx1+dx2+dx3+dx4+dx5; - dy = dy1+dy2+dy3+dy4+dy5; - if (STBTT_fabs(dx) > STBTT_fabs(dy)) - dy6 = -dy; - else - dx6 = -dx; - stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); - stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); - break; - - default: - return STBTT__CSERR("unimplemented"); - } - } break; - - default: - if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254)) - return STBTT__CSERR("reserved operator"); - - // push immediate - if (b0 == 255) { - f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; - } else { - stbtt__buf_skip(&b, -1); - f = (float)(stbtt_int16)stbtt__cff_int(&b); - } - if (sp >= 48) return STBTT__CSERR("push stack overflow"); - s[sp++] = f; - clear_stack = 0; - break; - } - if (clear_stack) sp = 0; - } - return STBTT__CSERR("no endchar"); - -#undef STBTT__CSERR -} - -static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) -{ - // runs the charstring twice, once to count and once to output (to avoid realloc) - stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); - stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); - if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { - *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); - output_ctx.pvertices = *pvertices; - if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { - STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); - return output_ctx.num_vertices; - } - } - *pvertices = NULL; - return 0; -} - -static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) -{ - stbtt__csctx c = STBTT__CSCTX_INIT(1); - int r = stbtt__run_charstring(info, glyph_index, &c); - if (x0) *x0 = r ? c.min_x : 0; - if (y0) *y0 = r ? c.min_y : 0; - if (x1) *x1 = r ? c.max_x : 0; - if (y1) *y1 = r ? c.max_y : 0; - return r ? c.num_vertices : 0; -} - -STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) -{ - if (!info->cff.size) - return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); - else - return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); -} - -STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) -{ - stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); - if (glyph_index < numOfLongHorMetrics) { - if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); - if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); - } else { - if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); - if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); - } -} - -STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info) -{ - stbtt_uint8 *data = info->data + info->kern; - - // we only look at the first table. it must be 'horizontal' and format 0. - if (!info->kern) - return 0; - if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 - return 0; - if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format - return 0; - - return ttUSHORT(data+10); -} - -STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length) -{ - stbtt_uint8 *data = info->data + info->kern; - int k, length; - - // we only look at the first table. it must be 'horizontal' and format 0. - if (!info->kern) - return 0; - if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 - return 0; - if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format - return 0; - - length = ttUSHORT(data+10); - if (table_length < length) - length = table_length; - - for (k = 0; k < length; k++) - { - table[k].glyph1 = ttUSHORT(data+18+(k*6)); - table[k].glyph2 = ttUSHORT(data+20+(k*6)); - table[k].advance = ttSHORT(data+22+(k*6)); - } - - return length; -} - -static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) -{ - stbtt_uint8 *data = info->data + info->kern; - stbtt_uint32 needle, straw; - int l, r, m; - - // we only look at the first table. it must be 'horizontal' and format 0. - if (!info->kern) - return 0; - if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 - return 0; - if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format - return 0; - - l = 0; - r = ttUSHORT(data+10) - 1; - needle = glyph1 << 16 | glyph2; - while (l <= r) { - m = (l + r) >> 1; - straw = ttULONG(data+18+(m*6)); // note: unaligned read - if (needle < straw) - r = m - 1; - else if (needle > straw) - l = m + 1; - else - return ttSHORT(data+22+(m*6)); - } - return 0; -} - -static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) -{ - stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); - switch(coverageFormat) { - case 1: { - stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); - - // Binary search. - stbtt_int32 l=0, r=glyphCount-1, m; - int straw, needle=glyph; - while (l <= r) { - stbtt_uint8 *glyphArray = coverageTable + 4; - stbtt_uint16 glyphID; - m = (l + r) >> 1; - glyphID = ttUSHORT(glyphArray + 2 * m); - straw = glyphID; - if (needle < straw) - r = m - 1; - else if (needle > straw) - l = m + 1; - else { - return m; - } - } - } break; - - case 2: { - stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); - stbtt_uint8 *rangeArray = coverageTable + 4; - - // Binary search. - stbtt_int32 l=0, r=rangeCount-1, m; - int strawStart, strawEnd, needle=glyph; - while (l <= r) { - stbtt_uint8 *rangeRecord; - m = (l + r) >> 1; - rangeRecord = rangeArray + 6 * m; - strawStart = ttUSHORT(rangeRecord); - strawEnd = ttUSHORT(rangeRecord + 2); - if (needle < strawStart) - r = m - 1; - else if (needle > strawEnd) - l = m + 1; - else { - stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); - return startCoverageIndex + glyph - strawStart; - } - } - } break; - - default: { - // There are no other cases. - STBTT_assert(0); - } break; - } - - return -1; -} - -static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) -{ - stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); - switch(classDefFormat) - { - case 1: { - stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); - stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); - stbtt_uint8 *classDef1ValueArray = classDefTable + 6; - - if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) - return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); - - classDefTable = classDef1ValueArray + 2 * glyphCount; - } break; - - case 2: { - stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); - stbtt_uint8 *classRangeRecords = classDefTable + 4; - - // Binary search. - stbtt_int32 l=0, r=classRangeCount-1, m; - int strawStart, strawEnd, needle=glyph; - while (l <= r) { - stbtt_uint8 *classRangeRecord; - m = (l + r) >> 1; - classRangeRecord = classRangeRecords + 6 * m; - strawStart = ttUSHORT(classRangeRecord); - strawEnd = ttUSHORT(classRangeRecord + 2); - if (needle < strawStart) - r = m - 1; - else if (needle > strawEnd) - l = m + 1; - else - return (stbtt_int32)ttUSHORT(classRangeRecord + 4); - } - - classDefTable = classRangeRecords + 6 * classRangeCount; - } break; - - default: { - // There are no other cases. - STBTT_assert(0); - } break; - } - - return -1; -} - -// Define to STBTT_assert(x) if you want to break on unimplemented formats. -#define STBTT_GPOS_TODO_assert(x) - -static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) -{ - stbtt_uint16 lookupListOffset; - stbtt_uint8 *lookupList; - stbtt_uint16 lookupCount; - stbtt_uint8 *data; - stbtt_int32 i; - - if (!info->gpos) return 0; - - data = info->data + info->gpos; - - if (ttUSHORT(data+0) != 1) return 0; // Major version 1 - if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 - - lookupListOffset = ttUSHORT(data+8); - lookupList = data + lookupListOffset; - lookupCount = ttUSHORT(lookupList); - - for (i=0; i> 1; - pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; - secondGlyph = ttUSHORT(pairValue); - straw = secondGlyph; - if (needle < straw) - r = m - 1; - else if (needle > straw) - l = m + 1; - else { - stbtt_int16 xAdvance = ttSHORT(pairValue + 2); - return xAdvance; - } - } - } break; - - case 2: { - stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); - stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); - - stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); - stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); - int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); - int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); - - stbtt_uint16 class1Count = ttUSHORT(table + 12); - stbtt_uint16 class2Count = ttUSHORT(table + 14); - STBTT_assert(glyph1class < class1Count); - STBTT_assert(glyph2class < class2Count); - - // TODO: Support more formats. - STBTT_GPOS_TODO_assert(valueFormat1 == 4); - if (valueFormat1 != 4) return 0; - STBTT_GPOS_TODO_assert(valueFormat2 == 0); - if (valueFormat2 != 0) return 0; - - if (glyph1class >= 0 && glyph1class < class1Count && glyph2class >= 0 && glyph2class < class2Count) { - stbtt_uint8 *class1Records = table + 16; - stbtt_uint8 *class2Records = class1Records + 2 * (glyph1class * class2Count); - stbtt_int16 xAdvance = ttSHORT(class2Records + 2 * glyph2class); - return xAdvance; - } - } break; - - default: { - // There are no other cases. - STBTT_assert(0); - break; - }; - } - } - break; - }; - - default: - // TODO: Implement other stuff. - break; - } - } - - return 0; -} - -STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) -{ - int xAdvance = 0; - - if (info->gpos) - xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); - else if (info->kern) - xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); - - return xAdvance; -} - -STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) -{ - if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs - return 0; - return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); -} - -STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) -{ - stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); -} - -STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) -{ - if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); - if (descent) *descent = ttSHORT(info->data+info->hhea + 6); - if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); -} - -STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) -{ - int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); - if (!tab) - return 0; - if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); - if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); - if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); - return 1; -} - -STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) -{ - *x0 = ttSHORT(info->data + info->head + 36); - *y0 = ttSHORT(info->data + info->head + 38); - *x1 = ttSHORT(info->data + info->head + 40); - *y1 = ttSHORT(info->data + info->head + 42); -} - -STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) -{ - int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); - return (float) height / fheight; -} - -STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) -{ - int unitsPerEm = ttUSHORT(info->data + info->head + 18); - return pixels / unitsPerEm; -} - -STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) -{ - STBTT_free(v, info->userdata); -} - -STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl) -{ - int i; - stbtt_uint8 *data = info->data; - stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info); - - int numEntries = ttUSHORT(svg_doc_list); - stbtt_uint8 *svg_docs = svg_doc_list + 2; - - for(i=0; i= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) - return svg_doc; - } - return 0; -} - -STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg) -{ - stbtt_uint8 *data = info->data; - stbtt_uint8 *svg_doc; - - if (info->svg == 0) - return 0; - - svg_doc = stbtt_FindSVGDoc(info, gl); - if (svg_doc != NULL) { - *svg = (char *) data + info->svg + ttULONG(svg_doc + 4); - return ttULONG(svg_doc + 8); - } else { - return 0; - } -} - -STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg) -{ - return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg); -} - -////////////////////////////////////////////////////////////////////////////// -// -// antialiasing software rasterizer -// - -STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) -{ - int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning - if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { - // e.g. space character - if (ix0) *ix0 = 0; - if (iy0) *iy0 = 0; - if (ix1) *ix1 = 0; - if (iy1) *iy1 = 0; - } else { - // move to integral bboxes (treating pixels as little squares, what pixels get touched)? - if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); - if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); - if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); - if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); - } -} - -STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) -{ - stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); -} - -STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) -{ - stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); -} - -STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) -{ - stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); -} - -////////////////////////////////////////////////////////////////////////////// -// -// Rasterizer - -typedef struct stbtt__hheap_chunk -{ - struct stbtt__hheap_chunk *next; -} stbtt__hheap_chunk; - -typedef struct stbtt__hheap -{ - struct stbtt__hheap_chunk *head; - void *first_free; - int num_remaining_in_head_chunk; -} stbtt__hheap; - -static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) -{ - if (hh->first_free) { - void *p = hh->first_free; - hh->first_free = * (void **) p; - return p; - } else { - if (hh->num_remaining_in_head_chunk == 0) { - int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); - stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); - if (c == NULL) - return NULL; - c->next = hh->head; - hh->head = c; - hh->num_remaining_in_head_chunk = count; - } - --hh->num_remaining_in_head_chunk; - return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; - } -} - -static void stbtt__hheap_free(stbtt__hheap *hh, void *p) -{ - *(void **) p = hh->first_free; - hh->first_free = p; -} - -static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) -{ - stbtt__hheap_chunk *c = hh->head; - while (c) { - stbtt__hheap_chunk *n = c->next; - STBTT_free(c, userdata); - c = n; - } -} - -typedef struct stbtt__edge { - float x0,y0, x1,y1; - int invert; -} stbtt__edge; - - -typedef struct stbtt__active_edge -{ - struct stbtt__active_edge *next; - #if STBTT_RASTERIZER_VERSION==1 - int x,dx; - float ey; - int direction; - #elif STBTT_RASTERIZER_VERSION==2 - float fx,fdx,fdy; - float direction; - float sy; - float ey; - #else - #error "Unrecognized value of STBTT_RASTERIZER_VERSION" - #endif -} stbtt__active_edge; - -#if STBTT_RASTERIZER_VERSION == 1 -#define STBTT_FIXSHIFT 10 -#define STBTT_FIX (1 << STBTT_FIXSHIFT) -#define STBTT_FIXMASK (STBTT_FIX-1) - -static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) -{ - stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); - float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); - STBTT_assert(z != NULL); - if (!z) return z; - - // round dx down to avoid overshooting - if (dxdy < 0) - z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); - else - z->dx = STBTT_ifloor(STBTT_FIX * dxdy); - - z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount - z->x -= off_x * STBTT_FIX; - - z->ey = e->y1; - z->next = 0; - z->direction = e->invert ? 1 : -1; - return z; -} -#elif STBTT_RASTERIZER_VERSION == 2 -static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) -{ - stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); - float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); - STBTT_assert(z != NULL); - //STBTT_assert(e->y0 <= start_point); - if (!z) return z; - z->fdx = dxdy; - z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; - z->fx = e->x0 + dxdy * (start_point - e->y0); - z->fx -= off_x; - z->direction = e->invert ? 1.0f : -1.0f; - z->sy = e->y0; - z->ey = e->y1; - z->next = 0; - return z; -} -#else -#error "Unrecognized value of STBTT_RASTERIZER_VERSION" -#endif - -#if STBTT_RASTERIZER_VERSION == 1 -// note: this routine clips fills that extend off the edges... ideally this -// wouldn't happen, but it could happen if the truetype glyph bounding boxes -// are wrong, or if the user supplies a too-small bitmap -static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) -{ - // non-zero winding fill - int x0=0, w=0; - - while (e) { - if (w == 0) { - // if we're currently at zero, we need to record the edge start point - x0 = e->x; w += e->direction; - } else { - int x1 = e->x; w += e->direction; - // if we went to zero, we need to draw - if (w == 0) { - int i = x0 >> STBTT_FIXSHIFT; - int j = x1 >> STBTT_FIXSHIFT; - - if (i < len && j >= 0) { - if (i == j) { - // x0,x1 are the same pixel, so compute combined coverage - scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); - } else { - if (i >= 0) // add antialiasing for x0 - scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); - else - i = -1; // clip - - if (j < len) // add antialiasing for x1 - scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); - else - j = len; // clip - - for (++i; i < j; ++i) // fill pixels between x0 and x1 - scanline[i] = scanline[i] + (stbtt_uint8) max_weight; - } - } - } - } - - e = e->next; - } -} - -static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) -{ - stbtt__hheap hh = { 0, 0, 0 }; - stbtt__active_edge *active = NULL; - int y,j=0; - int max_weight = (255 / vsubsample); // weight per vertical scanline - int s; // vertical subsample index - unsigned char scanline_data[512], *scanline; - - if (result->w > 512) - scanline = (unsigned char *) STBTT_malloc(result->w, userdata); - else - scanline = scanline_data; - - y = off_y * vsubsample; - e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; - - while (j < result->h) { - STBTT_memset(scanline, 0, result->w); - for (s=0; s < vsubsample; ++s) { - // find center of pixel for this scanline - float scan_y = y + 0.5f; - stbtt__active_edge **step = &active; - - // update all active edges; - // remove all active edges that terminate before the center of this scanline - while (*step) { - stbtt__active_edge * z = *step; - if (z->ey <= scan_y) { - *step = z->next; // delete from list - STBTT_assert(z->direction); - z->direction = 0; - stbtt__hheap_free(&hh, z); - } else { - z->x += z->dx; // advance to position for current scanline - step = &((*step)->next); // advance through list - } - } - - // resort the list if needed - for(;;) { - int changed=0; - step = &active; - while (*step && (*step)->next) { - if ((*step)->x > (*step)->next->x) { - stbtt__active_edge *t = *step; - stbtt__active_edge *q = t->next; - - t->next = q->next; - q->next = t; - *step = q; - changed = 1; - } - step = &(*step)->next; - } - if (!changed) break; - } - - // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline - while (e->y0 <= scan_y) { - if (e->y1 > scan_y) { - stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); - if (z != NULL) { - // find insertion point - if (active == NULL) - active = z; - else if (z->x < active->x) { - // insert at front - z->next = active; - active = z; - } else { - // find thing to insert AFTER - stbtt__active_edge *p = active; - while (p->next && p->next->x < z->x) - p = p->next; - // at this point, p->next->x is NOT < z->x - z->next = p->next; - p->next = z; - } - } - } - ++e; - } - - // now process all active edges in XOR fashion - if (active) - stbtt__fill_active_edges(scanline, result->w, active, max_weight); - - ++y; - } - STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); - ++j; - } - - stbtt__hheap_cleanup(&hh, userdata); - - if (scanline != scanline_data) - STBTT_free(scanline, userdata); -} - -#elif STBTT_RASTERIZER_VERSION == 2 - -// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 -// (i.e. it has already been clipped to those) -static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) -{ - if (y0 == y1) return; - STBTT_assert(y0 < y1); - STBTT_assert(e->sy <= e->ey); - if (y0 > e->ey) return; - if (y1 < e->sy) return; - if (y0 < e->sy) { - x0 += (x1-x0) * (e->sy - y0) / (y1-y0); - y0 = e->sy; - } - if (y1 > e->ey) { - x1 += (x1-x0) * (e->ey - y1) / (y1-y0); - y1 = e->ey; - } - - if (x0 == x) - STBTT_assert(x1 <= x+1); - else if (x0 == x+1) - STBTT_assert(x1 >= x); - else if (x0 <= x) - STBTT_assert(x1 <= x); - else if (x0 >= x+1) - STBTT_assert(x1 >= x+1); - else - STBTT_assert(x1 >= x && x1 <= x+1); - - if (x0 <= x && x1 <= x) - scanline[x] += e->direction * (y1-y0); - else if (x0 >= x+1 && x1 >= x+1) - ; - else { - STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); - scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position - } -} - -static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) -{ - float y_bottom = y_top+1; - - while (e) { - // brute force every pixel - - // compute intersection points with top & bottom - STBTT_assert(e->ey >= y_top); - - if (e->fdx == 0) { - float x0 = e->fx; - if (x0 < len) { - if (x0 >= 0) { - stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); - stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); - } else { - stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); - } - } - } else { - float x0 = e->fx; - float dx = e->fdx; - float xb = x0 + dx; - float x_top, x_bottom; - float sy0,sy1; - float dy = e->fdy; - STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); - - // compute endpoints of line segment clipped to this scanline (if the - // line segment starts on this scanline. x0 is the intersection of the - // line with y_top, but that may be off the line segment. - if (e->sy > y_top) { - x_top = x0 + dx * (e->sy - y_top); - sy0 = e->sy; - } else { - x_top = x0; - sy0 = y_top; - } - if (e->ey < y_bottom) { - x_bottom = x0 + dx * (e->ey - y_top); - sy1 = e->ey; - } else { - x_bottom = xb; - sy1 = y_bottom; - } - - if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { - // from here on, we don't have to range check x values - - if ((int) x_top == (int) x_bottom) { - float height; - // simple case, only spans one pixel - int x = (int) x_top; - height = sy1 - sy0; - STBTT_assert(x >= 0 && x < len); - scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; - scanline_fill[x] += e->direction * height; // everything right of this pixel is filled - } else { - int x,x1,x2; - float y_crossing, step, sign, area; - // covers 2+ pixels - if (x_top > x_bottom) { - // flip scanline vertically; signed area is the same - float t; - sy0 = y_bottom - (sy0 - y_top); - sy1 = y_bottom - (sy1 - y_top); - t = sy0, sy0 = sy1, sy1 = t; - t = x_bottom, x_bottom = x_top, x_top = t; - dx = -dx; - dy = -dy; - t = x0, x0 = xb, xb = t; - } - - x1 = (int) x_top; - x2 = (int) x_bottom; - // compute intersection with y axis at x1+1 - y_crossing = (x1+1 - x0) * dy + y_top; - - sign = e->direction; - // area of the rectangle covered from y0..y_crossing - area = sign * (y_crossing-sy0); - // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) - scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); - - step = sign * dy; - for (x = x1+1; x < x2; ++x) { - scanline[x] += area + step/2; - area += step; - } - y_crossing += dy * (x2 - (x1+1)); - - STBTT_assert(STBTT_fabs(area) <= 1.01f); - - scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing); - - scanline_fill[x2] += sign * (sy1-sy0); - } - } else { - // if edge goes outside of box we're drawing, we require - // clipping logic. since this does not match the intended use - // of this library, we use a different, very slow brute - // force implementation - int x; - for (x=0; x < len; ++x) { - // cases: - // - // there can be up to two intersections with the pixel. any intersection - // with left or right edges can be handled by splitting into two (or three) - // regions. intersections with top & bottom do not necessitate case-wise logic. - // - // the old way of doing this found the intersections with the left & right edges, - // then used some simple logic to produce up to three segments in sorted order - // from top-to-bottom. however, this had a problem: if an x edge was epsilon - // across the x border, then the corresponding y position might not be distinct - // from the other y segment, and it might ignored as an empty segment. to avoid - // that, we need to explicitly produce segments based on x positions. - - // rename variables to clearly-defined pairs - float y0 = y_top; - float x1 = (float) (x); - float x2 = (float) (x+1); - float x3 = xb; - float y3 = y_bottom; - - // x = e->x + e->dx * (y-y_top) - // (y-y_top) = (x - e->x) / e->dx - // y = (x - e->x) / e->dx + y_top - float y1 = (x - x0) / dx + y_top; - float y2 = (x+1 - x0) / dx + y_top; - - if (x0 < x1 && x3 > x2) { // three segments descending down-right - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); - stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); - stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); - } else if (x3 < x1 && x0 > x2) { // three segments descending down-left - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); - stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); - stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); - } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); - stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); - } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); - stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); - } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); - stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); - } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); - stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); - } else { // one segment - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); - } - } - } - } - e = e->next; - } -} - -// directly AA rasterize edges w/o supersampling -static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) -{ - stbtt__hheap hh = { 0, 0, 0 }; - stbtt__active_edge *active = NULL; - int y,j=0, i; - float scanline_data[129], *scanline, *scanline2; - - STBTT__NOTUSED(vsubsample); - - if (result->w > 64) - scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); - else - scanline = scanline_data; - - scanline2 = scanline + result->w; - - y = off_y; - e[n].y0 = (float) (off_y + result->h) + 1; - - while (j < result->h) { - // find center of pixel for this scanline - float scan_y_top = y + 0.0f; - float scan_y_bottom = y + 1.0f; - stbtt__active_edge **step = &active; - - STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); - STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); - - // update all active edges; - // remove all active edges that terminate before the top of this scanline - while (*step) { - stbtt__active_edge * z = *step; - if (z->ey <= scan_y_top) { - *step = z->next; // delete from list - STBTT_assert(z->direction); - z->direction = 0; - stbtt__hheap_free(&hh, z); - } else { - step = &((*step)->next); // advance through list - } - } - - // insert all edges that start before the bottom of this scanline - while (e->y0 <= scan_y_bottom) { - if (e->y0 != e->y1) { - stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); - if (z != NULL) { - if (j == 0 && off_y != 0) { - if (z->ey < scan_y_top) { - // this can happen due to subpixel positioning and some kind of fp rounding error i think - z->ey = scan_y_top; - } - } - STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds - // insert at front - z->next = active; - active = z; - } - } - ++e; - } - - // now process all active edges - if (active) - stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); - - { - float sum = 0; - for (i=0; i < result->w; ++i) { - float k; - int m; - sum += scanline2[i]; - k = scanline[i] + sum; - k = (float) STBTT_fabs(k)*255 + 0.5f; - m = (int) k; - if (m > 255) m = 255; - result->pixels[j*result->stride + i] = (unsigned char) m; - } - } - // advance all the edges - step = &active; - while (*step) { - stbtt__active_edge *z = *step; - z->fx += z->fdx; // advance to position for current scanline - step = &((*step)->next); // advance through list - } - - ++y; - ++j; - } - - stbtt__hheap_cleanup(&hh, userdata); - - if (scanline != scanline_data) - STBTT_free(scanline, userdata); -} -#else -#error "Unrecognized value of STBTT_RASTERIZER_VERSION" -#endif - -#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) - -static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) -{ - int i,j; - for (i=1; i < n; ++i) { - stbtt__edge t = p[i], *a = &t; - j = i; - while (j > 0) { - stbtt__edge *b = &p[j-1]; - int c = STBTT__COMPARE(a,b); - if (!c) break; - p[j] = p[j-1]; - --j; - } - if (i != j) - p[j] = t; - } -} - -static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) -{ - /* threshold for transitioning to insertion sort */ - while (n > 12) { - stbtt__edge t; - int c01,c12,c,m,i,j; - - /* compute median of three */ - m = n >> 1; - c01 = STBTT__COMPARE(&p[0],&p[m]); - c12 = STBTT__COMPARE(&p[m],&p[n-1]); - /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ - if (c01 != c12) { - /* otherwise, we'll need to swap something else to middle */ - int z; - c = STBTT__COMPARE(&p[0],&p[n-1]); - /* 0>mid && midn => n; 0 0 */ - /* 0n: 0>n => 0; 0 n */ - z = (c == c12) ? 0 : n-1; - t = p[z]; - p[z] = p[m]; - p[m] = t; - } - /* now p[m] is the median-of-three */ - /* swap it to the beginning so it won't move around */ - t = p[0]; - p[0] = p[m]; - p[m] = t; - - /* partition loop */ - i=1; - j=n-1; - for(;;) { - /* handling of equality is crucial here */ - /* for sentinels & efficiency with duplicates */ - for (;;++i) { - if (!STBTT__COMPARE(&p[i], &p[0])) break; - } - for (;;--j) { - if (!STBTT__COMPARE(&p[0], &p[j])) break; - } - /* make sure we haven't crossed */ - if (i >= j) break; - t = p[i]; - p[i] = p[j]; - p[j] = t; - - ++i; - --j; - } - /* recurse on smaller side, iterate on larger */ - if (j < (n-i)) { - stbtt__sort_edges_quicksort(p,j); - p = p+i; - n = n-i; - } else { - stbtt__sort_edges_quicksort(p+i, n-i); - n = j; - } - } -} - -static void stbtt__sort_edges(stbtt__edge *p, int n) -{ - stbtt__sort_edges_quicksort(p, n); - stbtt__sort_edges_ins_sort(p, n); -} - -typedef struct -{ - float x,y; -} stbtt__point; - -static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) -{ - float y_scale_inv = invert ? -scale_y : scale_y; - stbtt__edge *e; - int n,i,j,k,m; -#if STBTT_RASTERIZER_VERSION == 1 - int vsubsample = result->h < 8 ? 15 : 5; -#elif STBTT_RASTERIZER_VERSION == 2 - int vsubsample = 1; -#else - #error "Unrecognized value of STBTT_RASTERIZER_VERSION" -#endif - // vsubsample should divide 255 evenly; otherwise we won't reach full opacity - - // now we have to blow out the windings into explicit edge lists - n = 0; - for (i=0; i < windings; ++i) - n += wcount[i]; - - e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel - if (e == 0) return; - n = 0; - - m=0; - for (i=0; i < windings; ++i) { - stbtt__point *p = pts + m; - m += wcount[i]; - j = wcount[i]-1; - for (k=0; k < wcount[i]; j=k++) { - int a=k,b=j; - // skip the edge if horizontal - if (p[j].y == p[k].y) - continue; - // add edge from j to k to the list - e[n].invert = 0; - if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { - e[n].invert = 1; - a=j,b=k; - } - e[n].x0 = p[a].x * scale_x + shift_x; - e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; - e[n].x1 = p[b].x * scale_x + shift_x; - e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; - ++n; - } - } - - // now sort the edges by their highest point (should snap to integer, and then by x) - //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); - stbtt__sort_edges(e, n); - - // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule - stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); - - STBTT_free(e, userdata); -} - -static void stbtt__add_point(stbtt__point *points, int n, float x, float y) -{ - if (!points) return; // during first pass, it's unallocated - points[n].x = x; - points[n].y = y; -} - -// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching -static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) -{ - // midpoint - float mx = (x0 + 2*x1 + x2)/4; - float my = (y0 + 2*y1 + y2)/4; - // versus directly drawn line - float dx = (x0+x2)/2 - mx; - float dy = (y0+y2)/2 - my; - if (n > 16) // 65536 segments on one curve better be enough! - return 1; - if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA - stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); - stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); - } else { - stbtt__add_point(points, *num_points,x2,y2); - *num_points = *num_points+1; - } - return 1; -} - -static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) -{ - // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough - float dx0 = x1-x0; - float dy0 = y1-y0; - float dx1 = x2-x1; - float dy1 = y2-y1; - float dx2 = x3-x2; - float dy2 = y3-y2; - float dx = x3-x0; - float dy = y3-y0; - float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); - float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); - float flatness_squared = longlen*longlen-shortlen*shortlen; - - if (n > 16) // 65536 segments on one curve better be enough! - return; - - if (flatness_squared > objspace_flatness_squared) { - float x01 = (x0+x1)/2; - float y01 = (y0+y1)/2; - float x12 = (x1+x2)/2; - float y12 = (y1+y2)/2; - float x23 = (x2+x3)/2; - float y23 = (y2+y3)/2; - - float xa = (x01+x12)/2; - float ya = (y01+y12)/2; - float xb = (x12+x23)/2; - float yb = (y12+y23)/2; - - float mx = (xa+xb)/2; - float my = (ya+yb)/2; - - stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); - stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); - } else { - stbtt__add_point(points, *num_points,x3,y3); - *num_points = *num_points+1; - } -} - -// returns number of contours -static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) -{ - stbtt__point *points=0; - int num_points=0; - - float objspace_flatness_squared = objspace_flatness * objspace_flatness; - int i,n=0,start=0, pass; - - // count how many "moves" there are to get the contour count - for (i=0; i < num_verts; ++i) - if (vertices[i].type == STBTT_vmove) - ++n; - - *num_contours = n; - if (n == 0) return 0; - - *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); - - if (*contour_lengths == 0) { - *num_contours = 0; - return 0; - } - - // make two passes through the points so we don't need to realloc - for (pass=0; pass < 2; ++pass) { - float x=0,y=0; - if (pass == 1) { - points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); - if (points == NULL) goto error; - } - num_points = 0; - n= -1; - for (i=0; i < num_verts; ++i) { - switch (vertices[i].type) { - case STBTT_vmove: - // start the next contour - if (n >= 0) - (*contour_lengths)[n] = num_points - start; - ++n; - start = num_points; - - x = vertices[i].x, y = vertices[i].y; - stbtt__add_point(points, num_points++, x,y); - break; - case STBTT_vline: - x = vertices[i].x, y = vertices[i].y; - stbtt__add_point(points, num_points++, x, y); - break; - case STBTT_vcurve: - stbtt__tesselate_curve(points, &num_points, x,y, - vertices[i].cx, vertices[i].cy, - vertices[i].x, vertices[i].y, - objspace_flatness_squared, 0); - x = vertices[i].x, y = vertices[i].y; - break; - case STBTT_vcubic: - stbtt__tesselate_cubic(points, &num_points, x,y, - vertices[i].cx, vertices[i].cy, - vertices[i].cx1, vertices[i].cy1, - vertices[i].x, vertices[i].y, - objspace_flatness_squared, 0); - x = vertices[i].x, y = vertices[i].y; - break; - } - } - (*contour_lengths)[n] = num_points - start; - } - - return points; -error: - STBTT_free(points, userdata); - STBTT_free(*contour_lengths, userdata); - *contour_lengths = 0; - *num_contours = 0; - return NULL; -} - -STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) -{ - float scale = scale_x > scale_y ? scale_y : scale_x; - int winding_count = 0; - int *winding_lengths = NULL; - stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); - if (windings) { - stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); - STBTT_free(winding_lengths, userdata); - STBTT_free(windings, userdata); - } -} - -STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) -{ - STBTT_free(bitmap, userdata); -} - -STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) -{ - int ix0,iy0,ix1,iy1; - stbtt__bitmap gbm; - stbtt_vertex *vertices; - int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); - - if (scale_x == 0) scale_x = scale_y; - if (scale_y == 0) { - if (scale_x == 0) { - STBTT_free(vertices, info->userdata); - return NULL; - } - scale_y = scale_x; - } - - stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); - - // now we get the size - gbm.w = (ix1 - ix0); - gbm.h = (iy1 - iy0); - gbm.pixels = NULL; // in case we error - - if (width ) *width = gbm.w; - if (height) *height = gbm.h; - if (xoff ) *xoff = ix0; - if (yoff ) *yoff = iy0; - - if (gbm.w && gbm.h) { - gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); - if (gbm.pixels) { - gbm.stride = gbm.w; - - stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); - } - } - STBTT_free(vertices, info->userdata); - return gbm.pixels; -} - -STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) -{ - return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); -} - -STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) -{ - int ix0,iy0; - stbtt_vertex *vertices; - int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); - stbtt__bitmap gbm; - - stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); - gbm.pixels = output; - gbm.w = out_w; - gbm.h = out_h; - gbm.stride = out_stride; - - if (gbm.w && gbm.h) - stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); - - STBTT_free(vertices, info->userdata); -} - -STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) -{ - stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); -} - -STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) -{ - return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); -} - -STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) -{ - stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); -} - -STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) -{ - stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); -} - -STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) -{ - return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); -} - -STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) -{ - stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); -} - -////////////////////////////////////////////////////////////////////////////// -// -// bitmap baking -// -// This is SUPER-CRAPPY packing to keep source code small - -static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) - float pixel_height, // height of font in pixels - unsigned char *pixels, int pw, int ph, // bitmap to be filled in - int first_char, int num_chars, // characters to bake - stbtt_bakedchar *chardata) -{ - float scale; - int x,y,bottom_y, i; - stbtt_fontinfo f; - f.userdata = NULL; - if (!stbtt_InitFont(&f, data, offset)) - return -1; - STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels - x=y=1; - bottom_y = 1; - - scale = stbtt_ScaleForPixelHeight(&f, pixel_height); - - for (i=0; i < num_chars; ++i) { - int advance, lsb, x0,y0,x1,y1,gw,gh; - int g = stbtt_FindGlyphIndex(&f, first_char + i); - stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); - stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); - gw = x1-x0; - gh = y1-y0; - if (x + gw + 1 >= pw) - y = bottom_y, x = 1; // advance to next row - if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row - return -i; - STBTT_assert(x+gw < pw); - STBTT_assert(y+gh < ph); - stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); - chardata[i].x0 = (stbtt_int16) x; - chardata[i].y0 = (stbtt_int16) y; - chardata[i].x1 = (stbtt_int16) (x + gw); - chardata[i].y1 = (stbtt_int16) (y + gh); - chardata[i].xadvance = scale * advance; - chardata[i].xoff = (float) x0; - chardata[i].yoff = (float) y0; - x = x + gw + 1; - if (y+gh+1 > bottom_y) - bottom_y = y+gh+1; - } - return bottom_y; -} - -STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) -{ - float d3d_bias = opengl_fillrule ? 0 : -0.5f; - float ipw = 1.0f / pw, iph = 1.0f / ph; - const stbtt_bakedchar *b = chardata + char_index; - int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); - int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); - - q->x0 = round_x + d3d_bias; - q->y0 = round_y + d3d_bias; - q->x1 = round_x + b->x1 - b->x0 + d3d_bias; - q->y1 = round_y + b->y1 - b->y0 + d3d_bias; - - q->s0 = b->x0 * ipw; - q->t0 = b->y0 * iph; - q->s1 = b->x1 * ipw; - q->t1 = b->y1 * iph; - - *xpos += b->xadvance; -} - -////////////////////////////////////////////////////////////////////////////// -// -// rectangle packing replacement routines if you don't have stb_rect_pack.h -// - -#ifndef STB_RECT_PACK_VERSION - -typedef int stbrp_coord; - -//////////////////////////////////////////////////////////////////////////////////// -// // -// // -// COMPILER WARNING ?!?!? // -// // -// // -// if you get a compile warning due to these symbols being defined more than // -// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // -// // -//////////////////////////////////////////////////////////////////////////////////// - -typedef struct -{ - int width,height; - int x,y,bottom_y; -} stbrp_context; - -typedef struct -{ - unsigned char x; -} stbrp_node; - -struct stbrp_rect -{ - stbrp_coord x,y; - int id,w,h,was_packed; -}; - -static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) -{ - con->width = pw; - con->height = ph; - con->x = 0; - con->y = 0; - con->bottom_y = 0; - STBTT__NOTUSED(nodes); - STBTT__NOTUSED(num_nodes); -} - -static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) -{ - int i; - for (i=0; i < num_rects; ++i) { - if (con->x + rects[i].w > con->width) { - con->x = 0; - con->y = con->bottom_y; - } - if (con->y + rects[i].h > con->height) - break; - rects[i].x = con->x; - rects[i].y = con->y; - rects[i].was_packed = 1; - con->x += rects[i].w; - if (con->y + rects[i].h > con->bottom_y) - con->bottom_y = con->y + rects[i].h; - } - for ( ; i < num_rects; ++i) - rects[i].was_packed = 0; -} -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// bitmap baking -// -// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If -// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. - -STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) -{ - stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); - int num_nodes = pw - padding; - stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); - - if (context == NULL || nodes == NULL) { - if (context != NULL) STBTT_free(context, alloc_context); - if (nodes != NULL) STBTT_free(nodes , alloc_context); - return 0; - } - - spc->user_allocator_context = alloc_context; - spc->width = pw; - spc->height = ph; - spc->pixels = pixels; - spc->pack_info = context; - spc->nodes = nodes; - spc->padding = padding; - spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; - spc->h_oversample = 1; - spc->v_oversample = 1; - spc->skip_missing = 0; - - stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); - - if (pixels) - STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels - - return 1; -} - -STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) -{ - STBTT_free(spc->nodes , spc->user_allocator_context); - STBTT_free(spc->pack_info, spc->user_allocator_context); -} - -STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) -{ - STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); - STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); - if (h_oversample <= STBTT_MAX_OVERSAMPLE) - spc->h_oversample = h_oversample; - if (v_oversample <= STBTT_MAX_OVERSAMPLE) - spc->v_oversample = v_oversample; -} - -STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) -{ - spc->skip_missing = skip; -} - -#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) - -static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) -{ - unsigned char buffer[STBTT_MAX_OVERSAMPLE]; - int safe_w = w - kernel_width; - int j; - STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze - for (j=0; j < h; ++j) { - int i; - unsigned int total; - STBTT_memset(buffer, 0, kernel_width); - - total = 0; - - // make kernel_width a constant in common cases so compiler can optimize out the divide - switch (kernel_width) { - case 2: - for (i=0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char) (total / 2); - } - break; - case 3: - for (i=0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char) (total / 3); - } - break; - case 4: - for (i=0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char) (total / 4); - } - break; - case 5: - for (i=0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char) (total / 5); - } - break; - default: - for (i=0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char) (total / kernel_width); - } - break; - } - - for (; i < w; ++i) { - STBTT_assert(pixels[i] == 0); - total -= buffer[i & STBTT__OVER_MASK]; - pixels[i] = (unsigned char) (total / kernel_width); - } - - pixels += stride_in_bytes; - } -} - -static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) -{ - unsigned char buffer[STBTT_MAX_OVERSAMPLE]; - int safe_h = h - kernel_width; - int j; - STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze - for (j=0; j < w; ++j) { - int i; - unsigned int total; - STBTT_memset(buffer, 0, kernel_width); - - total = 0; - - // make kernel_width a constant in common cases so compiler can optimize out the divide - switch (kernel_width) { - case 2: - for (i=0; i <= safe_h; ++i) { - total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; - pixels[i*stride_in_bytes] = (unsigned char) (total / 2); - } - break; - case 3: - for (i=0; i <= safe_h; ++i) { - total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; - pixels[i*stride_in_bytes] = (unsigned char) (total / 3); - } - break; - case 4: - for (i=0; i <= safe_h; ++i) { - total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; - pixels[i*stride_in_bytes] = (unsigned char) (total / 4); - } - break; - case 5: - for (i=0; i <= safe_h; ++i) { - total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; - pixels[i*stride_in_bytes] = (unsigned char) (total / 5); - } - break; - default: - for (i=0; i <= safe_h; ++i) { - total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; - pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); - } - break; - } - - for (; i < h; ++i) { - STBTT_assert(pixels[i*stride_in_bytes] == 0); - total -= buffer[i & STBTT__OVER_MASK]; - pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); - } - - pixels += 1; - } -} - -static float stbtt__oversample_shift(int oversample) -{ - if (!oversample) - return 0.0f; - - // The prefilter is a box filter of width "oversample", - // which shifts phase by (oversample - 1)/2 pixels in - // oversampled space. We want to shift in the opposite - // direction to counter this. - return (float)-(oversample - 1) / (2.0f * (float)oversample); -} - -// rects array must be big enough to accommodate all characters in the given ranges -STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) -{ - int i,j,k; - int missing_glyph_added = 0; - - k=0; - for (i=0; i < num_ranges; ++i) { - float fh = ranges[i].font_size; - float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); - ranges[i].h_oversample = (unsigned char) spc->h_oversample; - ranges[i].v_oversample = (unsigned char) spc->v_oversample; - for (j=0; j < ranges[i].num_chars; ++j) { - int x0,y0,x1,y1; - int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; - int glyph = stbtt_FindGlyphIndex(info, codepoint); - if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { - rects[k].w = rects[k].h = 0; - } else { - stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, - scale * spc->h_oversample, - scale * spc->v_oversample, - 0,0, - &x0,&y0,&x1,&y1); - rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); - rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); - if (glyph == 0) - missing_glyph_added = 1; - } - ++k; - } - } - - return k; -} - -STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) -{ - stbtt_MakeGlyphBitmapSubpixel(info, - output, - out_w - (prefilter_x - 1), - out_h - (prefilter_y - 1), - out_stride, - scale_x, - scale_y, - shift_x, - shift_y, - glyph); - - if (prefilter_x > 1) - stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); - - if (prefilter_y > 1) - stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); - - *sub_x = stbtt__oversample_shift(prefilter_x); - *sub_y = stbtt__oversample_shift(prefilter_y); -} - -// rects array must be big enough to accommodate all characters in the given ranges -STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) -{ - int i,j,k, missing_glyph = -1, return_value = 1; - - // save current values - int old_h_over = spc->h_oversample; - int old_v_over = spc->v_oversample; - - k = 0; - for (i=0; i < num_ranges; ++i) { - float fh = ranges[i].font_size; - float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); - float recip_h,recip_v,sub_x,sub_y; - spc->h_oversample = ranges[i].h_oversample; - spc->v_oversample = ranges[i].v_oversample; - recip_h = 1.0f / spc->h_oversample; - recip_v = 1.0f / spc->v_oversample; - sub_x = stbtt__oversample_shift(spc->h_oversample); - sub_y = stbtt__oversample_shift(spc->v_oversample); - for (j=0; j < ranges[i].num_chars; ++j) { - stbrp_rect *r = &rects[k]; - if (r->was_packed && r->w != 0 && r->h != 0) { - stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; - int advance, lsb, x0,y0,x1,y1; - int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; - int glyph = stbtt_FindGlyphIndex(info, codepoint); - stbrp_coord pad = (stbrp_coord) spc->padding; - - // pad on left and top - r->x += pad; - r->y += pad; - r->w -= pad; - r->h -= pad; - stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); - stbtt_GetGlyphBitmapBox(info, glyph, - scale * spc->h_oversample, - scale * spc->v_oversample, - &x0,&y0,&x1,&y1); - stbtt_MakeGlyphBitmapSubpixel(info, - spc->pixels + r->x + r->y*spc->stride_in_bytes, - r->w - spc->h_oversample+1, - r->h - spc->v_oversample+1, - spc->stride_in_bytes, - scale * spc->h_oversample, - scale * spc->v_oversample, - 0,0, - glyph); - - if (spc->h_oversample > 1) - stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, - r->w, r->h, spc->stride_in_bytes, - spc->h_oversample); - - if (spc->v_oversample > 1) - stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, - r->w, r->h, spc->stride_in_bytes, - spc->v_oversample); - - bc->x0 = (stbtt_int16) r->x; - bc->y0 = (stbtt_int16) r->y; - bc->x1 = (stbtt_int16) (r->x + r->w); - bc->y1 = (stbtt_int16) (r->y + r->h); - bc->xadvance = scale * advance; - bc->xoff = (float) x0 * recip_h + sub_x; - bc->yoff = (float) y0 * recip_v + sub_y; - bc->xoff2 = (x0 + r->w) * recip_h + sub_x; - bc->yoff2 = (y0 + r->h) * recip_v + sub_y; - - if (glyph == 0) - missing_glyph = j; - } else if (spc->skip_missing) { - return_value = 0; - } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) { - ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; - } else { - return_value = 0; // if any fail, report failure - } - - ++k; - } - } - - // restore original values - spc->h_oversample = old_h_over; - spc->v_oversample = old_v_over; - - return return_value; -} - -STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) -{ - stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); -} - -STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) -{ - stbtt_fontinfo info; - int i,j,n, return_value = 1; - //stbrp_context *context = (stbrp_context *) spc->pack_info; - stbrp_rect *rects; - - // flag all characters as NOT packed - for (i=0; i < num_ranges; ++i) - for (j=0; j < ranges[i].num_chars; ++j) - ranges[i].chardata_for_range[j].x0 = - ranges[i].chardata_for_range[j].y0 = - ranges[i].chardata_for_range[j].x1 = - ranges[i].chardata_for_range[j].y1 = 0; - - n = 0; - for (i=0; i < num_ranges; ++i) - n += ranges[i].num_chars; - - rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); - if (rects == NULL) - return 0; - - info.userdata = spc->user_allocator_context; - stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); - - n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); - - stbtt_PackFontRangesPackRects(spc, rects, n); - - return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); - - STBTT_free(rects, spc->user_allocator_context); - return return_value; -} - -STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, - int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) -{ - stbtt_pack_range range; - range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; - range.array_of_unicode_codepoints = NULL; - range.num_chars = num_chars_in_range; - range.chardata_for_range = chardata_for_range; - range.font_size = font_size; - return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); -} - -STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) -{ - int i_ascent, i_descent, i_lineGap; - float scale; - stbtt_fontinfo info; - stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); - scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); - stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); - *ascent = (float) i_ascent * scale; - *descent = (float) i_descent * scale; - *lineGap = (float) i_lineGap * scale; -} - -STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) -{ - float ipw = 1.0f / pw, iph = 1.0f / ph; - const stbtt_packedchar *b = chardata + char_index; - - if (align_to_integer) { - float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); - float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); - q->x0 = x; - q->y0 = y; - q->x1 = x + b->xoff2 - b->xoff; - q->y1 = y + b->yoff2 - b->yoff; - } else { - q->x0 = *xpos + b->xoff; - q->y0 = *ypos + b->yoff; - q->x1 = *xpos + b->xoff2; - q->y1 = *ypos + b->yoff2; - } - - q->s0 = b->x0 * ipw; - q->t0 = b->y0 * iph; - q->s1 = b->x1 * ipw; - q->t1 = b->y1 * iph; - - *xpos += b->xadvance; -} - -////////////////////////////////////////////////////////////////////////////// -// -// sdf computation -// - -#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) -#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) - -static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) -{ - float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; - float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; - float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; - float roperp = orig[1]*ray[0] - orig[0]*ray[1]; - - float a = q0perp - 2*q1perp + q2perp; - float b = q1perp - q0perp; - float c = q0perp - roperp; - - float s0 = 0., s1 = 0.; - int num_s = 0; - - if (a != 0.0) { - float discr = b*b - a*c; - if (discr > 0.0) { - float rcpna = -1 / a; - float d = (float) STBTT_sqrt(discr); - s0 = (b+d) * rcpna; - s1 = (b-d) * rcpna; - if (s0 >= 0.0 && s0 <= 1.0) - num_s = 1; - if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { - if (num_s == 0) s0 = s1; - ++num_s; - } - } - } else { - // 2*b*s + c = 0 - // s = -c / (2*b) - s0 = c / (-2 * b); - if (s0 >= 0.0 && s0 <= 1.0) - num_s = 1; - } - - if (num_s == 0) - return 0; - else { - float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); - float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; - - float q0d = q0[0]*rayn_x + q0[1]*rayn_y; - float q1d = q1[0]*rayn_x + q1[1]*rayn_y; - float q2d = q2[0]*rayn_x + q2[1]*rayn_y; - float rod = orig[0]*rayn_x + orig[1]*rayn_y; - - float q10d = q1d - q0d; - float q20d = q2d - q0d; - float q0rd = q0d - rod; - - hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; - hits[0][1] = a*s0+b; - - if (num_s > 1) { - hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; - hits[1][1] = a*s1+b; - return 2; - } else { - return 1; - } - } -} - -static int equal(float *a, float *b) -{ - return (a[0] == b[0] && a[1] == b[1]); -} - -static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) -{ - int i; - float orig[2], ray[2] = { 1, 0 }; - float y_frac; - int winding = 0; - - orig[0] = x; - orig[1] = y; - - // make sure y never passes through a vertex of the shape - y_frac = (float) STBTT_fmod(y, 1.0f); - if (y_frac < 0.01f) - y += 0.01f; - else if (y_frac > 0.99f) - y -= 0.01f; - orig[1] = y; - - // test a ray from (-infinity,y) to (x,y) - for (i=0; i < nverts; ++i) { - if (verts[i].type == STBTT_vline) { - int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; - int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; - if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { - float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; - if (x_inter < x) - winding += (y0 < y1) ? 1 : -1; - } - } - if (verts[i].type == STBTT_vcurve) { - int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; - int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; - int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; - int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); - int by = STBTT_max(y0,STBTT_max(y1,y2)); - if (y > ay && y < by && x > ax) { - float q0[2],q1[2],q2[2]; - float hits[2][2]; - q0[0] = (float)x0; - q0[1] = (float)y0; - q1[0] = (float)x1; - q1[1] = (float)y1; - q2[0] = (float)x2; - q2[1] = (float)y2; - if (equal(q0,q1) || equal(q1,q2)) { - x0 = (int)verts[i-1].x; - y0 = (int)verts[i-1].y; - x1 = (int)verts[i ].x; - y1 = (int)verts[i ].y; - if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { - float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; - if (x_inter < x) - winding += (y0 < y1) ? 1 : -1; - } - } else { - int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); - if (num_hits >= 1) - if (hits[0][0] < 0) - winding += (hits[0][1] < 0 ? -1 : 1); - if (num_hits >= 2) - if (hits[1][0] < 0) - winding += (hits[1][1] < 0 ? -1 : 1); - } - } - } - } - return winding; -} - -static float stbtt__cuberoot( float x ) -{ - if (x<0) - return -(float) STBTT_pow(-x,1.0f/3.0f); - else - return (float) STBTT_pow( x,1.0f/3.0f); -} - -// x^3 + c*x^2 + b*x + a = 0 -static int stbtt__solve_cubic(float a, float b, float c, float* r) -{ - float s = -a / 3; - float p = b - a*a / 3; - float q = a * (2*a*a - 9*b) / 27 + c; - float p3 = p*p*p; - float d = q*q + 4*p3 / 27; - if (d >= 0) { - float z = (float) STBTT_sqrt(d); - float u = (-q + z) / 2; - float v = (-q - z) / 2; - u = stbtt__cuberoot(u); - v = stbtt__cuberoot(v); - r[0] = s + u + v; - return 1; - } else { - float u = (float) STBTT_sqrt(-p/3); - float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative - float m = (float) STBTT_cos(v); - float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; - r[0] = s + u * 2 * m; - r[1] = s - u * (m + n); - r[2] = s - u * (m - n); - - //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? - //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); - //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); - return 3; - } -} - -STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) -{ - float scale_x = scale, scale_y = scale; - int ix0,iy0,ix1,iy1; - int w,h; - unsigned char *data; - - if (scale == 0) return NULL; - - stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); - - // if empty, return NULL - if (ix0 == ix1 || iy0 == iy1) - return NULL; - - ix0 -= padding; - iy0 -= padding; - ix1 += padding; - iy1 += padding; - - w = (ix1 - ix0); - h = (iy1 - iy0); - - if (width ) *width = w; - if (height) *height = h; - if (xoff ) *xoff = ix0; - if (yoff ) *yoff = iy0; - - // invert for y-downwards bitmaps - scale_y = -scale_y; - - { - int x,y,i,j; - float *precompute; - stbtt_vertex *verts; - int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); - data = (unsigned char *) STBTT_malloc(w * h, info->userdata); - precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); - - for (i=0,j=num_verts-1; i < num_verts; j=i++) { - if (verts[i].type == STBTT_vline) { - float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; - float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; - float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); - precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; - } else if (verts[i].type == STBTT_vcurve) { - float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; - float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; - float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; - float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; - float len2 = bx*bx + by*by; - if (len2 != 0.0f) - precompute[i] = 1.0f / (bx*bx + by*by); - else - precompute[i] = 0.0f; - } else - precompute[i] = 0.0f; - } - - for (y=iy0; y < iy1; ++y) { - for (x=ix0; x < ix1; ++x) { - float val; - float min_dist = 999999.0f; - float sx = (float) x + 0.5f; - float sy = (float) y + 0.5f; - float x_gspace = (sx / scale_x); - float y_gspace = (sy / scale_y); - - int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path - - for (i=0; i < num_verts; ++i) { - float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; - - // check against every point here rather than inside line/curve primitives -- @TODO: wrong if multiple 'moves' in a row produce a garbage point, and given culling, probably more efficient to do within line/curve - float dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); - if (dist2 < min_dist*min_dist) - min_dist = (float) STBTT_sqrt(dist2); - - if (verts[i].type == STBTT_vline) { - float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; - - // coarse culling against bbox - //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && - // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) - float dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; - STBTT_assert(i != 0); - if (dist < min_dist) { - // check position along line - // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) - // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) - float dx = x1-x0, dy = y1-y0; - float px = x0-sx, py = y0-sy; - // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy - // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve - float t = -(px*dx + py*dy) / (dx*dx + dy*dy); - if (t >= 0.0f && t <= 1.0f) - min_dist = dist; - } - } else if (verts[i].type == STBTT_vcurve) { - float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; - float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; - float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); - float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); - float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); - float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); - // coarse culling against bbox to avoid computing cubic unnecessarily - if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { - int num=0; - float ax = x1-x0, ay = y1-y0; - float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; - float mx = x0 - sx, my = y0 - sy; - float res[3],px,py,t,it; - float a_inv = precompute[i]; - if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula - float a = 3*(ax*bx + ay*by); - float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); - float c = mx*ax+my*ay; - if (a == 0.0) { // if a is 0, it's linear - if (b != 0.0) { - res[num++] = -c/b; - } - } else { - float discriminant = b*b - 4*a*c; - if (discriminant < 0) - num = 0; - else { - float root = (float) STBTT_sqrt(discriminant); - res[0] = (-b - root)/(2*a); - res[1] = (-b + root)/(2*a); - num = 2; // don't bother distinguishing 1-solution case, as code below will still work - } - } - } else { - float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point - float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; - float d = (mx*ax+my*ay) * a_inv; - num = stbtt__solve_cubic(b, c, d, res); - } - if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { - t = res[0], it = 1.0f - t; - px = it*it*x0 + 2*t*it*x1 + t*t*x2; - py = it*it*y0 + 2*t*it*y1 + t*t*y2; - dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); - if (dist2 < min_dist * min_dist) - min_dist = (float) STBTT_sqrt(dist2); - } - if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { - t = res[1], it = 1.0f - t; - px = it*it*x0 + 2*t*it*x1 + t*t*x2; - py = it*it*y0 + 2*t*it*y1 + t*t*y2; - dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); - if (dist2 < min_dist * min_dist) - min_dist = (float) STBTT_sqrt(dist2); - } - if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { - t = res[2], it = 1.0f - t; - px = it*it*x0 + 2*t*it*x1 + t*t*x2; - py = it*it*y0 + 2*t*it*y1 + t*t*y2; - dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); - if (dist2 < min_dist * min_dist) - min_dist = (float) STBTT_sqrt(dist2); - } - } - } - } - if (winding == 0) - min_dist = -min_dist; // if outside the shape, value is negative - val = onedge_value + pixel_dist_scale * min_dist; - if (val < 0) - val = 0; - else if (val > 255) - val = 255; - data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; - } - } - STBTT_free(precompute, info->userdata); - STBTT_free(verts, info->userdata); - } - return data; -} - -STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) -{ - return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); -} - -STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) -{ - STBTT_free(bitmap, userdata); -} - -////////////////////////////////////////////////////////////////////////////// -// -// font name matching -- recommended not to use this -// - -// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string -static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) -{ - stbtt_int32 i=0; - - // convert utf16 to utf8 and compare the results while converting - while (len2) { - stbtt_uint16 ch = s2[0]*256 + s2[1]; - if (ch < 0x80) { - if (i >= len1) return -1; - if (s1[i++] != ch) return -1; - } else if (ch < 0x800) { - if (i+1 >= len1) return -1; - if (s1[i++] != 0xc0 + (ch >> 6)) return -1; - if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; - } else if (ch >= 0xd800 && ch < 0xdc00) { - stbtt_uint32 c; - stbtt_uint16 ch2 = s2[2]*256 + s2[3]; - if (i+3 >= len1) return -1; - c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; - if (s1[i++] != 0xf0 + (c >> 18)) return -1; - if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; - if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; - if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; - s2 += 2; // plus another 2 below - len2 -= 2; - } else if (ch >= 0xdc00 && ch < 0xe000) { - return -1; - } else { - if (i+2 >= len1) return -1; - if (s1[i++] != 0xe0 + (ch >> 12)) return -1; - if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; - if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; - } - s2 += 2; - len2 -= 2; - } - return i; -} - -static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) -{ - return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); -} - -// returns results in whatever encoding you request... but note that 2-byte encodings -// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare -STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) -{ - stbtt_int32 i,count,stringOffset; - stbtt_uint8 *fc = font->data; - stbtt_uint32 offset = font->fontstart; - stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); - if (!nm) return NULL; - - count = ttUSHORT(fc+nm+2); - stringOffset = nm + ttUSHORT(fc+nm+4); - for (i=0; i < count; ++i) { - stbtt_uint32 loc = nm + 6 + 12 * i; - if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) - && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { - *length = ttUSHORT(fc+loc+8); - return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); - } - } - return NULL; -} - -static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) -{ - stbtt_int32 i; - stbtt_int32 count = ttUSHORT(fc+nm+2); - stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); - - for (i=0; i < count; ++i) { - stbtt_uint32 loc = nm + 6 + 12 * i; - stbtt_int32 id = ttUSHORT(fc+loc+6); - if (id == target_id) { - // find the encoding - stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); - - // is this a Unicode encoding? - if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { - stbtt_int32 slen = ttUSHORT(fc+loc+8); - stbtt_int32 off = ttUSHORT(fc+loc+10); - - // check if there's a prefix match - stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); - if (matchlen >= 0) { - // check for target_id+1 immediately following, with same encoding & language - if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { - slen = ttUSHORT(fc+loc+12+8); - off = ttUSHORT(fc+loc+12+10); - if (slen == 0) { - if (matchlen == nlen) - return 1; - } else if (matchlen < nlen && name[matchlen] == ' ') { - ++matchlen; - if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) - return 1; - } - } else { - // if nothing immediately following - if (matchlen == nlen) - return 1; - } - } - } - - // @TODO handle other encodings - } - } - return 0; -} - -static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) -{ - stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); - stbtt_uint32 nm,hd; - if (!stbtt__isfont(fc+offset)) return 0; - - // check italics/bold/underline flags in macStyle... - if (flags) { - hd = stbtt__find_table(fc, offset, "head"); - if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; - } - - nm = stbtt__find_table(fc, offset, "name"); - if (!nm) return 0; - - if (flags) { - // if we checked the macStyle flags, then just check the family and ignore the subfamily - if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; - } else { - if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; - } - - return 0; -} - -static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) -{ - stbtt_int32 i; - for (i=0;;++i) { - stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); - if (off < 0) return off; - if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) - return off; - } -} - -#if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-qual" -#endif - -STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, - float pixel_height, unsigned char *pixels, int pw, int ph, - int first_char, int num_chars, stbtt_bakedchar *chardata) -{ - return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); -} - -STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) -{ - return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); -} - -STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) -{ - return stbtt_GetNumberOfFonts_internal((unsigned char *) data); -} - -STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) -{ - return stbtt_InitFont_internal(info, (unsigned char *) data, offset); -} - -STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) -{ - return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); -} - -STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) -{ - return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); -} - -#if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic pop -#endif - -#endif // STB_TRUETYPE_IMPLEMENTATION - - -// FULL VERSION HISTORY -// -// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod -// 1.18 (2018-01-29) add missing function -// 1.17 (2017-07-23) make more arguments const; doc fix -// 1.16 (2017-07-12) SDF support -// 1.15 (2017-03-03) make more arguments const -// 1.14 (2017-01-16) num-fonts-in-TTC function -// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts -// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual -// 1.11 (2016-04-02) fix unused-variable warning -// 1.10 (2016-04-02) allow user-defined fabs() replacement -// fix memory leak if fontsize=0.0 -// fix warning from duplicate typedef -// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges -// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges -// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; -// allow PackFontRanges to pack and render in separate phases; -// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); -// fixed an assert() bug in the new rasterizer -// replace assert() with STBTT_assert() in new rasterizer -// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) -// also more precise AA rasterizer, except if shapes overlap -// remove need for STBTT_sort -// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC -// 1.04 (2015-04-15) typo in example -// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes -// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ -// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match -// non-oversampled; STBTT_POINT_SIZE for packed case only -// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling -// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) -// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID -// 0.8b (2014-07-07) fix a warning -// 0.8 (2014-05-25) fix a few more warnings -// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back -// 0.6c (2012-07-24) improve documentation -// 0.6b (2012-07-20) fix a few more warnings -// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, -// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty -// 0.5 (2011-12-09) bugfixes: -// subpixel glyph renderer computed wrong bounding box -// first vertex of shape can be off-curve (FreeSans) -// 0.4b (2011-12-03) fixed an error in the font baking example -// 0.4 (2011-12-01) kerning, subpixel rendering (tor) -// bugfixes for: -// codepoint-to-glyph conversion using table fmt=12 -// codepoint-to-glyph conversion using table fmt=4 -// stbtt_GetBakedQuad with non-square texture (Zer) -// updated Hello World! sample to use kerning and subpixel -// fixed some warnings -// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) -// userdata, malloc-from-userdata, non-zero fill (stb) -// 0.2 (2009-03-11) Fix unsigned/signed char warnings -// 0.1 (2009-03-09) First public release -// - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/tools/lib/lazy_property.py b/tools/lib/lazy_property.py deleted file mode 100644 index 85c038f28..000000000 --- a/tools/lib/lazy_property.py +++ /dev/null @@ -1,12 +0,0 @@ -class lazy_property(object): - """Defines a property whose value will be computed only once and as needed. - - This can only be used on instance methods. - """ - def __init__(self, func): - self._func = func - - def __get__(self, obj_self, cls): - value = self._func(obj_self) - setattr(obj_self, self._func.__name__, value) - return value diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index 01c666fd1..1c9cc81e3 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -13,7 +13,7 @@ from cereal import log as capnp_log # this is an iterator itself, and uses private variables from LogReader class MultiLogIterator(object): - def __init__(self, log_paths, wraparound=True): + def __init__(self, log_paths, wraparound=False): self._log_paths = log_paths self._wraparound = wraparound @@ -26,7 +26,6 @@ class MultiLogIterator(object): def _log_reader(self, i): if self._log_readers[i] is None and self._log_paths[i] is not None: log_path = self._log_paths[i] - print("LogReader:%s" % log_path) self._log_readers[i] = LogReader(log_path) return self._log_readers[i]