xml3d / xml3d.js

The WebGL/JS implementation of XML3D
Other
75 stars 25 forks source link

Strange performance issues #180

Open Arsakes opened 8 years ago

Arsakes commented 8 years ago

I'm switching large textures 4000x2000 for some object, sometimes after switching beetwen two different textures a sudden drop in performance occurs (camera becomes jagged fps drops), the strange part is the performance can go back to normal after browser window resize - any ideas what can cause it?

I've noticed that function readPixels (and some other related to picking) have very different total excution time in different cases (no performance drop vs performance drop).

EDIT: the same stuff happened before when I was using fullscreen with former application.

csvurt commented 8 years ago

Hi,

see my answer about textures in the stackoverflow question. Long story short, I think a smaller texture size is the only way to improve things here.

About window resizing, I noticed that the active RenderTree is reset to the default one when a resize event happens so that might explain the difference if you're using your own RenderTree. This will be fixed in the next release, until then you should listen for the resize event and re-run your RenderTree initialization code there.

readPixels is always very slow in WebGL but the difference you're seeing might be instances where the picking passes are being re-drawn vs where an old result is used again. If nothing in the scene has changed then the old pass can be reused to do the picking, otherwise it needs to be rendered again which takes more time.

If you don't have any mouse interaction that relies on onmouseover or onmouseout then you can probably disable mousemove picking:

XML3D.options.setValue("renderer-mousemove-picking", false);

If you use the XML3D StandardCamera in camera.js you'll also need to disable it there:

camera.mousemovePicking = false;

This way the picking stuff is only processed for clicks (including mouseup and mousedown).

Arsakes commented 8 years ago

Is there any other way to speed up picking pass? There are in fact models that relies on onmouseover events, but most of scene objects should never be picked at all no matter what way. Honestly I don't understand how picking works is there any resource I can read about it? Maybe there are other ways to optimize picking pass I would be very interested into that.

csvurt commented 8 years ago

Unfortunately there's not much you can do right now. We've talked about making certain objects unpickable through an attribute but it wouldn't be added before we rework the picking system to allow things like custom picking passes.

But this is all open source, so if it's really important you could always change it yourself to have it check for an attribute on the element before drawing the object in the picking pass. The place to do that would be in src/renderer/webgl/render-passes/pick-object.js inside the for loop in renderObjects. You could extend the visibility check there with something like this:

if (!obj.visible || obj.node.hasAttribute("disable-picking"))
     continue;

Then for every mesh or model element that shouldn't be pickable:

<mesh src="mymesh.xml" type="triangles" disable-picking></mesh>

The basic idea in WebGL picking is to give every object a simple number ID and then render those IDs as colors using a special shader. Once all objects are rendered this way you use gl.readPixels at the mouse coordinates to read out the color under the mouse pointer, decode that back into an object ID and then fire off the mouse event on that object.

If you ask for the position or normal of the picked point (available through event.position and event.normal in the event that gets sent to your listener) that kicks off a second render pass that renders just the picked object with a special shader that encodes the data as a color. That also gets read back out using readPixels and returned to your event listener.

Other frameworks might use a ray that's intersected with the scene in JavaScript instead. Both approaches have their advantages and disadvantages.

Arsakes commented 8 years ago

Hmm, now when you explained it to me I wonder if it would be a much performance gain if some objects would be excluded form the pick render pass. It would depends solely on how complex geometry of excluded objects is? Am I right?

csvurt commented 8 years ago

Yes, the triangle count and the number of objects are the two big factors. The materials (shaders) that they use don't play any role because they aren't used in the picking passes.

Arsakes commented 8 years ago

Hmm, I've another question tell me if its the wrong place for such discussion. Is there any way to tell (callback event) that texture was loaded into GPU or into RAM memory? Is there any way to prefetch asset for XML3D (so the browser doesn't need to do request to obtain it - even if it is in cache the browser still checks the version of the file with server and waits for 304).

Seond topic - partial texture update: Can I somehow arrange that only part of texture is changed? I mean by using gl.teximage2d - what would I need to change in XML3D code to make that happen.

csvurt commented 8 years ago

Here's fine :)

There's no callback for that built in, but the way it works is XML3D waits for the image's onload event and uploads the texture to the GPU in the next frame (or possibly right away, I'd have to check that tomorrow at work). This means the image is decoded there too. I had a look at the performance on Monday and it seems like the framerate dips are caused by the image decoding itself. This is done by the browser when the image is sent to the GPU so unfortunately there's nothing we can do about it directly.

Prefetching is not built in to XML3D either but I think there might be ways of emulating it, though I haven't tried them myself. You could try loading the images through a normal <img> tag somewhere on the page (hidden of course). It should then use the cached version when you change the src of a texture to point to that image. If you want to prevent the 304 lookup you need to set the right cache control header on the request that XML3D does, you can do this through the onRequest interface that was added in 5.1. What happens then depends on what the browser decides to do with it, there's no guarantee that it will skip the 304 check.

As far as I know in WebGL there isn't much performance benefit to doing a partial texture update (as seen in this jsperf). And support for texture atlases where you can swap out certain regions only would be a fair bit of work to add. But if you want to have a look at the relevant code I would start with:

src/data/adapter/misc.js ImgDataAdapter wraps the <img> element and hands the image data to a TextureEntry

src/data/adapter/texture.js wraps the <texture> element, doesn't do much except translate the texture wrapping and filtering modes

src/xflow/interface/data.js TextureEntry and TexelSource (the imageData 'get' function looks suspicious, you might want to try changing it to return the _source directly instead of copying it to a canvas first. I'm not sure why it's done this way tbh I'll have to have a closer look at it)

src/renderer/webgl/base/texture.js GLTexture which handles sending the data from the TextureEntry above to the GPU through WebGL

csvurt commented 8 years ago

Above I said you could change the cache control headers through the onRequest interface, but I just realized that's something you need to do server side as part of the response to the first image request. Here's one example of headers that will ask a browser very nicely to use the cached version from now on (whether it actually does or fires off a 304 check anyway is up to the browser).

Arsakes commented 8 years ago

Going back to the performance problems. The performance drop has nothing to do with textures (just checked it). With my former application which showed bare meshes (no texture shader) there was similar problem. After going to fullscreen for a while, preformance dropped drastically (sometimes) to cure the thing one had to resize window a bit and things were magically cured. It had something to do with picking and readPixel function for sure.

readPixels is always very slow in WebGL but the difference you're seeing might be instances where the picking passes are being re-drawn vs where an old result is used again. If nothing in the scene has changed then the old pass can be reused to do the picking, otherwise it needs to be rendered again which takes more time.

Under what circumstances old result is used? How often picking pass is "rendered" do I have control over that? Why you mean by "nothing has changed" - not even camera, or the scene content is the same(no geometry added deleted)?

Also the problem is the performance drops for a loooong time (till you resize the window). Checked all this for default renderer.

I've turned on profiler and compared CPU time for both cases. Performance drop occures: Function, Total time (for this functions and every function fired from inside). readPixels :~60% d.mouseup: ~40% d.mousemove 15.5%

Performance drop doesn't occur: readPixels :~35% d.mouseup: ~4% d.mousemove 14%

csvurt commented 8 years ago

Ok, in that case drawing the picking pass shouldn't have anything to do with it because it's not part of the readPixels call (that happens after). Any scene change at all will trigger a new picking pass, including moving an object, the camera or adding/removing something. It has to otherwise the positions of the objects on the screen won't necessarily match their positions in the picking buffer.

I haven't seen this particular problem happen in any of our scenes so it's hard to say what might be causing it. During a resize the picking render target is recreated, along with the default render tree itself, so this might fix whatever problem there was on the GPU side to cause the performance drop.

Can you post a scene (or a link to one) where this happens? Also what graphics hardware are you using? Maybe I can find something similar here to test with.

Arsakes commented 8 years ago

Will put my application online this weekend. I'm using Nvidia 8600M GT. Will post the link.

Is there a way to completely block picking phase rendering? I've checked XML3D options there doesn't seem to be any way to do it (other than implementing it myslef which I consider.)

Also getting closer to demo release - which means a whole new set of issues to post here :)

csvurt commented 8 years ago

Right now there's no way to turn picking off completely, but it wouldn't be hard to add. You can change src/renderer/webgl/renderer.js to define an option for it and then check the value of that option in getRenderObjectFromPickingBuffer, returning null if picking is disabled.

Options.register("renderer-picking-disabled", false);

...

getRenderObjectFromPickingBuffer: function (x, y) {
   if (Options.getValue("renderer-picking-disabled"))
      return null;
   ...
}