- Home /
Real time graphing with texture2d = performance hell
Dear all,
I'm receiving some data from a sensor tool and need to draw the data as it arrives. I just need a normal graph, basically, that inserts points whenever it receives a new data point from the sensor tool.
I have a working solution, but it's incredibly slow. I solved it by adding the new point to a Queue every time one arrives, then updating a Texture2D (1600x200) with the contents of the Queue afterwards, then calling Texture2D.Apply() to upload the texture to VRAM, then assigning the texture to a GUI.Box.
I'm getting a ridiculous drop in framerate when the graph is active, though, especially after it has run for a while. I'm not surprised at all, I know it happens because:
- I need to clear the texture every time to make sure it isn't drawing data in outdated positions when the graph scrolls
- The calls to SetPixels get expensive when the Queue reaches max length (= width of the texture = 1600 data points)
- Texture2D.Apply() is severely limited by bus bandwidth is should never be called continuously in this manner in the first place.
I know I can probably get around these things by defining the graph as a collection of Vector2, and then use RenderTexture to have the graphics card render them to a texture for me. That would save me the call to Apply, right? Problem is, my company hasn't invested in Unity Pro yet. :( So I don't have access to RenderTextures.
Does anyone know of a solution I can use in the meantime that won't destroy my performance?
Thanks a lot in advance,
Christian
EDIT:
This is a follow-up to finalize the question and show that the solution worked. :) I went with the LineRenderer solution proposed in several of the answers. This is what the graph looks like now:
Nevermind the white bars; they're part of the skybox which is visible because the graph is transparent. The little red dot is a Time-indicator. You can go back in time and look at data that got recorded previously, it just shows where on the graph you are. This was implemented with a LineRenderer and a small sphere for the red dot, put in a layer on its own and rendered with an orthographic camera that culls everything but that layer. It has virtually no impact on performance. This question can be closed, now. :)
Could you provide us with a picture of what the graph needs to look like? I have an idea that might work.
Answer by Noise crime · Apr 01, 2011 at 07:08 PM
If you want to continue with the setPixels approach...
Unity does not support native (gpu) non-Power-of-two textures, instead it will convert them all to POT. In fact it will make two copies, one stretched to fit the next POT up and one 'placed' on a next POT. It does this so that it can be used on either meshes or as gui.texture.
So using a 1600x200, becomes a 2048x256 texture and any time you update the texture its being updated by Unity twice!
Far better then for you to create a 2048x256 texture to begin with and simply adjust the uv mapping to display the right area on a plane. This alone can give a substantial boost to performance.
As you've discovered its important to use setPixels over setPixel as it usually offers better performance as you are updating an area of pixels in one go instead of one at a time.
However it can also be beneficial to update only a section of a texture if possible. I suspect, though have not done any tests, that it may be analogous to the opengl glTexSubImage2D function which only updates a portion of an existing texture.
As to other improvements it depends on the type of graph and updates, but as an example.
If you are scrolling a line graph as new data arrives, by shifting the pixel data left/right, requiring substanical amount of data copying/moving then that is obviously going to be slow.
I'd look to use an alternative approach where instead of shifting the pixels you scroll the texture via uv (i.e in the material offset transform) instead. To update the texture you keep track of an offset in the texture as to which column to update. Every time you update a column of pixels in the texture/graph you increment to the next column (wrapping back around at the end) then shift the material offset in the opposite direction.
This method could probably be improved further by storing the graph at 90 degrees (so the texture becomes 256x2048), assuming it normally scrolls left to right. The reason I suggest this is that instead of updating a column of pixels (which will not be sequential in the pixel data) you are able to update a row (which is sequential). Then all you need to do is rotate the texture when displayed to make it horizontal again.
One final improvement, but which requires Unity Pro is to update the texture via direct opengl calls. However this depends upon how much data you are updating, i'm unsure if you manage to just update a single row of the graph each time if it would provide a noticeable performance boost over Unity's functions.
Great, exhaustive answer! Thanks for taking the time to write that out. You are precisely correct - I do need to shift the data towards the left in the texture constantly to scroll when new points arrive. I'm positive the things you've suggested will improve the performance significantly, but they still rely on the costly call to Texture2D.Apply, right?
I think a more prudent approach is to render a graph based on the LineRenderer, as other people have suggested. This has the added advantage of automatically connecting points on the graph.
Yeah, as I said its only if you 'need' to stick with setPixels(), if you can use a LineRenderer its likely to be much faster. Not sure about Apply() being costly, its somewhat relative to size of texture and whether you need to update textures. Personally i'd suspect the fact that you have to convert data first into Unity Color type (floats), only for Unity to have to convert it back to a byte array before uploading to the gpu, is the real performance drain.
Oh and one aspect I forgot to mention was to obviously avoid having mipmapping active on the texture, since updating the texture would require updates to the mipmaps too.
Answer by efge · Apr 01, 2011 at 03:04 PM
You could draw a mesh and use an orthographic camera. Take a look at the Procedural Examples. Maybe the "Tron Trail" is a good starting point.
Or try to use the LineRenderer or the GL Low-level graphics library.
Answer by taoa · Apr 01, 2011 at 03:07 PM
I don't know exactly what kind of result you're supposed to obtain, but I'm pretty sure you could very easily obtain a very similar result by building a Mesh instead of modifying a 2D Texture. Every time you receive new data, you can rebuild your mesh, adding new vertices to it to represent it on your graph.
That would save you having to do nasty things like modifying a 1600x1200 texture in real-time, plus you'd benefit from using the full real-time rendering engine that is Unity to render meshes.
And you wouldn't need Unity pro ^^
I've sort of accepted that this approach is the way to go. :) Thanks for the feedback! I've been bogged down by work on a different part of the program at the moment, but I'll get back to this one within a few days and redo it, scrapping the Texture2D-based approach.
Answer by DaveA · Apr 01, 2011 at 03:16 PM
For 10 measly bucks, get Vectrocity from the Asset Store. Just go look at it (and/or google it)
It's 15 measly bucks, but I agree. ;) http://u3d.as/content/starscene-software/vectrosity/1s7
Answer by flaviusxvii · Apr 01, 2011 at 03:04 PM
Forget textures. Why don't you just render the graph like you'd render anything else?
It seems you're having a fundamental misunderstanding about rendering graphics.