#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../Axis/Frequency_Axis_p.h" #include "../Axis/Time_Axis_p.h" #include "../architecture/Render_Time.h" #include "../architecture/Renderable.h" #include "../base/Memory.h" #include "../base/global.h" #include "../render/Canvas.h" #include "../render/Image.h" #include "../render/Render_Frame_Snapshot.h" #include "Psc_Cpp_Core/Base/RingBuffer.hpp" #include "Interpolation_p.h" #include "Waterfall.h" namespace renderive { struct Waterfall_Data { int tick{}; std::pmr::vector frequency{memory_resource(Memory_Domain::Waterfall)}; std::shared_ptr completion_state; }; struct Waterfall_Input_Data : Input_Data {}; struct Waterfall_Render_State : Render_State, Hover_Tooltip_State, Waterfall_Prop {}; struct Waterfall_Ring_Buffer { Psc::StreamRingBuffer_ST buffer{memory_resource(Memory_Domain::Waterfall)}; std::pmr::vector record_buffer{memory_resource(Memory_Domain::Waterfall)}; std::pmr::vector snapshot_buffer{memory_resource(Memory_Domain::Waterfall)}; std::pmr::vector row_buffer{memory_resource(Memory_Domain::Waterfall)}; std::size_t record_byte_size{}; std::size_t row_byte_size{}; int row_count{}; int col_count{}; int data_count{}; bool match(int col, int row) const { return col_count == col && row_count == row && record_byte_size; } void resize(int col, int row) { col_count = col; row_count = row; data_count = 0; row_byte_size = sizeof(double) * col_count; record_byte_size = row_byte_size + sizeof(int); record_buffer.resize(record_byte_size); snapshot_buffer.resize(record_byte_size * row_count); row_buffer.resize(col_count); buffer.init(record_byte_size * row_count); } void push_data(const double* data, int tick) { if (data_count == row_count) { buffer.skip(record_byte_size); --data_count; } std::memcpy(record_buffer.data(), data, row_byte_size); std::memcpy(record_buffer.data() + row_byte_size, &tick, sizeof(tick)); buffer.write(record_buffer.data(), record_byte_size); ++data_count; } int snapshot() { buffer.peek_best_effort(snapshot_buffer.data(), record_byte_size * data_count); return data_count; } const double* frequency_data(int row, int first_col, int last_col) { std::size_t offset = static_cast(first_col) * sizeof(double); std::size_t size = static_cast(last_col - first_col + 1) * sizeof(double); std::memcpy(reinterpret_cast(row_buffer.data()) + offset, snapshot_buffer.data() + row * record_byte_size + offset, size); return row_buffer.data(); } const double* frequency_data(int row) { std::memcpy(row_buffer.data(), snapshot_buffer.data() + row * record_byte_size, row_byte_size); return row_buffer.data(); } int tick(int row) const { int value; std::memcpy(&value, snapshot_buffer.data() + row * record_byte_size + row_byte_size, sizeof(value)); return value; } }; struct Waterfall_Image_Cache_Key { int start_tick{}; int first_col{}; int last_col{}; Range frequency_range{}; Range power_range{}; Range visible_range{}; Color background; std::uint64_t color_map_hash{}; int width{}; int height{}; bool operator==(const Waterfall_Image_Cache_Key& other) const { return start_tick == other.start_tick && first_col == other.first_col && last_col == other.last_col && frequency_range == other.frequency_range && power_range == other.power_range && visible_range == other.visible_range && background == other.background && color_map_hash == other.color_map_hash && width == other.width && height == other.height; } }; struct Waterfall_Image_Snapshot { Waterfall_Image_Snapshot() : update_states(memory_resource(Memory_Domain::Update_Completion)) {} Waterfall_Image_Cache_Key key; Image image; Range frequency_range{}; Range time_range{}; RectF source_rect{}; std::pmr::vector> update_states; }; struct Waterfall_Image_Request { Waterfall_Image_Request() : rows(memory_resource(Memory_Domain::Waterfall)), update_states(memory_resource(Memory_Domain::Update_Completion)) {} Waterfall_Image_Cache_Key key; Color_Map color_map; double value_start_coord{}; double rate{}; int lower_stream_tick{}; int upper_stream_tick{}; int visible_time_count{}; bool newest_at_axis_start{}; Range draw_frequency_range{}; Range draw_time_range{}; RectF draw_source_rect{}; std::pmr::vector rows; std::pmr::vector> update_states; }; struct Waterfall_Image_Tile { int x{}; int y{}; Image image; }; struct Waterfall_Image_Job { explicit Waterfall_Image_Job(std::shared_ptr request, int columns, int rows) : request(std::move(request)), tile_columns(columns), tile_rows(rows), remaining(columns * rows) { tiles.resize(static_cast(columns * rows)); } std::shared_ptr request; int tile_columns{}; int tile_rows{}; std::vector tiles; std::atomic remaining; }; static void complete_waterfall_request_states(Waterfall_Image_Request& request, std::error_code error, Update_Outcome outcome) { for (auto& state : request.update_states) { if (state) state->complete_all(error, outcome); } request.update_states.clear(); } static void complete_waterfall_result_states(Waterfall_Image_Snapshot& snapshot, std::error_code error, Update_Outcome outcome) { for (const auto& state : snapshot.update_states) { if (state) state->complete_all(error, outcome); } snapshot.update_states.clear(); } static bool waterfall_request_coord_by_stream_tick(const Waterfall_Image_Request& request, int stream_tick, int& coord) { if (request.visible_time_count <= 0) return false; if (stream_tick < request.lower_stream_tick || stream_tick > request.upper_stream_tick) return false; if (request.newest_at_axis_start) coord = request.key.start_tick + (request.upper_stream_tick - stream_tick); else coord = request.key.start_tick + (request.key.height - request.visible_time_count) + (stream_tick - request.lower_stream_tick); return coord >= request.key.start_tick && coord < request.key.start_tick + request.key.height; } static int waterfall_tile_column_count(int width) { static constexpr int Tile_Width = 128; return std::max(1, (width + Tile_Width - 1) / Tile_Width); } static int waterfall_tile_row_count(int height) { static constexpr int Tile_Height = 64; return std::max(1, (height + Tile_Height - 1) / Tile_Height); } static void write_waterfall_tile(Waterfall_Image_Job& job, int tile_index) { const Waterfall_Image_Request& request = *job.request; int tile_x = tile_index % job.tile_columns; int tile_y = tile_index / job.tile_columns; int x_begin = request.key.width * tile_x / job.tile_columns; int x_end = request.key.width * (tile_x + 1) / job.tile_columns; int y_begin = request.key.height * tile_y / job.tile_rows; int y_end = request.key.height * (tile_y + 1) / job.tile_rows; Waterfall_Image_Tile& tile = job.tiles[static_cast(tile_index)]; tile.x = x_begin; tile.y = y_begin; int tile_width = std::max(0, x_end - x_begin); int tile_height = std::max(0, y_end - y_begin); tile.image.resize(tile_width, tile_height); tile.image.fill(request.key.background); for (const Waterfall_Data& row : request.rows) { int coord{}; if (!waterfall_request_coord_by_stream_tick(request, row.tick, coord)) continue; int image_y = coord - request.key.start_tick; if (image_y < y_begin || image_y >= y_end) continue; Pixel* line = tile.image.row(image_y - y_begin); if (!line) continue; int first_col = std::max(request.key.first_col, x_begin); int last_col = std::min(request.key.last_col, x_end - 1); for (int col = first_col; col <= last_col; ++col) { std::size_t source_col = static_cast(col - request.key.first_col); if (source_col >= row.frequency.size()) continue; int color_offset = static_cast((row.frequency[source_col] - request.value_start_coord) * request.rate); line[col - x_begin] = request.color_map.at_offset(color_offset); } } } static void write_waterfall_tiles(Waterfall_Image_Job& job) { int tile_count = static_cast(job.tiles.size()); for (int tile_index = 0; tile_index < tile_count; ++tile_index) write_waterfall_tile(job, tile_index); } static Waterfall_Image_Snapshot build_waterfall_image_snapshot(const std::shared_ptr& request_ptr) { Waterfall_Image_Request& request = *request_ptr; Waterfall_Image_Job job( request_ptr, waterfall_tile_column_count(request.key.width), waterfall_tile_row_count(request.key.height)); write_waterfall_tiles(job); Waterfall_Image_Snapshot snapshot; snapshot.key = request.key; snapshot.frequency_range = request.draw_frequency_range; snapshot.time_range = request.draw_time_range; snapshot.source_rect = request.draw_source_rect; snapshot.image.resize(request.key.width, request.key.height); for (const auto& tile : job.tiles) { for (int y = 0; y < tile.image.height(); ++y) { Pixel* target = snapshot.image.row(tile.y + y); const Pixel* source = tile.image.row(y); if (target && source) std::memcpy(target + tile.x, source, static_cast(tile.image.width()) * sizeof(Pixel)); } } snapshot.update_states = std::move(request.update_states); return snapshot; } struct Waterfall_Private : Typed_Render_Data, Hit_Testable, Hover_Interactive { static constexpr std::size_t Row_Queue_Capacity = 256; Waterfall_Ring_Buffer ring_buffer; std::weak_ptr color_bar; std::optional pending_latest_row; std::pmr::vector> pending_image_update_states{memory_resource(Memory_Domain::Update_Completion)}; rigtorp::MPMCQueue> row_queue; std::atomic_uint64_t dropped_row_count{0}; std::uint64_t last_line_commit_ns{}; std::optional image_snapshot; std::optional submitted_image_key; std::uint64_t pending_latest_deadline_ns{}; Waterfall_Private() : row_queue(Row_Queue_Capacity) {} bool select_test(const PointF& pos) override { return true; } void set_hover_state(Point pos, bool active) override { Render_Edit_Lease edit(this); edit->hover_position = pos; edit->hover_info_active = active; } ~Waterfall_Private() override { if (image_snapshot) complete_waterfall_result_states(*image_snapshot, std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); std::shared_ptr row; while (row_queue.try_pop(row)) { if (row && row->completion_state) row->completion_state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); } if (pending_latest_row && pending_latest_row->completion_state) pending_latest_row->completion_state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); for (auto& state : pending_image_update_states) { if (state) state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); } pending_image_update_states.clear(); } void enqueue_row(int tick, std::span data, std::shared_ptr completion_state = {}) { auto row = std::make_shared(); row->tick = tick; row->frequency.assign(data.begin(), data.end()); row->completion_state = std::move(completion_state); enqueue_row(std::move(row)); } void enqueue_row(int tick, std::pmr::vector&& data, std::shared_ptr completion_state = {}) { auto row = std::make_shared(); row->tick = tick; row->frequency = std::move(data); row->completion_state = std::move(completion_state); enqueue_row(std::move(row)); } void enqueue_row(std::shared_ptr row) { if (!row) return; if (!row_queue.try_push(row)) { if (row->completion_state) row->completion_state->complete_all({}, Update_Outcome::Superseded); dropped_row_count.fetch_add(1, std::memory_order_relaxed); return; } if (row->completion_state) row->completion_state->complete_until(Update_Stage::Input_Released); if (q()) q()->mark_render_dirty(); } void consume_row_queue(std::pmr::vector& output) { std::shared_ptr row; while (row_queue.try_pop(row)) { if (!row) continue; output.push_back(std::move(*row)); } } void commit_row_ticket(Waterfall_Data& row) { if (!row.completion_state) return; row.completion_state->complete_until(Update_Stage::Committed); pending_image_update_states.push_back(std::move(row.completion_state)); } void schedule_pending_latest_deadline(std::uint64_t deadline_ns) { if (!deadline_ns || pending_latest_deadline_ns == deadline_ns) return; pending_latest_deadline_ns = deadline_ns; std::weak_ptr owner = q() ? std::weak_ptr(q()->shared_from_this()) : std::weak_ptr(); Global::instance()->render_scheduler().post_at(deadline_ns, [owner]() { if (auto renderable = owner.lock()) renderable->mark_render_dirty(); }); } bool push_row(std::span frequency_data, int tick, int col_count) { if (frequency_data.size() != col_count) { return false; } ring_buffer.push_data(frequency_data.data(), tick); return true; } bool push_owned_row(std::pmr::vector& frequency_data, int tick, int col_count, Waterfall_Row_Update_Policy update_policy) { if (frequency_data.size() != col_count) { return false; } ring_buffer.push_data(frequency_data.data(), tick); (void)update_policy; return true; } void prepare_data(const Render_Frame_Snapshot& snapshot) override { sync_state_pipeline(); const Waterfall_Render_State* s = render_state(); auto time_axis = s->time_axis.lock(); if (!time_axis) return; auto timeline = Time_Axis_Render_Access::capture_timeline(time_axis.get(), snapshot); if (!timeline) return; int col_count = s->frequency_bin_count, row_count = timeline->visible_time_point_count; if (col_count <= 0 || row_count <= 0) return; if (!ring_buffer.match(col_count, row_count)) { ring_buffer.resize(col_count, row_count); } std::pmr::vector queued_rows(memory_resource(Memory_Domain::Waterfall)); consume_row_queue(queued_rows); switch (s->row_update_policy) { case Waterfall_Row_Update_Policy::All_Pending: for (auto& row : queued_rows) { if (push_row(row.frequency, row.tick, col_count)) commit_row_ticket(row); else if (row.completion_state) row.completion_state->complete_all(std::make_error_code(std::errc::invalid_argument), Update_Outcome::Cancelled); } break; case Waterfall_Row_Update_Policy::Latest_Only: case Waterfall_Row_Update_Policy::Rate_Limited_Latest: { for (auto& row : queued_rows) { if (row.frequency.size() != col_count) { if (row.completion_state) row.completion_state->complete_all(std::make_error_code(std::errc::invalid_argument), Update_Outcome::Cancelled); continue; } if (pending_latest_row && pending_latest_row->completion_state) pending_latest_row->completion_state->complete_all({}, Update_Outcome::Superseded); pending_latest_row = std::move(row); } if (pending_latest_row && pending_latest_row->frequency.size() != col_count) { if (pending_latest_row->completion_state) pending_latest_row->completion_state->complete_all(std::make_error_code(std::errc::invalid_argument), Update_Outcome::Cancelled); pending_latest_row.reset(); } if (pending_latest_row) { bool allow_commit = true; std::uint64_t now_ns = steady_now_ns(); std::uint64_t deadline_ns = 0; if (s->row_update_policy == Waterfall_Row_Update_Policy::Rate_Limited_Latest && last_line_commit_ns != 0) { std::uint64_t interval_ns = static_cast(std::max(1, s->latest_row_min_interval_ms)) * 1000000ull; deadline_ns = last_line_commit_ns + interval_ns; allow_commit = now_ns >= deadline_ns; } if (allow_commit) { if (push_owned_row(pending_latest_row->frequency, pending_latest_row->tick, col_count, s->row_update_policy)) { commit_row_ticket(*pending_latest_row); last_line_commit_ns = now_ns; pending_latest_deadline_ns = 0; pending_latest_row.reset(); } } else { schedule_pending_latest_deadline(deadline_ns); } } break; } } update_image_item(snapshot, *timeline); } void update_image_item(const Render_Frame_Snapshot& snapshot, const Timeline_Stream_Snapshot& timeline) { Waterfall_Render_State* s = render_state(); auto frequency_axis = s->frequency_axis.lock(); auto time_axis = s->time_axis.lock(); if (!frequency_axis || !time_axis) return; auto color_scale = s->color_scale.snapshot(); Abs_Axis* h_axis = frequency_axis.get(); Range h_range = s->frequency_range; Range v_range{static_cast(timeline.coordinate_begin), static_cast(timeline.coordinate_begin + timeline.visible_time_point_count)}; if (s->visible_range_only && !intersect_range(s->frequency_range, Axis_Render_Access::coord_range(h_axis), h_range)) { image_snapshot.reset(); return; } int col_count = s->frequency_bin_count, row_count = timeline.visible_time_point_count; if (col_count <= 0 || row_count <= 0 || s->frequency_range.length() == 0.0 || color_scale->range.size() == 0.0) return; double source_start = static_cast(col_count) * (h_range.origin - s->frequency_range.origin) / s->frequency_range.length(); double source_end = static_cast(col_count) * (h_range.target - s->frequency_range.origin) / s->frequency_range.length(); double source_min = std::min(source_start, source_end); double source_max = std::max(source_start, source_end); int first_col = std::clamp(static_cast(std::floor(source_min)), 0, col_count - 1); int last_col = std::clamp(static_cast(std::ceil(source_max)) - 1, first_col, col_count - 1); double value_start_coord = color_scale->range.origin; double rate = static_cast(color_scale->color_map.size()) / color_scale->range.size(); int buffer_data_count = ring_buffer.snapshot(); int start_tick = timeline.coordinate_begin; std::uint64_t color_map_hash = color_scale->color_map.hash(); Waterfall_Image_Cache_Key key{ start_tick, first_col, last_col, s->frequency_range, color_scale->range, h_range, snapshot.background_color, color_map_hash, col_count, row_count }; bool should_submit = !submitted_image_key || !(*submitted_image_key == key) || !pending_image_update_states.empty(); if (should_submit) { submitted_image_key = key; auto request = std::make_shared(); request->key = key; request->color_map = color_scale->color_map; request->value_start_coord = value_start_coord; request->rate = rate; request->lower_stream_tick = timeline.first_stream_tick; request->upper_stream_tick = timeline.last_stream_tick; request->visible_time_count = static_cast(timeline.times.size()); request->newest_at_axis_start = timeline.newest_at_axis_start; request->draw_frequency_range = h_range; request->draw_time_range = v_range; request->draw_source_rect = RectF(source_start, 0.0, source_end - source_start, static_cast(row_count)); request->rows.reserve(static_cast(buffer_data_count)); for (int row = 0; row < buffer_data_count; ++row) { int buffer_row = buffer_data_count - row - 1; int stream_tick = ring_buffer.tick(buffer_row); int image_coord{}; if (!waterfall_request_coord_by_stream_tick(*request, stream_tick, image_coord)) continue; const double* list = ring_buffer.frequency_data(buffer_row, first_col, last_col); Waterfall_Data image_row; image_row.tick = stream_tick; image_row.frequency.assign(list + first_col, list + last_col + 1); request->rows.push_back(std::move(image_row)); } request->update_states = std::move(pending_image_update_states); pending_image_update_states = std::pmr::vector>(memory_resource(Memory_Domain::Update_Completion)); image_snapshot = build_waterfall_image_snapshot(request); } } void draw_image(Canvas& canvas, Waterfall_Render_State* s, Abs_Axis* h_axis, Time_Axis* v_axis) { if (!image_snapshot || image_snapshot->image.empty()) return; if (image_snapshot->frequency_range.length() == 0.0 || image_snapshot->time_range.length() == 0.0) return; if (image_snapshot->source_rect.width == 0.0 || image_snapshot->source_rect.height == 0.0) return; Axis_Mapping_2D mapping = Axis_Render_Access::mapping(h_axis, v_axis); Axis_Basis_2D basis = Axis_Basis_2D::from_ranges(mapping, image_snapshot->frequency_range, image_snapshot->time_range); canvas.save(); canvas.set_image_interpolation(s->interpolation_mode); canvas.transform( basis.domain_vector.x / image_snapshot->source_rect.width, basis.domain_vector.y / image_snapshot->source_rect.width, basis.value_vector.x / image_snapshot->source_rect.height, basis.value_vector.y / image_snapshot->source_rect.height, basis.origin.x, basis.origin.y); canvas.draw_image(RectF{0.0, 0.0, image_snapshot->source_rect.width, image_snapshot->source_rect.height}, image_snapshot->image, image_snapshot->source_rect); canvas.restore(); } void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot) override { Waterfall_Render_State* s = render_state(); auto frequency_axis = s->frequency_axis.lock(); auto time_axis = s->time_axis.lock(); if (!frequency_axis || !time_axis) return; if (snapshot.frame_update_states && image_snapshot) { for (const auto& state : image_snapshot->update_states) snapshot.frame_update_states->push_back(state); image_snapshot->update_states.clear(); } Abs_Axis* h_axis = frequency_axis.get(); Time_Axis* v_axis = time_axis.get(); draw_image(canvas, s, h_axis, v_axis); if (q()->hover_ok(q())) { q()->draw_hover_tooltip(&canvas, h_axis, v_axis); } } }; } // namespace renderive