Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DaggerfallBillboardBatch: mesh data creation process multi-threaded #2427

Conversation

andrew-raphael-lukasik
Copy link
Contributor

@andrew-raphael-lukasik andrew-raphael-lukasik commented Sep 12, 2022

Hi! This PR is part of #2416 saga.

What

I refactored #2416 changes to the DaggerfallBillboardBatch.cs to make them more conservative, safe and limited to a single file. It delivers less benefits than original, but will serve as a good foundation for future improvements which has been identified so far.

I know you guys have other priorities now so no worries - take your time. I am posting this now while my mind is still familiar with this code.

Results

  • CreateMesh takes less time from the main thread. Before: about 266 ms (left), after: about 34 [s] (right):

  • AddItemsAsync were added to open the door for future decrease in the overhead from the thousands of separate AddItem calls (about an 100 ms, sometimes more)

  • ProfilerMarker introduced, to make profiling much more granular and informative. My controversial proposal here is in it's naming convention ie. ____PascalCase (for methods) and ____camelCase (for generic markers). I tried different schemes and this was my conclusion as most consistent and readable, but you might have different conclusions. Prefix ___ especially helps with readability as it makes these markers * immediately * apparent and distinguishable from the rest of the code mid-all these busy methods.

@Interkarma
Copy link
Owner

Hey, thanks for the isolated update. Much easier to review. :)

Just on a first pass test, I did get an exception (travelling to Daggerfall City).

image

@andrew-raphael-lukasik
Copy link
Contributor Author

andrew-raphael-lukasik commented Sep 12, 2022

Just on a first pass test, I did get an exception (travelling to Daggerfall City).

Thank you for catching that! 121a594 should resolve it

@BadLuckBurt
Copy link
Contributor

Hi again, I happen to be working on some stuff that involves using a custom atlas and billboard batches. Since I'm definitely feeling the performance hits there, I decided to give this new class a go and ran into a minor issue:

The CreateMeshForCustomMaterial method errors out because it tries to read the cachedMaterial property. That property is only populated by the regular SetMaterial call that deals with Daggerfall's data, the SetMaterial call that handles the custom atlas does not attempt to do anything with it and there is no way to access cachedMaterial at the moment because of it's protection level.

The mod I'm working on caches the atlases it creates so I was able to work around this by making the cachedMaterial property public and just setting it directly from my code (very bad Burt, I know). The SetMaterial call for custom atlases probably needs a CachedMaterial parameter so that mod authors can supply it properly.

However this change fixed the error so I was able to test further.

I can definitely see the performance increase, it feels so much smoother when moving between map pixels now even with two billboard batches per terrain.

Would love to see this get adopted into the core.

@andrew-raphael-lukasik
Copy link
Contributor Author

The mod I'm working on caches the atlases it creates so I was able to work around this by making the cachedMaterial property public and just setting it directly from my code (very bad Burt, I know). The SetMaterial call for custom atlases probably needs a CachedMaterial parameter so that mod authors can supply it properly.

I think you should write a PR that proposes exactly what you need here. Especially since you can immediately field test it

@BadLuckBurt
Copy link
Contributor

I think you should write a PR that proposes exactly what you need here. Especially since you can immediately field test it

Apologies for the late reply, I've been rather busy but I agree, I'll do a PR for this. I came to realise that passing the cached material as the only parameter (instead of the material) is a cleaner way of doing it since the CachedMaterial already contains the material itself so it can be read from there.

Since you've made the changes in this class standalone, I took the liberty of integrating it into my nature layout mod and the performance increase is mighty impressive. Instead of dealing with one billboard batch, I'm generating three batches at the same time, one for nature objects and the other two are used for billboard grass sprites. With the original billboard batches, this obviously caused a big hit in performance but with your class, it's reduced to a minor hickup when crossing map pixels.

I haven't released or uploaded this version to my repository yet as it was just a test and your code is not mine to distribute but I would like to ask your permission to release it with your class included.

@andrew-raphael-lukasik
Copy link
Contributor Author

I haven't released or uploaded this version to my repository yet as it was just a test and your code is not mine to distribute but I would like to ask your permission to release it with your class included.

Do it, it's yours now : )

ps: afaict Burst does not works for code that is part of a mod (different compilation pipeline), so no wins there.

@BadLuckBurt
Copy link
Contributor

Do it, it's yours now : )

Thank you, that's very kind. I'm currently working on the grass textures for the climates but once those are done I should be ready to release this version. I'll make sure to keep you posted.

ps: afaict Burst does not works for code that is part of a mod (different compilation pipeline), so no wins there.

I don't know about Burst but even without, the gain is substantial because of the multi-threading that occurs now and that's enough of a win for me :)

@Interkarma
Copy link
Owner

I'll roll this one in for preview testing in next release. Well done @andrew-raphael-lukasik. :)

@Interkarma Interkarma merged commit a40c507 into Interkarma:master Oct 2, 2022
@Interkarma
Copy link
Owner

Hey! I'm encountering a native array exception when loading some interior saves in current master after merging this PR. Stack trace and repro save below.

InvalidOperationException: The NativeArray has been deallocated, it is not allowed to access it Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion (Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle handle) (at <ca42919df2b74e53b11002749c8755af>:0) Unity.Collections.NativeList`1[T].Clear () (at Library/PackageCache/com.unity.collections@0.9.0-preview.6/Unity.Collections/NativeList.cs:417) DaggerfallWorkshop.DaggerfallBillboardBatch.Clear () (at Assets/Scripts/Internal/DaggerfallBillboardBatch.cs:358) DaggerfallWorkshop.DefaultTerrainNature.LayoutNature (DaggerfallWorkshop.DaggerfallTerrain dfTerrain, DaggerfallWorkshop.DaggerfallBillboardBatch dfBillboardBatch, System.Single terrainScale, System.Int32 terrainDist) (at Assets/Scripts/Terrain/TerrainNature.cs:97) DaggerfallWorkshop.StreamingWorld.UpdateTerrainNature (DaggerfallWorkshop.StreamingWorld+TerrainDesc terrainDesc) (at Assets/Scripts/Terrain/StreamingWorld.cs:1271) DaggerfallWorkshop.StreamingWorld.InitPlayerTerrain () (at Assets/Scripts/Terrain/StreamingWorld.cs:632) DaggerfallWorkshop.StreamingWorld.Update () (at Assets/Scripts/Terrain/StreamingWorld.cs:252)

SAVE405.zip

I'm getting close to next release. Let me know if you don't think this is an easy solve, and I'll revert PR changes for now.

Cheers! :)

@Interkarma
Copy link
Owner

Hit another exception after fast travelling.

InvalidOperationException: The previously scheduled job DaggerfallBillboardBatch:AnimateUVJob writes to the NativeArray AnimateUVJob.uv. You must call JobHandle.Complete() on the job DaggerfallBillboardBatch:AnimateUVJob, before you can read from the NativeArray safely. Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.CheckReadAndThrowNoEarlyOut (Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle handle) (at <ca42919df2b74e53b11002749c8755af>:0) Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.CheckReadAndThrow (Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle handle) (at <ca42919df2b74e53b11002749c8755af>:0) Unity.Collections.LowLevel.Unsafe.NativeArrayUnsafeUtility.GetUnsafeReadOnlyPtr[T] (Unity.Collections.NativeArray`1[T] nativeArray) (at <ca42919df2b74e53b11002749c8755af>:0) UnityEngine.Mesh.SetUVs[T] (System.Int32 channel, Unity.Collections.NativeArray`1[T] uvs, System.Int32 start, System.Int32 length) (at <ca42919df2b74e53b11002749c8755af>:0) UnityEngine.Mesh.SetUVs[T] (System.Int32 channel, Unity.Collections.NativeArray`1[T] uvs) (at <ca42919df2b74e53b11002749c8755af>:0) DaggerfallWorkshop.DaggerfallBillboardBatch.PushNewMeshData () (at Assets/Scripts/Internal/DaggerfallBillboardBatch.cs:901)

@Interkarma
Copy link
Owner

The exception when loading interior saves can also cause towns not to load.

image

I'll revert this one for now, and we can approach again later.

@andrew-raphael-lukasik
Copy link
Contributor Author

andrew-raphael-lukasik commented Oct 2, 2022

Goddamyt, it's hard to catch all of these. But fixes are very straigth-forward once there is a call-stack:

2e3f2f7 fixes

InvalidOperationException: The NativeArray has been deallocated, it is not allowed to access it Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion (Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle handle) (at <ca42919df2b74e53b11002749c8755af>:0) Unity.Collections.NativeList`1[T].Clear () (at Library/PackageCache/com.unity.collections@0.9.0-preview.6/Unity.Collections/NativeList.cs:417) DaggerfallWorkshop.DaggerfallBillboardBatch.Clear () (at Assets/Scripts/Internal/DaggerfallBillboardBatch.cs:358) DaggerfallWorkshop.DefaultTerrainNature.LayoutNature (DaggerfallWorkshop.DaggerfallTerrain dfTerrain, DaggerfallWorkshop.DaggerfallBillboardBatch dfBillboardBatch, System.Single terrainScale, System.Int32 terrainDist) (at Assets/Scripts/Terrain/TerrainNature.cs:97) DaggerfallWorkshop.StreamingWorld.UpdateTerrainNature (DaggerfallWorkshop.StreamingWorld+TerrainDesc terrainDesc) (at Assets/Scripts/Terrain/StreamingWorld.cs:1271) DaggerfallWorkshop.StreamingWorld.InitPlayerTerrain () (at Assets/Scripts/Terrain/StreamingWorld.cs:632) DaggerfallWorkshop.StreamingWorld.Update () (at Assets/Scripts/Terrain/StreamingWorld.cs:252)

4723886 fixes

InvalidOperationException: The previously scheduled job DaggerfallBillboardBatch:AnimateUVJob writes to the NativeArray AnimateUVJob.uv. You must call JobHandle.Complete() on the job DaggerfallBillboardBatch:AnimateUVJob, before you can read from the NativeArray safely. Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.CheckReadAndThrowNoEarlyOut (Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle handle) (at <ca42919df2b74e53b11002749c8755af>:0) Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.CheckReadAndThrow (Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle handle) (at <ca42919df2b74e53b11002749c8755af>:0) Unity.Collections.LowLevel.Unsafe.NativeArrayUnsafeUtility.GetUnsafeReadOnlyPtr[T] (Unity.Collections.NativeArray`1[T] nativeArray) (at <ca42919df2b74e53b11002749c8755af>:0) UnityEngine.Mesh.SetUVs[T] (System.Int32 channel, Unity.Collections.NativeArray`1[T] uvs, System.Int32 start, System.Int32 length) (at <ca42919df2b74e53b11002749c8755af>:0) UnityEngine.Mesh.SetUVs[T] (System.Int32 channel, Unity.Collections.NativeArray`1[T] uvs) (at <ca42919df2b74e53b11002749c8755af>:0) DaggerfallWorkshop.DaggerfallBillboardBatch.PushNewMeshData () (at Assets/Scripts/Internal/DaggerfallBillboardBatch.cs:901)

Can this PR be reopened somehow? Ok, a new PR is up.

@petchema
Copy link
Collaborator

petchema commented Feb 10, 2023

King Of Worms (@HybOj) noticed some regression with nature billboard shadows,

https://forums.dfworkshop.net/viewtopic.php?t=6103

Git bisect pointed at commit 84e155a which is part of this PR

@Interkarma
Copy link
Owner

Interkarma commented Feb 10, 2023

Most likely mesh/object is no longer using double-sided shadow casting (i.e. backface of quad casts no shadows). This is something I fixed in my original implementation. Not sure if it's still possible using new implementation yet, I'll need to allocate time to look at this.

Shadows on nature flats are essentially deprecated since 0.13 however. I left option in settings.ini, but it's not something I see a lot of need to support. If not possible to patch this, then I'll just remove option entirely.

@Interkarma
Copy link
Owner

@petchema Actually, looks like you fixed this same problem back in 2017 in my original implementation. Same correction might work here. :)
https://forums.dfworkshop.net/viewtopic.php?t=653&start=10

@petchema
Copy link
Collaborator

Yes, #1071
Bug this new bug looks different; As you look around the shadows appear or disappear, sometimes partially with only the most distant chunks of shadows missing. Maybe a video would be more obvious than a description, if you haven't tried KoW game save yet.

I must admit I have no idea what's going on and how this commit can affect shadows rendering; But that's a large commit full of Unity magic ;)

@Interkarma
Copy link
Owner

Hmm, could be normal related then. I'll really need to unpack it again. Agree so much of new method is pure memory management voodoo.

@HybOj
Copy link

HybOj commented Feb 11, 2023

Guys, please dont scratch the Nature Shadows. I really hope this can be solved in a manner the functionality is kept in the game. Maybe Andrew can look into this as well, as he understands his code and its implications. Removing the nature shadows completely would be a big mistake. Thanks a lot, fingers crossed.

@Interkarma
Copy link
Owner

I'm had a look at KoW's save on forums. At times you can see a spherical radius at the boundary where shadow casting is culled. Something about the mesh data must not be right, but interesting it only seems to affect shadow casting, not mesh visibility or lighting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants