DrawData recalc fixes/improvements #114112
Labels
No Label
Interest
Alembic
Interest
Animation & Rigging
Interest
Asset Browser
Interest
Asset Browser Project Overview
Interest
Audio
Interest
Automated Testing
Interest
Blender Asset Bundle
Interest
BlendFile
Interest
Collada
Interest
Compatibility
Interest
Compositing
Interest
Core
Interest
Cycles
Interest
Dependency Graph
Interest
Development Management
Interest
EEVEE
Interest
EEVEE & Viewport
Interest
Freestyle
Interest
Geometry Nodes
Interest
Grease Pencil
Interest
ID Management
Interest
Images & Movies
Interest
Import Export
Interest
Line Art
Interest
Masking
Interest
Metal
Interest
Modeling
Interest
Modifiers
Interest
Motion Tracking
Interest
Nodes & Physics
Interest
OpenGL
Interest
Overlay
Interest
Overrides
Interest
Performance
Interest
Physics
Interest
Pipeline, Assets & IO
Interest
Platforms, Builds & Tests
Interest
Python API
Interest
Render & Cycles
Interest
Render Pipeline
Interest
Sculpt, Paint & Texture
Interest
Text Editor
Interest
Translations
Interest
Triaging
Interest
Undo
Interest
USD
Interest
User Interface
Interest
UV Editing
Interest
VFX & Video
Interest
Video Sequencer
Interest
Virtual Reality
Interest
Vulkan
Interest
Wayland
Interest
Workbench
Interest: X11
Legacy
Blender 2.8 Project
Legacy
Milestone 1: Basic, Local Asset Browser
Legacy
OpenGL Error
Meta
Good First Issue
Meta
Papercut
Meta
Retrospective
Meta
Security
Module
Animation & Rigging
Module
Core
Module
Development Management
Module
EEVEE & Viewport
Module
Grease Pencil
Module
Modeling
Module
Nodes & Physics
Module
Pipeline, Assets & IO
Module
Platforms, Builds & Tests
Module
Python API
Module
Render & Cycles
Module
Sculpt, Paint & Texture
Module
Triaging
Module
User Interface
Module
VFX & Video
Platform
FreeBSD
Platform
Linux
Platform
macOS
Platform
Windows
Priority
High
Priority
Low
Priority
Normal
Priority
Unbreak Now!
Status
Archived
Status
Confirmed
Status
Duplicate
Status
Needs Info from Developers
Status
Needs Information from User
Status
Needs Triage
Status
Resolved
Type
Bug
Type
Design
Type
Known Issue
Type
Patch
Type
Report
Type
To Do
No Milestone
No project
No Assignees
4 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: blender/blender#114112
Loading…
Reference in New Issue
No description provided.
Delete Branch "%!s(<nil>)"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Draw Engines don't use the main
ID::recalc
flag (the flag is actually already cleared by the time rendering happens), they use a copy stored in theDrawData
instead. This flag is filled inflush_engine_data_update
and it's up to the engines to clear it when the update has been handled.EEVEE-Next relies on the
DrawData::recalc
flags to detect the shadow-map sections that need to be updated.However, there's an issue with the current system that prevents it from working correctly.
Since
DrawData
is stored perDrawEngineType
, and not per engine instance. The first instance that process the flags will clear them, causing the rest of the instances to not update correctly:A partial solution could be iterating all
DrawData
after all engine instances have been updated and clearing the flags then.However, not all viewports are always updated, the main (maybe only?) exception being animation playback on a single viewport.
A full solution could be storing
DrawData
per engine instance instead, butDrawData
as it is right now is not that optimal performance-wise and this would make it worse. This would also require iterating all IDs on instance deletion to remove its associatedDrawData
.Since the problem is the
recalc
flag itself (the only part required byNext
engines), and not the fullDrawData
system.I propose a simpler solution:
recalc
flag that can be shared by all engines/instances to theIdDdtTemplate
.tick
counter (ie. a counter that increments everyWM_main
loop).last_updated
property to theIdDdtTemplate
.flush_engine_data_update
writes therecalc
, store also the current tick inlast_updated
(therecalc
flag is never cleared).tick
they were updated. When instances sync anID
they check theirlast_update
:instance.last_update >= id.last_update
, thenrecalc
is 0.instance.last_update + 1 == id.last_update
, thenrecalc
isIdDdtTemplate.recalc
.instance.last_update + 1 < id.last_update
, thenrecalc
isID_RECALC_ALL
since the instance lost track of the recalc updates.Making the render engine dependent on a tick counter from the main even loop does not sound like a good idea to me. Headless (
blender --background
) renders do not have main loop running. The F12 renders do have main loop running, but it is on a different thread. And in both cases the system should be allowing to avoid full updates when next frame is rendered.Even within a case when you have 2 viewports, ideally you should be able to avoid recalculating/updating everything when you full-screen one viewport, do some modification, and exit the full-screen of that viewport.
The proposal does not cover is the what happens on rollover of the counter.
What I am not sure about, is why can't render-instance-specific data be explicitly stored in a per-render-instance-specific storage? Like, assign a
session_uid
to render engine, and use those to access render-instance-speicfic data (similarly to how the similar mechanism is used withsession_uuid
on the ID level).P.S. saying that
recalc
isID_RECALC_ALL
is not correct. TheID_RECALC_ALL
can only be used in a context ofid.recalc & ID_RECALC_ALL
to see if SOMETHING has changed. Nowadays we can/should get rid of this flag and doid.recalc
for that purposes.The main loop idea may be naive thinking on my side, I don't have the full picture of how the whole Depsgraph system works.
Couldn't we just have separate main counter/recalcs and render counter/recalcs?
While it would be great to avoid any redundant recalculations, I would prioritize performance on interaction and playback, rather than one-time context switches.
I think this would be similar to what I mention here:
Another issue is that, ideally, the recalc flags should be cleared after the whole instance is fully updated.
So, IMO, this would result in a much more complex system, that is slower overall (due to hashmaps lookups and crossed dependencies) except for the special case of one-time context switches.
Even assuming a simple uint32 counter and constant 240 fps updates, that's 207 days running Blender non-stop without it becoming an issue. If that's actually a problem, we can use a uint64.
That's what we use in EEVEE Next:
https://projects.blender.org/blender/blender/src/branch/main/source/blender/draw/engines/eevee_next/eevee_sync.cc#L40
Maybe we should replace it with an EEVEE specific flag that tags only what we care about?
To me it seems reasonable to use ticks for the draw code specifically.
For the depsgraph in general it would be harder because there are many different
ID_RECALC
flags and you would need a separate tick counter for each, for the updates to be fine grained enough. But for the draw code it seems like a nice simple solution to me. If you need to distinguish between different flags in the future I guess there would not be that many.Assuming the draw manager data is all per-depsgraph, storing it on the depsgraph sounds right to me. I don't think having it global is right since there can be multiple depsgraphs alive at the same time, and there are depsgraphs that are completely separate like the one for preview renders.
I guess a global atomic variable that you increment in
deg_graph_flush_updates
would work too, but why have it be global if it can be local per depsgraph.I've actually just edited my answer (probably while you were writing yours, sorry) to remove the part about storing it on the Depsgraph since the counter itself is not enough, it would also need recalc flags per ID and per Depsgraph, and then you have the same problem of needing some kind of hashmap/list per ID.
Maybe that's still ok, though?
In any case, yes, this would only apply to separate recalc flags (only used for drawing), not to the main ones.
Counter of a dependency graph level is not easier to maintain and check. It will break the assumption that there is only single update between two redraws. That is not the case, even with a single viewport which is always visible: you can have a Python handler which triggers an extra depsgraph evaluation. It also would mean that render engine instances would need to react to change of the active scene and view layer. So overall I do not see such counter be reliable or clear solution.
I do agree that the
DrawData
is not very well designed currently. There were some pivots from the original ideas made, and as per nowadays, it will be more optimal to replace list wit ha struct of per-engine-type pointers. This should reduce the overall size by avoidingnext
/prev
pointers, and will make access to engine-type-specific dataO(1)
. "Back-pointers" should also be avoided. There is no good reason to store ID and animation data permanently in the draw data.For finding good solution to the problem described here I'd need to know more details.
For example, I do hope that the actual content of the
DrawData
is independent from the engine-instance (otherwise two instances will constantly fight for the truthful state of the data). But if that's the case, why does it matter if theDrawData
was calculated by another engine instance, or was calculated by the same engine at a previews redraw?Or, why can't it be handled from
editor_update_cb
which knows all engines active in the viewport and can decide that re-render is needed because the callback received an object with a modified geometry?If you operate on IDs which come from the depsgraph you naturally get
id->recalc
be per-depsgraph. The id gets copied to every depsgraph it is part of, and is potentially evaluated to different states.We could increment the counter only once the updates are actually consumed by the Draw module.
AFAIK, a change of active scene/layer will trigger the delete/creation of engine instances anyway.
EEVEE Next doesn't really store anything in DrawData, it only needs the recalc flags.
I actually don't think we need
DrawData
at all in any of the "new" engines, we only need the recalc flags.As for context, the recalc data right now is used for:
editor_update_cb
, as you pointed out.This is all tracked and handled per instance, that's why we need reliable recalc updates for each instance.
There's no shared engine-specific data between instances (except for Volume Probes, but that still doesn't use DrawData).
Oh, I wasn't counting on that. That makes things easier.
I don't think there is such an assumption? The way I imagine it to work is:
That would be enough to know if the ID has been updated, but not to be sure of what has been updated:
From the main post:
Maybe we could just store a different
last_update
property for each relevant flag (I think we only would needID_RECALC_TRANSFORM
,ID_RECALC_GEOMETRY
andID_RECALC_SHADING
). We useID_RECALC_COPY_ON_WRITE
too, but that could simply update all of them.That would allow it to work the way @brecht suggests.
Then you need a separate counter per flag right?
last_update_transform
,last_update_geometry
, etc.I guess we wrote the same thing at the same time. 😅
Yes, I think that would work.
Is it something that can be implemented via the
ED_render_id_flush_update
andED_render_scene_update
? Would be nice if the depsgrapgh did not need to worry about draw-manager specific design.P.S. The
ID_RECALC_COPY_ON_WRITE
is another internal-ish flag. In a lot of cases where it is used it should not. So I'd like to know details behind this.Looking at it, it doesn't seem like it has any kind of per-object granularity?
If that's the case, then I don't think so.
Ideally, especially for shadow maps, we need to know exactly which objects have updated their transform/geometry/shading (including any kind of instance).
Also, keep in mind that we need some kind of solution for 4.1, the further we depart from the already existing solution, the riskier it gets.
We just check for it to reset sampling.
ED_render_id_flush_update
is called for every ID which received (possibly indirectly) recalc flags. What happens deeper in there I am not currently sure. From a quick glance it seems strange the things that happen there, and does not seem to be integrated with draw manager. Maybe we should make such integration? So that the draw manager can handle current logic fromflush_engine_data_update
there as well.What case does the
ID_RECALC_COPY_ON_WRITE
cover which is not covered withID_RECALC_TRANSFORM | ID_RECALC_SHADING | ID_RECALC_GEOMETRY
?A few precisions:
DrawData
eventually since we store all engine specific data per render engine instance now.ID_RECALC_COPY_ON_WRITE
was needed for properties update on scene, world and the like (maybe the tagging itself is wrong).I haven't a clear picture of the whole thing, but what I can say, is that we should strive to not keep it delegated to the draw manager.
Logic specific to the behavior and implementation the draw manager without intent and clear design of other users of the logic should be local to the draw manager. We should not have draw manager specific or Eevee specific logic in dependency graph. And we should not not have multiple mechanisms of informing users of the fact that scene has been re-evaluated, or that some IDs goe new recalc flags.
Edit. If scene's property affects shading/rendering using
ID_RECALC_COPY_ON_WRITE
for its tag is indeed incorrect.Talked to Clément. Here is the summary of what we've discussed:
deg_evaluate_on_refresh()
prior to theinitialize_execution()
.last_update_{transform, geometry, shading}
on Id runtime. Could be specific for Objects and Lamps, and not to other ID types.BKE_object_eval_transform_final()
witll assignobject->runtime.last_update_transform = DEG_get_current_tick(depsgraph);
.This allows to have enough level of granularity, which potentially will be even more granular than what the depsgraph can provide. For example,
BKE_object_handle_data_update
can go an extra step and keep track of topology vs. coordinates only changes.Making this work reliably will probably mean that we'll need to fix some things in the topology of the depsgraph, to ensure proper dependencies. Also, would need to use more proper flags for properties updates like DoF on camera. Doing those fixes and changes will be very welcome, and will open possibilities for further optimizations. This is how we've ended up the next part of discussion, which is not directly related to this proposal, but which seems to be very relevant to simplify and speed up things in draw manager.
Basically, two things which do not take much changes on the depsgraph level:
id_type_updated
available after the scene evaluation, so that render engines in the viewport can access it. Seems easy to do by movingDEG_ids_clear_recalc(depsgraph, false);
to the beginning of thescene_graph_update_tagged
did_anything_change
flag in the depsgraph, which is basically true iff any of the elements of theid_type_updated
is true. This can be done bygraph->is_anything_changed = !graph->entry_tags.is_empty();
in the beginning of thedeg_evaluate_on_refresh
.This should allow shortcuts like (only demonstration purposes, exact phrasing of the code is different):
This should allow to avoid things like
assign_if_different
and react to changes more directly. And will avoid need of looping over all possible IDs in the scene to detect changes.If that's what you decided, fine then.
But that sounds way more complex than just updating
flush_engine_data_update
.I'd like to understand the advantages of this approach.
The "topology vs. coordinates only changes" seems to have more to do with the
BKE_MESH_BATCH_DIRTY
flags than what we are discussing here?We need Materials too. Material/Nodes changes don't tag any Object recalc flag.
About the
id_type_updated
anddid_anything_change
, doesn't this have the same issue of assuming there has been a single Depsgraph updates between 2 redraws?There are few advantages:
flush_engine_data_update
should not be happening from the dependency graph code.DrawData
is planned to be removed, which would also mean theflush_engine_data_update
could be removed, leaving dependency graph doing the least amount of non-general code.if (is_object) { if (recalc & RECALC_GEOMTRY) object.last_geoemtry_update = foo; .... } if (material) { ... }
type of chain.Long story short: saying "object knows what renderer needs to draw" is shorter than "dependency graph knows what renderer needs to draw object".
See my comment about changes to the topology in the graph.
Render engine does not handle materials floating in the air, or affecting invisible objects or used in drivers. So knowing that material changed is not enough, as you'll need to figure out what changed material affected on. If there is relation coming from material to object then this check becomes easier to do.
Nothing prevents of having last_updated flag on material as well. With the proposal here it is all in control of the material itself.
Those flags are accumulated between those updates. The
id_type_updated
is how Cycles checks for updates.I've implemented the Depsgraph part following (I think) @Sergey design. #115196
I'd like to do the EEVEE side implementation before review (even if that goes on a separate PR).
But I would still appreciate some early feedback, since this is the first time I look at this part of the code
and I wouldn't be surprised if I got something super wrong.