Several test models I've used lately, including the sample WaterBottle model, have a very small amount of mesh data, and yet still take a long time to export compared to other Blender exporters.
I've been investigating the speed of the exporter (mostly with some hacked logging of times).
Turns out, for the models I've been testing, the performance bottleneck is exporting the images. In particular, the __get_image_data function spends several seconds per exported texture, slowly dismantling the color channels and reassembling them. This is really only needed for the OcclusionRoughnessMetallic texture, and only when the user's mapping of that texture's channels do not match the glTF spec. For other channels like BaseColor, Normal, Emission, and correctly baked ORM textures (such as from imported models), this is wasted export time.
But even more time-consuming than that, is a little parameter here in the PNG writer:
https://github.com/KhronosGroup/glTF-Blender-IO/blob/4c15398154af66e817f66a53fab3d4f03662238d/addons/io_scene_gltf2/io/exp/gltf2_io_image_data.py#L135
The number 9 here is the compression level setting, being passed to zlib. From the zlib manual:
#define Z_NO_COMPRESSION 0
#define Z_BEST_SPEED 1
#define Z_BEST_COMPRESSION 9
#define Z_DEFAULT_COMPRESSION (-1)
The compression level must be
Z_DEFAULT_COMPRESSION, or between 0 and 9: 1 gives best speed, 9 gives best compression, 0 gives no compression at all (the input data is simply copied a block at a time).Z_DEFAULT_COMPRESSIONrequests a default compromise between speed and compression (currently equivalent to level 6).
I tried setting this to -1 for the default compression, and saw a significant speedup but some cost in file size. I hesitate to add yet another option to our already overwhelming list of export options, but this one has a big effect on time spent and resulting size.
And of course, if the exporter already has access to a source PNG or JPG, it should be just copying the already-compressed data from there directly, not re-running the zlib compression as that takes a lot of time.
We will have a look at it today/tomorrow.
Probably worth mentioning: Besides being slow, I also noticed high RAM usage when exporting large scenes (it went up to ~12 GB RAM usage, and it took more than 5 minutes to export)
In particular, the __get_image_data function spends several seconds per exported texture, slowly dismantling the color channels and reassembling them. This is really only needed for the OcclusionRoughnessMetallic texture, and only when the user's mapping of that texture's channels do not match the glTF spec. For other channels like BaseColor, Normal, Emission, and correctly baked ORM textures (such as from imported models), this is wasted export time.
We should definitely look into this then!
I hesitate to add yet another option to our already overwhelming list of export options, but this one has a big effect on time spent and resulting size.
Maybe we should just use the default compression.
And of course, if the exporter already has access to a source PNG or JPG, it should be just copying the already-compressed data from there directly, not re-running the zlib compression as that takes a lot of time.
This should work since #130
After merging #207, the export is much faster already (83 % in the WaterBottle example) as you said. I will look into the other things next week.
Embedded output (.glb or .gltf with embedded textures) currently always encodes in .png :(
This for me ruins glb output as it takes my files from e.g. 800K to 8M. As mentioned by others: whenever possible output format should be preserved form the source image. And when we need conversion (for ORM textures) it would be really nice to have the option to specify output format - for web content size matters a lot, and factor 10 difference is a killer.
Embedded output (.glb or .gltf with embedded textures) currently always encodes in .png :(
This for me ruins glb output as it takes my files from e.g. 800K to 8M. As mentioned by others: whenever possible output format should be preserved form the source image. And when we need conversion (for ORM textures) it would be really nice to have the option to specify output format - for web content size matters a lot, and factor 10 difference is a killer.
I noticed the same thing. So as a workaround, I exported as gltf + separate bin + textures, then used Analytics' gltf-pipeline tool to "convert" it to a single glb
@soadzoor Yeah that will work - though it is an extra step. I'm currently also using gltf-pipeline for draco compression (until that lands in the exporter).
Is it possible to have jpg embedded textures @UX3D-nopper ?
As far as I know, there is no jpg encoder available in blenders python environment. Therefore we would never be able to convert a .png to .jpg or similar. However it should theoretically be possible to just load raw jpgs and embed them. I will take a look at the feasibility of such an approach today.
AFAIK, it worked well on the previous version: https://github.com/KhronosGroup/glTF-Blender-Exporter
We ended up doing it like this:
If a texture is originally PNG, then export it as PNG. If it's originally JPG, export it as JPG. If it's something else besides these two, then export it as JPG for textures-without-alpha, PNG for textures-with-alpha.
https://github.com/KhronosGroup/glTF-Blender-Exporter/pull/221
@soadzoor Thanks for the bump on that issue. Digging into that a little further, it looks like the exporter was actually saving the images like this:
context.scene.render.image_settings.file_format = file_format
context.scene.render.image_settings.color_depth = '8'
blender_image.save_render(dst_path, context.scene)
Basically we were asking Blender to save the image with native code, rather than a Python implementation of a PNG writer like we have now. This was probably more performant, but had a couple issues that I don't remember if we resolved, including the scene's exposure controls influencing the image. Also, the old exporter never attempted to assemble a new image out of channels from existing images, which we do in this exporter from the Principled node. Still this may be worth some investigation to see if this option should be resurrected for certain situations.
Just checking what happens when using Image > Save a Copy... gives me:
bpy.ops.image.save_as(
save_as_render=False,
copy=True,
filepath="//materials\\Wet Mud\\Wet Mud_Albedo_FOO.jpg",
relative_path=True,
show_multiview=False,
use_multiview=False
)
Maybe that could be used somehow?
@pjoe That seems like a good option for saving out JPG files to disk. I don't know if there's some way to capture that data for use in a GLB file. Probably still worth considering hooking that up for "separate" glTF mode.
The dialog that shows up when doing this in the UI allows choosing format (jpg, png, exr, tga, tif) and compression quality. Not sure how to control that from python though.
For GLB: maybe save to temp file and load buffer from there (just a thought).
You can take a look at the feature/jpg_export branch, it’s still work in progress but illustrates the way how in future jpegs could be exported.
Pull request #404 implements jpeg export functionality. @pjoe would you maybe check the branch out and test if this resolves your issue?
Will try to take a look. I'm in process of setting up new laptop, so may take a day or two before I get to it.
Just came across this: https://devtalk.blender.org/t/image-python-api-for-blender/174/5
Maybe worth looking into :) Guess it's only in 2.80 though
the more problem i had with animation export performance. i can take even 5-10 minutes for me with 10 animations. blender 2.8 and windows
@oxplay2 Can you zip up a sample .blend and drag it in here?
Hi, I also experience slow exporting issues. Here is a simple test file (Monkey with subdivision modifier). Exporting this object takes around ~30 seconds with glTF (or glb). Exporting to fbx takes around ~2 seconds.
gltf_slow_test.zip
My Blender version: 2.80 (sub 74), branch: master, commit date: 2019-06-19 18:29, hash: rBd30f72dfd8ac
Windows 10
https://www.youtube.com/watch?v=CSAl3vgxu4M&t=4m
That's unacceptable RAM usage.
https://github.com/KhronosGroup/glTF-Blender-IO/issues/221 - no one should have to TRIPLE their virtual memory. This exporter was not stable enough for release with 2.8
It would be helpful to know the feature(s) involved when performance is an issue – you can identify this by disabling animation, materials, etc. and comparing the export time. Currently we've identified bottlenecks in:
There may be others.
Textures were/are likely the main issue here, and @julienduroure has submitted a significant improvement there. And as @emackey mentions above, the exporter really should avoid re-compressing texture data unless it's necessary.
@wcbx I understand your frustration with the issue, but please keep in mind that this is not affecting all users, and feedback of the form "This exporter was not stable enough for release" is neither constructive nor productive in finding a solution. You've commented in a couple places that you're unhappy with performance, but haven't (as far as I can tell) provided an example that we can debug.
I agree with Don's comments.
From the video that @wcbx linked to, there is mention of a Sketchfab model that can be downloaded here: https://sketchfab.com/3d-models/laiku-9fe052ce3d174a8fa187dba89ee7a279
I tried this model on my system, and I didn't need to triple my virtual memory to get it to re-export to GLB with the default settings. It took about 4 minutes, so there's plenty room for speed improvement, but it didn't crash. I tested this on a several-years-old Dell laptop, originally Windows 7 but later upgraded to Windows 10, with 16GB RAM and what looks like only 2.4GB virtual memory. The RAM did get nearly full at one point, but I also had Outlook running and GMail and the Sketchfab page was open in the background at the time. The Blender export was successful, I tested the result.
@wcbx Certainly there's lots of room for improvement, but I've been using this exporter for work-related models for well over a year now, so I consider it stable for release. I wouldn't recommend that all users go changing their system settings or virtual memory.
As Don mentions, materials and textures can have a big impact on export performance and required resources. The glTF exporter is trying to do things with textures above and beyond what formats like OBJ will do. For example, glTF packs PBR channels like "roughness" and "metallic" into specific color channels, requiring that all glTF models agree to a particular standardization of those channels, lowering the bar for glTF loaders and viewers across a wide array of platforms to join the ecosystem. This raises the bar for exporters like this one, which must analyze a complex set of Blender material nodes and calculate how to pack which channels into which images during export. This is a lot more work than OBJ just writing the name of a texture map into a material file.
The textures that ship with this particular sample model are large: Three 4096x4096, and an 8192x8192 image are used. And if you're exporting to GLB, then all that image data needs to be read out of those files, and packed into the GLB via Python. There are various things you can try, including turning off material export or using smaller stand-in textures, if you need the export to work on an under-powered system. Actually as I'm writing this I just tried a no-material export, it ran in 7 seconds and didn't make a blip on the memory graph, so I think textures/materials are the clear culprit for this model.
So that's my advice. We'll continue to try to improve performance, particularly texture export performance. Julian just contributed such an improvement in #634 just three days ago (and my tests above did NOT include this improvement, I'll have to pull the repo and re-run my tests again).
But in the meantime, understand that glTF imposes strong requirements on textures being exported, so we're not just writing texture filenames like some other exporters. You may need a bit more RAM to be able to convert large textures for glTF export, or you have the option to turn that off and manually reassign textures on the other side.
Just tried with the #634 fix applied. The memory graph still peaked on my 16GB system, but the export took only half the time, about 2min 15sec. Awesome improvement @julienduroure!
If your 16 GB RAM is peaking then this should never have been released for 2.8
@wcbx If you work with large textures (like 8K) and the exporter needs to process them as explained by @emackey (e.g. to pack channels into singe ORM texture as required by glTF), then it is unfortunately gonna take memory and time. You can read more about how this works here: https://docs.blender.org/manual/en/latest/addons/io_scene_gltf2.html#metallic-and-roughness
Best option to avoid this would be to have the textures already packed 'correctly', so the exporter can just directly copy them over.
It's still as slow as a snail to this date.
I'm writing this while waiting for it to complete, slowest exporter I've seen anywhere.
Airing grievances online is not going to fix any performance problems in any type of software. The success of open-source software comes from those contributors willing to pitch in.
I'm using the release version of Blender 2.80, and I'm using the default settings on the glTF exporter. If I don't export materials, the exporter runs very quickly.
However, with materials enabled, I see the same problems described above. I notice the zlib compression is still set to the slowest speed (9), which may be one issue.
Exports take an hour an a half, and use all the memory on a 16gig machine. Sometimes the exporter crashes with an out of memory error (see traceback below).
I have 36 materials in my model, and for each one there are several textures (basecolor, normal, emissive, metallic, opacity and roughness). So 6 times 36 is 216 textures. Yes, that's a lot, but an hour and a half seems like a very long time indeed.
I'm curious... is it possible to export to separate files (json, bin, textures) once, and then subsequently export without materials? Our textures and materials aren't changing, we're essentially just moving objects around at this point.
If so, how would that work when importing? (I realize it may depend on the importer -- I'm using UniGLTF from Github to import into Unity).
If anyone has any suggestions, they'd be much appreciated. If it's an hour and a half every time we export, we'll end up spending more time waiting for the exporter than we spent creating the model!
Here's the traceback:
Traceback (most recent call last):
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2__init__.py", line 412, in execute
return gltf2_blender_export.save(context, export_settings)
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_export.py", line 40, in save
json, buffer = __export(export_settings)
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_export.py", line 54, in __export
__gather_gltf(exporter, export_settings)
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_export.py", line 69, in __gather_gltf
scenes, animations = gltf2_blender_gather.gather_gltf2(export_settings)
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_gather.py", line 34, in gather_gltf2
scenes.append(__gather_scene(blender_scene, export_settings))
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_gather_cache.py", line 63, in wrapper_cached
result = func(args)
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_gather.py", line 52, in __gather_scene
node = gltf2_blender_gather_nodes.gather_node(blender_object, blender_scene, export_settings)
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_gather_nodes.py", line 45, in gather_node
node = __gather_node(blender_object, blender_scene, export_settings)
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_gather_cache.py", line 63, in wrapper_cached
result = func(args)
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_gather_nodes.py", line 63, in __gather_node
mesh=__gather_mesh(blender_object, export_settings),
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_gather_nodes.py", line 273, in __gather_mesh
export_settings)
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_gather_cache.py", line 63, in wrapper_cached
result = func(args)
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_gather_mesh.py", line 40, in gather_mesh
primitives=__gather_primitives(blender_mesh, vertex_groups, modifiers, material_names, export_settings),
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_gather_mesh.py", line 113, in __gather_primitives
export_settings)
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_gather_cache.py", line 63, in wrapper_cached
result = func(args)
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_gather_primitives.py", line 60, in gather_primitives
export_settings)
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_gather_cache.py", line 63, in wrapper_cached
result = func(args)
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_gather_materials.py", line 53, in gather_material
normal_texture=__gather_normal_texture(blender_material, export_settings),
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_gather_materials.py", line 153, in __gather_normal_texture
export_settings)
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_gather_cache.py", line 63, in wrapper_cached
result = func(args)
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_gather_material_normal_texture_info_class.py", line 37, in gather_material_normal_texture_info_class
index=__gather_index(blender_shader_sockets_or_texture_slots, export_settings),
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_gather_material_normal_texture_info_class.py", line 93, in __gather_index
return gltf2_blender_gather_texture.gather_texture(blender_shader_sockets_or_texture_slots, export_settings)
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_gather_cache.py", line 63, in wrapper_cached
result = func(args)
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_gather_texture.py", line 47, in gather_texture
source=__gather_source(blender_shader_sockets_or_texture_slots, export_settings)
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_gather_texture.py", line 94, in __gather_source
return gltf2_blender_gather_image.gather_image(blender_shader_sockets_or_texture_slots, export_settings)
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_gather_cache.py", line 63, in wrapper_cached
result = func(args)
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_gather_image.py", line 39, in gather_image
image_data = __get_image_data(blender_shader_sockets_or_texture_slots, export_settings)
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_gather_image.py", line 174, in __get_image_data
image = gltf2_blender_image.ExportImage.from_blender_image(result.shader_node.image)
File "C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons\io_scene_gltf2\blender\exp\gltf2_blender_image.py", line 46, in from_blender_image
img = np.array(blender_image.pixels)
MemoryError
location:
I'm curious... is it possible to export to separate files (json, bin, textures) once, and then subsequently export without materials? Our textures and materials aren't changing, we're essentially just moving objects around at this point.
As long as people know what they're doing, this could really save a lot of time!
However, with materials enabled, I see the same problems described above. I notice the zlib compression is still set to the slowest speed (9), which may be one issue.
This causes an extreme slowdown compared to the default setting which is why set it to the default at some point. Why it's back to slowest, I don't know (I'm not really up-to-date with everything), but back then, my speed up was ~83% just by removing that explicit slowdown setting.
This is issue is still there. We've added a few textures to our model, and now the exporter crashes consistently because it runs out of memory.
Blender 2.82 will ship with massive improvements here thanks to @scurest's efforts in #820 and #812. I'm closing this issue, but of course additional performance improvement PRs are always welcome.
Most helpful comment
It would be helpful to know the feature(s) involved when performance is an issue – you can identify this by disabling animation, materials, etc. and comparing the export time. Currently we've identified bottlenecks in:
There may be others.
Textures were/are likely the main issue here, and @julienduroure has submitted a significant improvement there. And as @emackey mentions above, the exporter really should avoid re-compressing texture data unless it's necessary.
@wcbx I understand your frustration with the issue, but please keep in mind that this is not affecting all users, and feedback of the form "This exporter was not stable enough for release" is neither constructive nor productive in finding a solution. You've commented in a couple places that you're unhappy with performance, but haven't (as far as I can tell) provided an example that we can debug.