Skip to content

Commit f71b4cc

Browse files
author
andy
committed
Merge origin/main into wip/217-geomtrace
2 parents 58871c4 + e9cad2a commit f71b4cc

7 files changed

Lines changed: 99 additions & 18 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
type: pitfall
3+
title: "Ball and Stick spent most of its frame in the bond-cap test, O(atoms x bonds); the test itself is not a start/count range test"
4+
area: wx-viewer
5+
paths: [src/inv/moiv/ChemUnitCylinder.C, src/inv/moiv/ChemDisplay.C, include/inv/ChemKit/ChemDisplay.H, src/viz/sgcommands/SGContainer.C, tools/viewer-bench/viewer-bench.C]
6+
issues: [224]
7+
---
8+
`ChemUnitCylinder::render`, `renderHalfBonded` and
9+
`renderWithInterpolatedColor` ask, per bond end, whether to draw the
10+
cylinder's end cap (`PRE_RENDER_CAP`). That test scanned every entry of
11+
`ChemDisplay::atomIndex`, and `SGContainer` fills `atomIndex` with one
12+
`(atom, 1)` entry per atom, so a frame cost atoms x bonds field reads:
13+
about 800M instructions per frame for the 5184-atom water box, which is
14+
the gap between Ball and Stick (171 ms on an RTX 3050) and CPK (20 ms).
15+
Now `ChemDisplay::GLRender` reduces `atomIndex` to two bounds once per
16+
frame and `bondCapAtAtom()` answers in O(1).
17+
18+
The old test matches an entry when `count == -1 && atom >= start`, or
19+
when `atom <= start && atom <= count`, which is not "atom lies in
20+
[start, start+count)". With per-atom entries only atoms 0 and 1 lose their
21+
caps; every other cap is drawn inside its sphere. `bondCapAtAtom` keeps
22+
that result exactly; fixing the test would draw less but can change pixels,
23+
so check it with `tools/coin/scenes/styles.scene` (`waterbox` too) first.
24+
25+
The sibling `ADJUST_CYLINDER` in `ChemDisplayCylinders*.C` has the same
26+
range test but breaks on the first non-matching entry, so it is cheap.
27+
28+
Measuring: `BENCH_STYLES="CPK,Ball And Stick,..."` runs only the water
29+
box. On llvmpipe the frame time is dominated by rasterisation and is too
30+
noisy under load to show this; count instructions instead
31+
(`valgrind --tool=callgrind --toggle-collect='ChemDisplay::GLRender*'`).
32+
`perf` is not available to users on Debian (`perf_event_paranoid=3`).
33+
`GALLIUM_NOOP=1` makes the benchmark exit silently.

‎docs/claude/wx-viewer/index.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Read this before changing any dialog, panel, sizer, grid, ewx control or the Ope
3535
- [`SoWxRenderArea::renderCB` during a paint defers the redraw through `p_redrawPending`](sowxrenderarea-rendercb-silently-drops-a-redraw.md)
3636
- [A static `EVT_RADIOBOX` entry never reaches an `ewxRadioBox`'s panel; Bind on the widget (#81)](resolved-81-fixed-9a3004e-confirmed-live-2026.md)
3737
- [ChemDisplay's `glPopAttrib` undoes what Coin's lazy element sent inside it; the first offscreen render drew an ESP surface unlit](chemdisplay-glpopattrib-undoes-lazy-element-sends.md)
38+
- [Ball and Stick spent most of its frame in the bond-cap test, O(atoms x bonds) (#224)](bond-cap-test-was-quadratic.md)
3839
- [The unit cell is drawn only while the Periodic Builder panel is open](unit-cell-drawn-only-by-the-periodic-builder.md)
3940
- [The Vibrational Frequencies graph is `SpectrumCanvas` over the wx-free `VibSpectrum` model (#214)](vibrational-spectrum-canvas-214.md)
4041
- [Builder panel layouts (View > Panel layout): every pane stays an AUI pane; the one-column modes hide the inactive tab's panes](builder-panel-layouts-one-column.md)

‎include/inv/ChemKit/ChemDisplay.H‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,11 @@ class CHEMKIT_DLL_API ChemDisplay : public SoNonIndexedShape {
215215
const SbMatrix &getCurrentMVPMatrix() const
216216
{ return currentMVP; }
217217

218+
// Whether a bond cylinder draws its end cap at this atom (not for
219+
// DISPLAY_STICK, which always does). Valid during GLRender only.
220+
SbBool bondCapAtAtom(int32_t atom) const
221+
{ return !(atom >= capRestStart || atom <= capLowMax); }
222+
218223
protected:
219224
virtual void generatePrimitives(SoAction *action);
220225
virtual void computeBBox(SoAction *action, SbBox3f &box,
@@ -1551,6 +1556,10 @@ private:
15511556
MFVec2i vnormalResidueLabelIndex,vhighlightResidueLabelIndex;
15521557
friend class ChemOctreeNode;
15531558

1559+
// atomIndex reduced for bondCapAtAtom, once per GLRender
1560+
int32_t capRestStart, capLowMax;
1561+
void summarizeAtomIndexForCaps();
1562+
15541563
// duplicates
15551564
bool* renderedBonds;
15561565
bool* renderedResidues;

‎src/inv/moiv/ChemDisplay.C‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include <stdio.h>
22
#include <stdlib.h>
3+
#include <stdint.h>
34
#include <iostream>
45
using namespace std;
56
/*
@@ -384,6 +385,8 @@ ChemDisplay::ChemDisplay()
384385
octreenode = new ChemOctreeNode();
385386
// duplicates
386387
renderedBonds = NULL;
388+
capRestStart = INT32_MAX;
389+
capLowMax = INT32_MIN;
387390
renderedResidues = NULL;
388391
// <-- octree culling
389392
// --> improving ribbon speed
@@ -816,6 +819,7 @@ ChemDisplay::GLRender(SoGLRenderAction *action)
816819
if (!action->isRenderingDelayedPaths()) {
817820
generateIndices(action);
818821
}
822+
summarizeAtomIndexForCaps();
819823

820824
// --> EGB && SGB
821825
lodSelector->resetAtoms(chemData->getNumberOfAtoms());
@@ -6512,3 +6516,34 @@ void ChemDisplay::eachBBoxResiduesAsCylinders(SoState *state, ChemDisplayParam *
65126516
#undef ATOMLOOP_END
65136517
#undef BONDLOOP_START
65146518
#undef BONDLOOP_END
6519+
6520+
6521+
////////////////////////////////////////////////////////////////////////
6522+
//
6523+
// Description:
6524+
// Reduces atomIndex to two bounds so that bondCapAtAtom is O(1).
6525+
// The cylinder code asked this per bond end by scanning every
6526+
// atomIndex entry (one per atom in the Builder), O(atoms x bonds) per
6527+
// frame. The result is exactly the old scan's: a cap is drawn unless
6528+
// some entry matches, where an entry matches if count == -1 and
6529+
// atom >= start, or else if atom <= start and atom <= count. (That is
6530+
// not the start/count range test; changing it would change the image.)
6531+
//
6532+
// Use: private
6533+
6534+
void
6535+
ChemDisplay::summarizeAtomIndexForCaps()
6536+
{
6537+
capRestStart = INT32_MAX;
6538+
capLowMax = INT32_MIN;
6539+
const SbVec2i *ranges = atomIndex.getValues(0);
6540+
for (int i = 0, n = atomIndex.getNum(); i < n; i++) {
6541+
int32_t start = ranges[i][0], count = ranges[i][1];
6542+
if (count == -1) {
6543+
if (start < capRestStart) capRestStart = start;
6544+
} else {
6545+
int32_t low = (start < count) ? start : count;
6546+
if (low > capLowMax) capLowMax = low;
6547+
}
6548+
}
6549+
}

‎src/inv/moiv/ChemUnitCylinder.C‎

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -228,18 +228,8 @@ renderSoCylinder(SoCylinder *cyl, SoGLRenderAction *action)
228228

229229
// --> roundcap optimization
230230
#define PRE_RENDER_CAP(ATOM,BOOLEAN) \
231-
BOOLEAN = true; \
232-
if (cdp->displayStyle.getValue() != ChemDisplayParam::DISPLAY_STICK) \
233-
{ \
234-
int __i; \
235-
for (__i=0; __i<cd->atomIndex.getNum();__i++) \
236-
{ \
237-
const SbVec2i range = *cd->atomIndex.getValues(__i); \
238-
if (range[1] == -1) BOOLEAN = !(ATOM >= range[0]); \
239-
else BOOLEAN = !(range[0]>=ATOM && ATOM<=range[1]); \
240-
if (!BOOLEAN) break; \
241-
} \
242-
}
231+
BOOLEAN = (cdp->displayStyle.getValue() == ChemDisplayParam::DISPLAY_STICK) \
232+
|| cd->bondCapAtAtom(ATOM);
243233
// <-- roundcap optimization
244234

245235
////////////////////////////////////////////////////////////////////////
@@ -2555,7 +2545,6 @@ void ChemUnitCylinder::renderHalfBonded(
25552545
// --> cap optimization
25562546
ChemDisplayParam* cdp = ChemDisplayParamElement::get(action->getState());
25572547
bool renderFrom, renderTo;
2558-
//KLS VERY SLOW
25592548
PRE_RENDER_CAP(fromTo[0],renderFrom);
25602549
PRE_RENDER_CAP(fromTo[1],renderTo);
25612550
// <-- cap optimization

‎tools/viewer-bench/README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ build-cmake/viewer-bench-<host>-<date>.txt
2525
Send that file. Leave the window uncovered while it runs. Optional:
2626
`BENCH_FRAMES=360 BENCH_SECONDS=8 tools/viewer-bench/run.sh` (frames per run,
2727
time cap per run; defaults 180 and 4 s, 10 warm-up frames not counted).
28+
`BENCH_STYLES="CPK,Ball And Stick,Stick,Wireframe,Ball And Wireframe"` times
29+
only the water box, in those styles, with caching AUTO.
2830

2931
## What is timed
3032

‎tools/viewer-bench/viewer-bench.C‎

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -438,17 +438,29 @@ int BenchApp::OnRun()
438438
{"water box", "Ball And Stick", false}, {"water box", "CPK", false},
439439
};
440440

441+
// BENCH_STYLES="CPK,Stick,...": only the water box, in those styles,
442+
// caching AUTO (for profiling one style at a time).
443+
vector<Config> todo(configs, configs + sizeof configs / sizeof configs[0]);
444+
const char *only = getenv("BENCH_STYLES");
445+
if (only) {
446+
todo.clear();
447+
std::istringstream ss(only);
448+
string st;
449+
while (std::getline(ss, st, ','))
450+
if (!st.empty()) todo.push_back(Config{"water box", st, false});
451+
}
452+
441453
vector<Result> results;
442-
for (size_t i = 0; i < sizeof configs / sizeof configs[0]; i++) {
454+
for (size_t i = 0; i < todo.size(); i++) {
443455
string err;
444-
if (!loadScene(configs[i], err)) {
445-
fprintf(stderr, "skipping %s: %s\n", configs[i].system.c_str(),
456+
if (!loadScene(todo[i], err)) {
457+
fprintf(stderr, "skipping %s: %s\n", todo[i].system.c_str(),
446458
err.c_str());
447459
continue;
448460
}
449-
for (int cache = 1; cache >= 0; cache--) {
461+
for (int cache = 1; cache >= (only ? 1 : 0); cache--) {
450462
Result r;
451-
if (measure(configs[i], cache != 0, warm, frames, budget, r))
463+
if (measure(todo[i], cache != 0, warm, frames, budget, r))
452464
results.push_back(r);
453465
Yield(true);
454466
}

0 commit comments

Comments
 (0)