diff --git a/src/libvgcode/src/SegmentTemplate.cpp b/src/libvgcode/src/SegmentTemplate.cpp index 4e692d5b10..9d9872aa3b 100644 --- a/src/libvgcode/src/SegmentTemplate.cpp +++ b/src/libvgcode/src/SegmentTemplate.cpp @@ -15,7 +15,12 @@ namespace libvgcode { //| 2--0-------5--7 | //| \ | | / | //| 3-------4 | -static constexpr const std::array VERTEX_DATA = { +// The eight corners the vertex shader knows how to place. Each is sent once and +// referenced by INDEX_DATA below, so the post-transform cache can reuse it across +// the triangles that share it: the shader runs 8 times per segment instead of 24. +static constexpr const std::array VERTEX_DATA = { 0, 1, 2, 3, 4, 5, 6, 7 }; + +static constexpr const std::array INDEX_DATA = { 0, 1, 2, // front spike 0, 2, 3, // front spike 0, 3, 4, // right/bottom body @@ -31,7 +36,7 @@ void SegmentTemplate::init() if (m_vao_id != 0) return; - m_size_in_bytes_gpu += VERTEX_DATA.size() * sizeof(uint8_t); + m_size_in_bytes_gpu += (VERTEX_DATA.size() + INDEX_DATA.size()) * sizeof(uint8_t); int curr_vertex_array; glsafe(glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &curr_vertex_array)); @@ -51,12 +56,22 @@ void SegmentTemplate::init() glsafe(glVertexAttribIPointer(0, 1, GL_UNSIGNED_BYTE, 0, (const void*)0)); #endif // ENABLE_OPENGL_ES + // The element buffer binding is part of the vao state, so it is left bound here + // and restored together with the vao. + glsafe(glGenBuffers(1, &m_ibo_id)); + glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibo_id)); + glsafe(glBufferData(GL_ELEMENT_ARRAY_BUFFER, INDEX_DATA.size() * sizeof(uint8_t), INDEX_DATA.data(), GL_STATIC_DRAW)); + glsafe(glBindBuffer(GL_ARRAY_BUFFER, curr_array_buffer)); glsafe(glBindVertexArray(curr_vertex_array)); } void SegmentTemplate::shutdown() { + if (m_ibo_id != 0) { + glsafe(glDeleteBuffers(1, &m_ibo_id)); + m_ibo_id = 0; + } if (m_vbo_id != 0) { glsafe(glDeleteBuffers(1, &m_vbo_id)); m_vbo_id = 0; @@ -71,14 +86,15 @@ void SegmentTemplate::shutdown() void SegmentTemplate::render(size_t count) { - if (m_vao_id == 0 || m_vbo_id == 0 || count == 0) + if (m_vao_id == 0 || m_vbo_id == 0 || m_ibo_id == 0 || count == 0) return; int curr_vertex_array; glsafe(glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &curr_vertex_array)); glsafe(glBindVertexArray(m_vao_id)); - glsafe(glDrawArraysInstanced(GL_TRIANGLES, 0, static_cast(VERTEX_DATA.size()), static_cast(count))); + glsafe(glDrawElementsInstanced(GL_TRIANGLES, static_cast(INDEX_DATA.size()), GL_UNSIGNED_BYTE, + nullptr, static_cast(count))); glsafe(glBindVertexArray(curr_vertex_array)); } diff --git a/src/libvgcode/src/SegmentTemplate.hpp b/src/libvgcode/src/SegmentTemplate.hpp index f104eef5d2..117ed2b678 100644 --- a/src/libvgcode/src/SegmentTemplate.hpp +++ b/src/libvgcode/src/SegmentTemplate.hpp @@ -40,6 +40,7 @@ private: // unsigned int m_vao_id{ 0 }; unsigned int m_vbo_id{ 0 }; + unsigned int m_ibo_id{ 0 }; // // Size of the data sent to gpu, in bytes. // diff --git a/src/libvgcode/src/ViewerImpl.cpp b/src/libvgcode/src/ViewerImpl.cpp index 10fda297d4..b1a7550b42 100644 --- a/src/libvgcode/src/ViewerImpl.cpp +++ b/src/libvgcode/src/ViewerImpl.cpp @@ -883,6 +883,12 @@ void ViewerImpl::reset() m_travels_time = { 0.0f, 0.0f }; m_vertices.clear(); m_vertices_colors.clear(); + // swap rather than clear: these are sized by the print, and a reset means the memory + // should go back, not sit reserved until the next load + for (std::vector& times : m_layer_start_times) + std::vector().swap(times); + std::vector().swap(m_layer_first_vertex); + std::vector().swap(m_colors_scratch); m_valid_lines_bitset.clear(); #if VGCODE_ENABLE_COG_AND_TOOL_MARKERS m_cog_marker.reset(); @@ -1056,6 +1062,37 @@ void ViewerImpl::load(GCodeInputData&& gcode_data) v.layer_duration = m_layers.get_layer_time(m_settings.time_mode, static_cast(v.layer_id)); } + // Index of the first vertex of each layer, walked back to front so that a layer with no + // vertex of its own inherits the next layer's index and the array stays non-decreasing. + if (!m_layers.empty()) { + const uint32_t vertices_count = static_cast(m_vertices.size()); + m_layer_first_vertex.assign(m_layers.count(), vertices_count); + for (uint32_t i = vertices_count; i > 0; --i) { + const uint32_t layer_id = m_vertices[i - 1].layer_id; + if (layer_id < m_layer_first_vertex.size()) + m_layer_first_vertex[layer_id] = i - 1; + } + for (size_t i = m_layer_first_vertex.size() - 1; i > 0; --i) + m_layer_first_vertex[i - 1] = std::min(m_layer_first_vertex[i - 1], m_layer_first_vertex[i]); + + // the running time at each layer's first vertex, summed in vertex order so that + // get_estimated_time_at() matches a full accumulation exactly + std::array running{}; + for (std::vector& times : m_layer_start_times) + times.assign(m_layer_first_vertex.size(), 0.0f); + size_t layer = 0; + for (size_t i = 0; i <= m_vertices.size(); ++i) { + for (; layer < m_layer_first_vertex.size() && m_layer_first_vertex[layer] == i; ++layer) { + for (size_t j = 0; j < TIME_MODES_COUNT; ++j) + m_layer_start_times[j][layer] = running[j]; + } + if (i < m_vertices.size()) { + for (size_t j = 0; j < TIME_MODES_COUNT; ++j) + running[j] += m_vertices[i].times[j]; + } + } + } + if (!m_layers.empty()) m_layers.set_view_range(0, static_cast(m_layers.count()) - 1); @@ -1269,7 +1306,10 @@ void ViewerImpl::update_colors_texture() // Based on current settings and slider position, we might want to render some // vertices as dark grey (or darkened, see above). Use either that or the normal color (from the cache). - std::vector colors(m_vertices_colors.size()); + // Reused across calls: this runs on every slider tick, and the allocation alone is + // 4 bytes per vertex of the whole print each time. + std::vector& colors = m_colors_scratch; + colors.resize(m_vertices_colors.size()); assert(colors.size() == m_vertices.size() && m_vertices_colors.size() == m_vertices.size()); for (size_t i=0; i(m_settings.time_mode)]; }); + const size_t mode = static_cast(m_settings.time_mode); + if (mode >= TIME_MODES_COUNT || id >= m_vertices.size()) + return 0.0f; + size_t first = 0; + float time = 0.0f; + const size_t layer = static_cast(m_vertices[id].layer_id); + if (layer < m_layer_first_vertex.size() && m_layer_first_vertex[layer] <= id) { + first = m_layer_first_vertex[layer]; + time = m_layer_start_times[mode][layer]; + } + for (size_t i = first; i <= id; ++i) + time += m_vertices[i].times[mode]; + return time; } Color ViewerImpl::get_vertex_color(const PathVertex& v) const @@ -1759,6 +1810,10 @@ size_t ViewerImpl::get_used_cpu_memory() const ret += sizeof(m_extrusion_roles_colors); ret += sizeof(m_options_colors); ret += STDVEC_MEMSIZE(m_vertices, PathVertex); + for (const std::vector& times : m_layer_start_times) + ret += STDVEC_MEMSIZE(times, float); + ret += STDVEC_MEMSIZE(m_layer_first_vertex, uint32_t); + ret += STDVEC_MEMSIZE(m_colors_scratch, float); ret += m_valid_lines_bitset.size_in_bytes_cpu(); ret += m_height_range.size_in_bytes_cpu(); ret += m_width_range.size_in_bytes_cpu(); @@ -1824,7 +1879,11 @@ void ViewerImpl::update_view_full_range() const bool travels_visible = m_settings.options_visibility[size_t(EOptionType::Travels)]; const bool wipes_visible = m_settings.options_visibility[size_t(EOptionType::Wipes)]; + // every vertex before m_layer_first_vertex[layers_range[0]] has a smaller layer_id, so the loop + // below would skip all of them anyway auto first_it = m_vertices.begin(); + if (layers_range[0] < m_layer_first_vertex.size()) + first_it += m_layer_first_vertex[layers_range[0]]; while (first_it != m_vertices.end() && (first_it->layer_id < layers_range[0] || !is_visible(*first_it, m_settings))) { ++first_it; diff --git a/src/libvgcode/src/ViewerImpl.hpp b/src/libvgcode/src/ViewerImpl.hpp index 9231cbdefc..c9d658beb1 100644 --- a/src/libvgcode/src/ViewerImpl.hpp +++ b/src/libvgcode/src/ViewerImpl.hpp @@ -252,6 +252,20 @@ private: // std::array m_total_time{ 0.0f, 0.0f }; // + // Running sum of the vertex estimated times at each layer's first vertex, for each time mode, + // so that get_estimated_time_at() only accumulates the vertices of one layer. + // + std::array, TIME_MODES_COUNT> m_layer_start_times; + // + // For each layer L, the index of the first vertex whose layer_id is >= L (m_vertices.size() + // if there is none). Derived from the vertices, so it stays exact whatever order they arrive in. + // + std::vector m_layer_first_vertex; + // + // Scratch buffer for update_colors_texture(), kept alive across slider steps + // + std::vector m_colors_scratch; + // // Detected travel moves times // std::array m_travels_time{ 0.0f, 0.0f }; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index b86af5c259..68e81493c1 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1698,7 +1698,7 @@ void GCodeViewer::render_scene(int canvas_width, int canvas_height) glsafe(::glEnable(GL_DEPTH_TEST)); render_shells(canvas_width, canvas_height); - if (m_viewer.get_extrusion_roles().empty()) + if (m_viewer.get_extrusion_roles_count() == 0) return; render_toolpaths(); @@ -3520,6 +3520,12 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv std::vector>> ret; ret.reserve(custom_gcode_per_print_z.size()); + // Loop invariant, but built lazily: this lambda runs once per extruder on every frame + // and most prints reach neither colour change below, so fetching it up front would cost + // more than the per-item fetch it replaces. + std::vector zs; + bool zs_built = false; + for (const auto& item : custom_gcode_per_print_z) { if (extruder_id + 1 != static_cast(item.extruder)) continue; @@ -3527,7 +3533,10 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv if (item.type != ColorChange) continue; - const std::vector zs = m_viewer.get_layers_zs(); + if (!zs_built) { + zs = m_viewer.get_layers_zs(); + zs_built = true; + } auto lower_b = std::lower_bound(zs.begin(), zs.end(), static_cast(item.print_z - epsilon())); if (lower_b == zs.end()) @@ -4680,6 +4689,8 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv // ORCA: Get layer Zs as doubles std::vector layer_zs = get_layers_zs(); + // loop invariant, same reason as the layer Zs above + const std::vector layer_times = m_viewer.get_layers_estimated_times(); for (Slic3r::CustomGCode::Item custom_gcode : custom_gcode_per_print_z) { ImGui::Dummy({window_padding, window_padding}); @@ -4699,7 +4710,6 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv imgui.text(buf); ImGui::SameLine(max_len * 1.5); - std::vector layer_times = m_viewer.get_layers_estimated_times(); float custom_gcode_time = 0; if (layer > 0) { @@ -4748,7 +4758,7 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv std::string print_str = _u8L("Model printing time"); std::string total_str = _u8L("Total time"); float max_len = window_padding + 2 * ImGui::GetStyle().ItemSpacing.x; - if (m_viewer.get_layers_estimated_times().empty()) + if (m_viewer.get_layers_count() == 0) max_len += ImGui::CalcTextSize(total_str.c_str()).x; else { if (m_viewer.get_view_type() == libvgcode::EViewType::FeatureType)