Port PlotCurveItem experimental opengl codepath to shaders#3055
Port PlotCurveItem experimental opengl codepath to shaders#3055j9ac9k merged 3 commits intopyqtgraph:masterfrom
Conversation
j9ac9k
left a comment
There was a problem hiding this comment.
Should there be other curves with the other connect mechanisms like finite that test with a np.nan value?
Love what you're doing with this!!!
| __all__ = ['PlotCurveItem'] | ||
|
|
||
|
|
||
| class GLC: |
There was a problem hiding this comment.
This might be a good use-case for a dataclass with frozen=True
| program = self.glstate.m_program | ||
| program.bind() | ||
| with warnings.catch_warnings(): | ||
| # PySide2 : RuntimeWarning: SbkConverter: Unimplemented C++ array type. |
There was a problem hiding this comment.
Appreciate the comment specifying what warning we're looking to catch!
Implementing "pairs" already allows the user to plot arbitrary line segments and strips. It shouldn't be difficult by taking reference from the following: Alternatively for "finite" mode, line strips could be extracted for multiple calls to pyqtgraph/pyqtgraph/functions.py Line 1883 in 3743e57 However, I don't think it's a particularly useful thing to implement if the only purpose is to support more of the API of |
I'm in complete agreement here. The histogram and fill should either be removed from Should the library emit warning (or raise an Exception) when using openGL with |
|
A better solution is to only call |
|
Hi @pijyoi , this looks great! This does look like a good opportunity to rethink the architecture we now have with PlotCurveItem and PlotDataItem. It does not seem necessary to replicate everything that PlotCurveItem now does. From my perspective it might be reasonable for a revised PlotCurveItem to take data
That would reasonably move the task of preprocessing for NaNs to PlotDataItem. That could include processing all data into 'pairs', which would indeed be a very generic solution that should work for almost everything. One thing I wanted to add here is that the data I encounter might have very long "good" segments (all finite), then a short section where a failure generates NaN, followed by another "good" segment. If the fastest path is still the equivalent of 'finite', 'skip-tests' mode, then being able to plot multiple long segments of known-good data might be interesting. PlotDataItem would need to figure out what mode to use. As an additional comment/question, OpenGL is probably very happy to render the filled shapes used with fillLevel or by the FillBetweenItem. Is there a particular arrangement of code that avoids transferring the same curve data to the GPU twice? |
I think a good middle ground would be a hypothetical Although this would also support
The filling API of
For OpenGL (caveat, my knowledge of OpenGL is limited), filling the area would require generating the vertices for the triangle strip that covers the area to be filled. So it would in any case be two different sets of vertices to be sent to the GPU. One for the curve to be drawn using |
|
I think implementing a theoretical PlotLineItem so we can be aggressive about API breakage is a great idea. Also gives us the opportunity to use The one question I have is, would it make sense for any of the downsampling stuff to occur at that level, or should that remain in the PlotDataItem/higher level objects to implement? |
|
@pijyoi , thank you for your reply! Your suggestions look very reasonable to me, I have no further comments. I am certain that if you encounter a way to streamline the OpenGL data transfer, you'll be the very first to jump on it. :) I think the downsampling would want to be placed at the same level where transforms like log mode are handled.
|
60ef711 to
fc02759
Compare
I agree that the transforms and downsamples should stick together. In what order that happens, I think there is an argument to be made on how non-linear the transform is; but that's a discussion for another PR. The next concern I have right now is involving "thick" lines. I don't think this needs to be addressed in this specific PR, but I'd like to at least bring it up. The issue right now is the use of https://github.com/mhalber/Lines?tab=readme-ov-file#why Looking at some of the alternatives listed, the geometry shader seems like a good place to inject some functionality here, but that requires OpenGL 3.2. I'd be curious to hear anyone else's thoughts. |
Actually it uses a number of OpenGL 4.5 API (https://github.com/mhalber/Lines/blob/f20b5e2509d98e5d8e273d9730036ec5aecaa02d/geometry_shader_lines.h#L203-L214) |
Oh yeah, OpenGL 4.5 API is a non-starter, anything above 4.1 won't work , alternative being QRhi, but that's a whole other thing. |
| do_fill_outline = do_fill and self.opts['fillOutline'] | ||
|
|
||
| if ( | ||
| getConfigOption('enableExperimental') |
There was a problem hiding this comment.
do you think we should hide this functionality being the enableExperimental config option at this point? The downside of doing this is that it won't get as much usage and we may not get any feedback on its functionality.
There was a problem hiding this comment.
#2257 shows that there's 1 user using OpenGL while simultaneously leaving
enableExperimental=False. So at this point, enablingpaintGLcodepath w/oenableExperimental=Truemay constitute an API breakage for them. @bbc131, would you care to chime in on this?
We don't use this configuration (OpenGL and enableExperimental=False) anymore, so this won't break anything for us.
| __all__ = ['PlotCurveItem'] | ||
|
|
||
|
|
||
| class OpenGLState: |
There was a problem hiding this comment.
This may need to inherit from QObject due to the cleanup method being a Slot.
In some non-pyqtgraph Qt related work, I can into issues when connecting to a slot that wasn't decorated and wasn't decorated with the @Slot() decorator.
https://www.riverbankcomputing.com/static/Docs/PyQt6/signals_slots.html#the-pyqtslot-decorator
Connecting a signal to a decorated Python method also has the advantage of reducing the amount of memory used and is slightly faster.
I know pyqtgraph doesn't decorate its slots but I anticipate this being a change that we'll roll out in the near future.
There was a problem hiding this comment.
Was your issue because of being called from another thread?
In this case here, cleanup has to be a direct call and thus doesn't need to be registered as a QObject metamethod.
There was a problem hiding this comment.
in my case, I was working with qasync so I suppose that's another thread under the hood. Your grasp of the respective internals are better than mine, so I'll defer to your judgment 👍🏻
|
@pijyoi, thank you so much for the fantastic work on pyqtgraph performance. This is an excellent addition like so many that came before.
I am all for rethinking the API and separating concerns, especially where it leads to better performance. However I don't think this PR is the right place to start that work, mostly because (you can hear my experience with vispy here) OpenGL is really weird in some ways, and it tends to guide one toward strange architectural contortions that otherwise make life more difficult. As an example of that point, there's an even more interesting performance target which is to write GPU shaders that perform all of the data processing. Ideally, we should send only the raw data as provided by the user to the GPU and the rest happens there--filtering for finite values, transformations (both linear and nonlinear), construction of new geometry like triangle strips or histogram steps, etc. In that case it may not be necessary to have a simplifed PlotLineItem if, in the end, PlotCurveItem is able to handle all types of data efficiently. I think I prefer a somewhat different approach (that we have discussed before), using a PlotData class that encapsulates raw x,y data plus a set of filters / transforms and then allows the user of that data (PlotDataItem, PlotCurveItem, etc) to specify the result that is needed. For example, if PlotCurveItem wants strips of nan-free data, then PlotData can provide that in the most efficient way. If there is a ScatterPlotItem that uses the same PlotData instance, then when it asks for nan-free values, the PlotData instance can return previously cached results. If you wanted to implement all or part of this in GPU, then you could simply request PlotData give you raw or partially-processed data, plus the extra metadata needed to complete the processing in a GPU shader.
This is indeed a major issue, and one that I don't recommend taking on lightly. For now, just having the high performance of 1px-width lines covers the vast majority of high performance use cases. If you ever want to go further than that, then let's have a long talk about the architecture of vispy and how similar concepts could be implemented here. |
c25ca75 to
a4d29f6
Compare
|
I have implemented the origin shifting suggestion in #2417. This necessitates extra computation in that the data has to be converted to double precision first to perform the translation before finally converting it back to single precision for OpenGL vertex buffers. |
|
Correctness over speed for sure 👍
…On Tue, Jun 18, 2024 at 14:47 pijyoi ***@***.***> wrote:
I have implemented the origin shifting suggestion in #2417
<#2417>.
This does fix the obvious issue demonstrated in #2020 (comment)
<#2020 (comment)>
This necessitates extra computation in that the data has to be converted
to double precision first to perform the translation before finally
converting it back to single precision for OpenGL vertex buffers.
But correctness over speed, right?
—
Reply to this email directly, view it on GitHub
<#3055 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAE5Z7WGXTRQQM4R2YLZHJTZIAM3ZAVCNFSM6AAAAABI763AFCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCNZVHEYDKOBVGY>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
|
Thanks for this PR @pijyoi Getting a more robust OpenGL implementation throughout the library would be such an amazing improvement. Also having some implementations done makes it easier for contributors to attempt to add OpenGL support for other parts too. I hope we can revisit the |
|
non-working: if |

Changes