From 56e6df90d620a96238290e5940e2aff342e3a034 Mon Sep 17 00:00:00 2001 From: cappu Date: Mon, 19 Oct 2026 09:15:10 -0400 Subject: [PATCH] Reduce per-frame allocations and release size --- driver/CMakeLists.txt | 6 ++++-- runtime/CMakeLists.txt | 1 + runtime/src/model/babble_eye.cpp | 4 ++-- runtime/src/model/babble_mono_face.cpp | 10 ++++++---- runtime/src/model/fusionnet_mouth.cpp | 15 +++++++-------- runtime/src/model/fusionnet_mouth.h | 2 +- runtime/src/model/image_ops.cpp | 12 ++++++++---- runtime/src/sink/link_sink.cpp | 6 ++++-- runtime/src/sink/osc.cpp | 9 +++++---- runtime/src/sink/osc_sink.cpp | 3 ++- runtime/src/sink/vrc_native_sink.cpp | 8 +++++--- runtime/src/state/expression_state.cpp | 10 +++++++++- runtime/src/state/expression_state.h | 1 + runtime/src/ui/canvas.cpp | 13 ++++++++++--- 14 files changed, 65 insertions(+), 35 deletions(-) diff --git a/driver/CMakeLists.txt b/driver/CMakeLists.txt index 5757783..73b0fff 100644 --- a/driver/CMakeLists.txt +++ b/driver/CMakeLists.txt @@ -19,7 +19,8 @@ target_include_directories(driver_babble SYSTEM PRIVATE target_include_directories(driver_babble PRIVATE ${BABBLE_GENERATED_DIR}) # This directory has babble_version.h. add_dependencies(driver_babble babble_version) target_compile_options(driver_babble PRIVATE -Wall -Wextra) -target_link_options(driver_babble PRIVATE -Wl,--no-undefined -static-libstdc++ -static-libgcc) +target_link_options(driver_babble PRIVATE -Wl,--no-undefined -static-libstdc++ -static-libgcc + $<$:-Wl,--strip-all>) target_link_libraries(driver_babble PRIVATE dl pthread) set_target_properties(driver_babble PROPERTIES PREFIX "") @@ -28,7 +29,8 @@ add_executable(babble_setup src/babble_setup.cpp) target_include_directories(babble_setup SYSTEM PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/nlohmann_json/single_include) target_compile_options(babble_setup PRIVATE -Wall -Wextra) -target_link_options(babble_setup PRIVATE -static-libstdc++ -static-libgcc) +target_link_options(babble_setup PRIVATE -static-libstdc++ -static-libgcc + $<$:-Wl,--strip-all>) set_target_properties(babble_setup PROPERTIES OUTPUT_NAME babble-setup) # Put all the parts of the SteamVR driver folder in build/babble. diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index d36de16..8a012d6 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -128,6 +128,7 @@ target_link_libraries(babble_runtime PRIVATE babble_core) set_target_properties(babble_runtime PROPERTIES OUTPUT_NAME babble-runtime BUILD_RPATH "${THIRD_PARTY}/openvr/lib/linuxarm64" INSTALL_RPATH "$ORIGIN") target_link_options(babble_runtime PRIVATE "-Wl,-rpath,$ORIGIN" + $<$:-Wl,--strip-all> -rdynamic) # This option puts function names in crash backtraces. if(NOT BABBLE_ASAN) # Link libstdc++ statically because the binary must not use the libstdc++ of the OS. diff --git a/runtime/src/model/babble_eye.cpp b/runtime/src/model/babble_eye.cpp index a633b01..d55ab28 100644 --- a/runtime/src/model/babble_eye.cpp +++ b/runtime/src/model/babble_eye.cpp @@ -218,8 +218,8 @@ bool BabbleEye::run_model(const float *x, float *y) { half_out_.resize(os.elements()); out = half_out_.data(); } - std::vector iv{{const_cast(in), &is}}; - std::vector ov{{out, &os}}; + TensorView iv[] = {{const_cast(in), &is}}; + TensorView ov[] = {{out, &os}}; if (!model_->run(iv, ov)) return false; if (os.dtype == DType::F16) diff --git a/runtime/src/model/babble_mono_face.cpp b/runtime/src/model/babble_mono_face.cpp index d19b00b..b385d9c 100644 --- a/runtime/src/model/babble_mono_face.cpp +++ b/runtime/src/model/babble_mono_face.cpp @@ -73,8 +73,6 @@ bool BabbleMonoFace::init() { for (size_t i = 0; i < face::kShapes.size(); i++) ids_[i] = deps_.state.add_signal(face::kShapes[i]); - tmp_a_.resize(kMaxSide * kMaxSide); - tmp_b_.resize(kMaxSide * kMaxSide); small_.resize(kSize * kSize); input_.resize(kSize * kSize); out_.resize(face::kShapes.size()); @@ -96,6 +94,10 @@ void BabbleMonoFace::prepare(const Frame &f, float *input) { const uint32_t w = std::min(cur.width, kMaxSide), h = std::min(cur.height, kMaxSide); cur.width = w; cur.height = h; + if ((gain_ != 1.0f || flip_h_ || flip_v_ || quarter_turns_) && tmp_a_.size() < size_t(w) * h) { + tmp_a_.resize(size_t(w) * h); + tmp_b_.resize(size_t(w) * h); + } if (gain_ != 1.0f) { // The gain saturates and rounds as the OpenCV convertTo does. for (uint32_t y = 0; y < h; y++) @@ -160,8 +162,8 @@ void BabbleMonoFace::process(const StereoFrame &sf) { half_out_.resize(os.elements()); out = half_out_.data(); } - std::vector iv{{in, &is}}; - std::vector ov{{out, &os}}; + TensorView iv[] = {{in, &is}}; + TensorView ov[] = {{out, &os}}; if (!runner_->run(iv, ov)) return; if (os.dtype == DType::F16) diff --git a/runtime/src/model/fusionnet_mouth.cpp b/runtime/src/model/fusionnet_mouth.cpp index 1e18dfb..1f27edf 100644 --- a/runtime/src/model/fusionnet_mouth.cpp +++ b/runtime/src/model/fusionnet_mouth.cpp @@ -79,7 +79,6 @@ bool FusionNetMouth::init() { tmp_a_.resize(400 * 400); tmp_b_.resize(400 * 400); small_.resize(kSize * kSize); - eq_.resize(kSize * kSize); in0_.resize(kSize * kSize); in1_.resize(kSize * kSize); probe_left_ = probe_total_ = cfg_.value("probe_orientation", 0); @@ -123,9 +122,9 @@ void FusionNetMouth::prepare(const StereoFrame &sf, float *view0, float *view1) img::resize_area(cur, small); else img::resize_linear(cur, small); - img::equalize_hist(small, {eq_.data(), kSize, kSize, kSize}); - for (size_t i = 0; i < eq_.size(); i++) - outs[v][i] = float(eq_[i]) / 255.0f; + img::equalize_hist(small, small); + for (size_t i = 0; i < small_.size(); i++) + outs[v][i] = float(small_[i]) / 255.0f; } } @@ -140,9 +139,9 @@ void FusionNetMouth::probe(const StereoFrame &sf) { const int saved_turns = quarter_turns_; auto ri = runner_->inputs(); auto ro = runner_->outputs(); - std::vector iv{{in0_.data(), &ri[0]}, {in1_.data(), &ri[1]}}; + TensorView iv[] = {{in0_.data(), &ri[0]}, {in1_.data(), &ri[1]}}; std::array out{}; - std::vector ov{{out.data(), &ro[0]}}; + TensorView ov[] = {{out.data(), &ro[0]}}; for (int c = 0; c < 8; c++) { swap_ = c >= 4; quarter_turns_ = c % 4; @@ -183,8 +182,8 @@ void FusionNetMouth::process(const StereoFrame &sf) { auto ri = runner_->inputs(); auto ro = runner_->outputs(); - std::vector iv{{in0_.data(), &ri[0]}, {in1_.data(), &ri[1]}}; - std::vector ov{{out_.data(), &ro[0]}}; + TensorView iv[] = {{in0_.data(), &ri[0]}, {in1_.data(), &ri[1]}}; + TensorView ov[] = {{out_.data(), &ro[0]}}; if (!runner_->run(iv, ov)) return; uint64_t t2 = mono_ns(); diff --git a/runtime/src/model/fusionnet_mouth.h b/runtime/src/model/fusionnet_mouth.h index b933b4f..8767bc8 100644 --- a/runtime/src/model/fusionnet_mouth.h +++ b/runtime/src/model/fusionnet_mouth.h @@ -31,7 +31,7 @@ class FusionNetMouth : public PipelineBase { std::unique_ptr post_; std::array ids_{}; - std::vector tmp_a_, tmp_b_, small_, eq_; + std::vector tmp_a_, tmp_b_, small_; std::vector in0_, in1_; std::array out_{}; std::atomic reset_pending_{false}; diff --git a/runtime/src/model/image_ops.cpp b/runtime/src/model/image_ops.cpp index fb0aa5b..e953905 100644 --- a/runtime/src/model/image_ops.cpp +++ b/runtime/src/model/image_ops.cpp @@ -130,11 +130,15 @@ void resize_area(View src, Mut dst) { std::vector buf(dw), sum(dw, 0.f); int prev_dy = ytab.empty() ? 0 : ytab[0].di; + int prev_sy = -1; for (const AreaTab &yt : ytab) { - const uint8_t *S = src.data + size_t(yt.si) * src.stride; - std::fill(buf.begin(), buf.end(), 0.f); - for (const AreaTab &xt : xtab) - buf[xt.di] += float(S[xt.si]) * xt.alpha; + if (yt.si != prev_sy) { + const uint8_t *S = src.data + size_t(yt.si) * src.stride; + std::fill(buf.begin(), buf.end(), 0.f); + for (const AreaTab &xt : xtab) + buf[xt.di] += float(S[xt.si]) * xt.alpha; + prev_sy = yt.si; + } if (yt.di != prev_dy) { uint8_t *D = dst.data + size_t(prev_dy) * dst.stride; for (int x = 0; x < dw; x++) { diff --git a/runtime/src/sink/link_sink.cpp b/runtime/src/sink/link_sink.cpp index d1b40c3..cb49a0c 100644 --- a/runtime/src/sink/link_sink.cpp +++ b/runtime/src/sink/link_sink.cpp @@ -616,6 +616,8 @@ void LinkSink::choose(uint64_t now) { void LinkSink::run() { const uint64_t scan_ns = 5'000'000'000; + std::vector fds; + std::vector owners; while (!stop_) { uint64_t now = mono_ns(); if (now >= next_scan_ns_) { @@ -628,8 +630,8 @@ void LinkSink::run() { ping(now); choose(now); - std::vector fds; - std::vector owners; + fds.clear(); + owners.clear(); if (listen_sock_ >= 0) { fds.push_back({listen_sock_, POLLIN, 0}); owners.push_back(nullptr); diff --git a/runtime/src/sink/osc.cpp b/runtime/src/sink/osc.cpp index fabba82..04db0c6 100644 --- a/runtime/src/sink/osc.cpp +++ b/runtime/src/sink/osc.cpp @@ -15,9 +15,9 @@ size_t encode_float(std::string_view address, float value, uint8_t *out, size_t const size_t total = addr_len + 4 + 4; if (total > cap) return 0; - memset(out, 0, total); memcpy(out, address.data(), address.size()); - memcpy(out + addr_len, ",f", 2); + memset(out + address.size(), 0, addr_len - address.size()); + memcpy(out + addr_len, ",f\0", 4); uint32_t bits; memcpy(&bits, &value, 4); bits = __builtin_bswap32(bits); @@ -42,13 +42,14 @@ size_t encode_blob(std::string_view address, const uint8_t *data, size_t size, u const size_t total = addr_len + 4 + 4 + data_len; if (total > cap || size > 0x7fffffff) return 0; - memset(out, 0, total); memcpy(out, address.data(), address.size()); - memcpy(out + addr_len, ",b", 2); + memset(out + address.size(), 0, addr_len - address.size()); + memcpy(out + addr_len, ",b\0", 4); const uint32_t n = __builtin_bswap32(uint32_t(size)); memcpy(out + addr_len + 4, &n, 4); if (size) memcpy(out + addr_len + 8, data, size); + memset(out + addr_len + 8 + size, 0, data_len - size); return total; } diff --git a/runtime/src/sink/osc_sink.cpp b/runtime/src/sink/osc_sink.cpp index cce49bb..89dd307 100644 --- a/runtime/src/sink/osc_sink.cpp +++ b/runtime/src/sink/osc_sink.cpp @@ -159,6 +159,7 @@ void OscSink::run() { uint64_t seq = 0, last_batch_ns = 0, generation = 0; std::vector last_sent_t; // The t_ns of the last value that the sink sent for each signal. std::vector addresses; // The OSC address for each signal, or "" if the signal is excluded. + ExpressionState::Snapshot snap; while (!stop_) { if (test_pattern_) { @@ -183,7 +184,7 @@ void OscSink::run() { generation = g; } - auto snap = state_.snapshot(); + state_.snapshot(snap); if (addresses.size() != snap.names.size()) { // Make the address table again if the number of signals changes. addresses.clear(); diff --git a/runtime/src/sink/vrc_native_sink.cpp b/runtime/src/sink/vrc_native_sink.cpp index 38f705c..a288bda 100644 --- a/runtime/src/sink/vrc_native_sink.cpp +++ b/runtime/src/sink/vrc_native_sink.cpp @@ -304,8 +304,9 @@ std::optional VrcNativeSink::live_pc() { std::ifstream in(log_path_, std::ios::binary); std::stringstream text; text << in.rdbuf(); - log_pc_ = vrc_native::streaming_pc(text.str()); - log_pid_ = vrc_native::session_pid(text.str()); + const std::string contents = text.str(); + log_pc_ = vrc_native::streaming_pc(contents); + log_pid_ = vrc_native::session_pid(contents); } // The session is live if its vrlink process still runs. if (!log_pc_ || !log_pid_) @@ -384,6 +385,7 @@ void VrcNativeSink::run() { uint64_t seq = 0, last_send_ns = 0; std::vector targets; std::vector> values; + ExpressionState::Snapshot snap; while (!stop_) { seq = state_.wait_update(seq, std::chrono::milliseconds(200)); @@ -396,7 +398,7 @@ void VrcNativeSink::run() { continue; last_send_ns = now; - const auto snap = state_.snapshot(); + state_.snapshot(snap); if (targets.size() != snap.names.size()) { targets.clear(); for (const auto &nm : snap.names) diff --git a/runtime/src/state/expression_state.cpp b/runtime/src/state/expression_state.cpp index 581866a..b32289d 100644 --- a/runtime/src/state/expression_state.cpp +++ b/runtime/src/state/expression_state.cpp @@ -41,8 +41,16 @@ std::string ExpressionState::name(SignalId id) const { } ExpressionState::Snapshot ExpressionState::snapshot() const { + Snapshot out; + snapshot(out); + return out; +} + +void ExpressionState::snapshot(Snapshot &out) const { std::lock_guard lock(mutex_); - return {seq_, names_, signals_}; + out.seq = seq_; + out.names = names_; + out.signals = signals_; } uint64_t ExpressionState::wait_update(uint64_t after_seq, std::chrono::nanoseconds timeout) const { diff --git a/runtime/src/state/expression_state.h b/runtime/src/state/expression_state.h index 60fc53a..9941a6d 100644 --- a/runtime/src/state/expression_state.h +++ b/runtime/src/state/expression_state.h @@ -33,6 +33,7 @@ class ExpressionState { void update(std::span> values); Snapshot snapshot() const; + void snapshot(Snapshot &out) const; // Writes the current value for ids[i] to out[i] and returns the sequence number of the values. uint64_t read(std::span ids, Signal *out) const; size_t count() const; // The number of registered signals, with ids from 0 to count-1. diff --git a/runtime/src/ui/canvas.cpp b/runtime/src/ui/canvas.cpp index d63b28b..07bc5d1 100644 --- a/runtime/src/ui/canvas.cpp +++ b/runtime/src/ui/canvas.cpp @@ -85,12 +85,19 @@ void Canvas::line(float x0, float y0, float x1, float y1, float width, Color c) void Canvas::image(const Image &img, int x, int y, float radius) { const Rect r{float(x), float(y), float(img.width), float(img.height)}; const float rad = std::min({radius, r.w / 2, r.h / 2}); - for (int iy = 0; iy < img.height; iy++) - for (int ix = 0; ix < img.width; ix++) { + const int x0 = int(std::clamp(-int64_t(x), 0, img.width)); + const int x1 = int(std::clamp(int64_t(w_) - x, 0, img.width)); + const int y0 = int(std::clamp(-int64_t(y), 0, img.height)); + const int y1 = int(std::clamp(int64_t(h_) - y, 0, img.height)); + for (int iy = y0; iy < y1; iy++) + for (int ix = x0; ix < x1; ix++) { const uint8_t *s = &img.rgba[(size_t(iy) * img.width + ix) * 4]; + if (!s[3]) + continue; // The coverage is the rounded-corner coverage multiplied by the alpha of the image. float cov = 1.0f; - if (rad > 0) + if (rad > 0 && (ix + 0.5f < rad || ix + 0.5f > r.w - rad) && + (iy + 0.5f < rad || iy + 0.5f > r.h - rad)) cov = std::clamp(0.5f - round_box_sdf(x + ix + 0.5f, y + iy + 0.5f, r, rad), 0.f, 1.f); blend(x + ix, y + iy, Color{s[0], s[1], s[2], s[3]}, cov); }