Compare commits

..

305 Commits

Author SHA1 Message Date
6fc81d6bca Merge branch 'master' into xr-controller-support 2021-10-03 12:22:05 +09:00
85e1f28fca Fix controller model loading for no stb image 2021-10-03 12:16:07 +09:00
c4f65fa5da Cleanup
- Use doxygen style comments
- Improve comment for XR region type
- Improve RNA descriptions for XR nav location/rotation/scale
- Split controller draw callback into separate model and aim functions
2021-10-03 12:11:10 +09:00
d3afe0c126 Geometry Nodes: Resample Curve Fields Update
This update of the Resample Curve node allows a field to populate the
count or length input of the node depending on the current mode. The
field is evaluated on the spline domain.

Differential Revision: https://developer.blender.org/D12735
2021-10-02 21:45:51 -05:00
e863e05697 Data Transfer: Remove unnecessary noisy error message
I've seen requests to remove this or complaints about this error message
quite frequently. In lots of production files it's just always going off.
It's not an actionable warning, and since "slow" is relative, it isn't
always even correct.

Differential Revision: https://developer.blender.org/D12694
2021-10-02 20:33:15 -05:00
b57b4dfab1 Cleanup: clang-format 2021-10-03 12:13:29 +11:00
f49dff97d4 Cleanup: spelling in strings 2021-10-03 12:13:29 +11:00
74f45ed9c5 Cleanup: spelling in comments 2021-10-03 12:13:29 +11:00
Jarrett Johnson
c5c94e3eae Geometry Nodes: Add Rotate Euler Node
This commit introduces the Rotate Euler function node which modifies
an input euler rotation. The node replaces the "Point Rotate" node.

Addresses T91375.

Differential Revision: https://developer.blender.org/D12531
2021-10-02 20:04:45 -05:00
34cf33eb12 Geometry Nodes: Switch Node Fields Update
This update of the Switch node allows for field compatible types
to be switched through the node. This includes the following:

Float, Int, Bool, String, Vector, and Color

The remaining types are processed with the orginal code:

Geometry, Object, Collection, Texture, and Material

Because the old types require a diffent "switch" socket than the
field types, versioning for old files is included to move links
of those types to a new switch socket. Once fields of other types
are supported, this node can be updated to support them as well.

Differential Revision: https://developer.blender.org/D12642
2021-10-02 17:34:47 -05:00
Leon Leno
54927caf4f Geometry Nodes: Add side and fill segments to Cone/Cylinder nodes
This commit extends the 'Cone' and 'Cylinder' mesh primitive nodes,
with two inputs to control the segments along the side and in the fill.
This makes the nodes more flexible and brings them more in line with
the improved cube node.

Differential Revision: https://developer.blender.org/D12463
2021-10-02 17:29:25 -05:00
12e8c78353 Fix T91888: Pivot point settings shown in timeline
Added to timeline by accident in f9e0981976.
2021-10-01 23:26:59 +02:00
1476d35870 Geometry Nodes: Set Handle Type Node Field Update
This update of the Set Handle Type node allows for a bool field to be
used as the selection of the affected control point handles for
bezier splines. If no bezier splines are provided a info message is
shown.

Differential Revision: https://developer.blender.org/D12526
2021-10-01 14:22:24 -05:00
1fb364491b Geometry Nodes: Set Spline Type Node Field Update
This update of the Set Spline Type node allows for a bool field to be
used as the selection of the affected splines.

Differential Revision: https://developer.blender.org/D12522
2021-10-01 11:59:29 -05:00
eacdc0ab4a VSE: Draw active strips with a different color in the preview window 2021-10-01 18:03:18 +02:00
dc30a9087c Geometry Nodes: Spline Length Input Node
The Spline Length Input node provides a field containing the
length of the current evaluated spline to the Point and Spline
domains.

Differential Revision: https://developer.blender.org/D12706
2021-10-01 09:58:49 -05:00
9e456ca695 Asset Browser: expose current catalog ID in RNA
Add read-only access to the active catalog ID via
`context.space_data.params.catalog_id` in the Asset Browser context.

The UUID is exposed as string to Python.
2021-10-01 16:47:16 +02:00
aae96176e8 Geometry Nodes: Curve Subdivide Node with Fields
The curve subdivide node can now take an int field to specify the
number of subdivisions to make at each curve segment.

Reviewed by: Hans Goudey
Differential Revision: https://developer.blender.org/D12534
2021-10-01 09:43:04 -05:00
e1952c541a Cleanup: unused function declaration
This should have been removed during the recent velocity attribute
refactor.
2021-10-01 16:41:50 +02:00
Johan Walles
fb820496f5 Tracking: Sort motion tracking tracks by start and end frames
Enable sorting motion tracking tracks by start / end times.

Help identifying what cases reconstructed camera jumps, based
on information about whether any track starts/ends at the frame.

Based on revision eb0eb54d96.

{F10563305}

Reviewed By: sergey

Differential Revision: https://developer.blender.org/D12621
2021-10-01 16:29:38 +02:00
f497e471f8 Asset Catalogs: always have an Asset Catalog Tree available
Always create an `AssetCatalogTree` in the `AssetCatalogService`. This
ensures that newly-created catalogs are immediately visible in the UI
(because they insert themselves into an already-existing tree).
2021-10-01 16:15:50 +02:00
3be4cb5b27 Cleanup: Asset Catalog Test, fix clang-tidy warnings
No functional changes.
2021-10-01 15:24:04 +02:00
56ce51d1f7 Asset Catalogs: add catalog filter for the asset browser
Given an "active catalog" (i.e. the one selected in the UI), construct
an `AssetCatalogFilter` instance. This filter can determine whether an
asset should be shown or not. It returns `true` when The asset's catalog
ID is:

- the active catalog,
- an alias of the active catalog (so different UUID that maps to the
  same path),
- a sub-catalog of the active catalog.

Not yet hooked up to the UI.
2021-10-01 15:22:05 +02:00
1c7ce7e0b4 Cleanup: asset catalogs, make function const
Declare `AssetCatalogService::find_catalog()` as `const`, as it's not
requiring modification off the service object.

No functional changes.
2021-10-01 15:22:05 +02:00
271210126e Fix T91834: Appending objects with shape keys into new file is broken.
Recent append refactor 'broke' this, we need special recursive care and
handling of those nasty shpae keys... again.
2021-10-01 14:49:04 +02:00
f9acf21063 Python API Docs: add an example of Bone.convert_local_to_pose usage.
The use case for this method is quite obscure and difficult to
understand without an example. Despite how big looks, this is
actually the simplest example that makes sense.
2021-10-01 15:47:30 +03:00
bdc66c9569 GPU: set 'GL_PACK_ALIGNMENT' 1 as default
This fixes T91828.

The current value of `GL_PACK_ALIGNMENT` may result in crash in the `gpu` module if the buffer is not aligned.

Differential Revision: https://developer.blender.org/D12720
2021-10-01 09:11:37 -03:00
eb3a8fb4e8 Fix T91872: incorrect socket inspection on group nodes
This bug was introduced in rBef45399f3be0955ba8.
2021-10-01 13:21:03 +02:00
798e593002 Fix T87189: Copy/pasting IDs does not handle properly instantiation.
Copy/Paste uses its own code path for ID linking, which was not setting
`LIB_TAG_DOIT` for proper instantiation later on.

Would be nice the make this logic closer to the rest of the link/append
code at some point, but for now this fix will do.
2021-10-01 12:21:08 +02:00
21c29480c3 Fix paste code linking 'direct' IDs with 'INDIRECT' flag.
No idea why this was done that way (it originate from initial paste
commit, rB12b642062c6f).

But the IDs 'selected' as direct paste in `BLO_library_link_copypaste`
should be 'directly' linked, it's similar case to actual append of
selected IDs by the user.

Related to T87189.
2021-10-01 12:21:08 +02:00
928d644895 Append: Fix appended objects potentially auto-instantiated in more than one collection.
Related to T87189.
2021-10-01 12:21:08 +02:00
af0b7925db Fix T87536: incorrect socket types in reroute nodes
This refactors and fixes the code that propagates socket types
through reroute nodes. In my tests it is faster than the previous
code. The difference becomes larger the more reroute nodes
there are, because the old code had O(n^2) runtime, while the
new code runs in linear time.

Differential Revision: https://developer.blender.org/D12716
2021-10-01 11:42:00 +02:00
7843cd63d8 Fix T91839: incorrect active vertex group index
Differential Revision: https://developer.blender.org/D12712
2021-10-01 11:36:10 +02:00
ae4b45145c Cleanup: Asset Catalog Paths, move default constructor to header file
No functional changes.
2021-10-01 10:58:33 +02:00
2e6c6426d3 Asset Catalogs: test that missing catalogs are created once
Add asset catalogs test, to ensure missing catalogs are only created once,
and not for every originally defined catalog.

No functional changes to Blender (the code was already doing the right
thing).
2021-10-01 10:58:16 +02:00
bdb7d262aa Cleanup: clang-tidy warnings 2021-10-01 16:20:31 +10:00
b559fb178e Gizmo: hide 2D gizmos while transforming
Hide gizmos in the sequencer & UV editor while transforming.
2021-10-01 16:19:12 +10:00
4485dc483c Cleanup: use C-style comments, nullptr for C++
Minor changes extracted from D6408
2021-10-01 10:45:09 +10:00
Vitor Boschi
3a59ddb292 Fix: Incorrect warning in curve to mesh node with instances
The node was setting a warning when used with instances on input,
even though it worked fine.

Differential Revision: https://developer.blender.org/D12718
2021-09-30 18:27:58 -05:00
66fe1c79f3 Compositor: Fix Composite node using alpha when "Use Alpha" is off
Alpha input was not receiving the final composite canvas 
as preferred causing a Translate operation being inserted 
for centering. This resulted in a transparent background.
The issue only affects Full Frame mode.
2021-09-30 23:56:53 +02:00
e2df5c8a56 Compositor: Fix Flip node not flipping translation on Full Frame
To match tiled implementation, flip center should not be translated
when canvas has offset. Instead the canvas offset needs to be flipped.
2021-09-30 23:56:53 +02:00
f3274bfa70 Compositor: Fix Dilate/Erode node crash with Step option
It was writing the buffer out of bounds.
Only "Full Frame" mode was affected.
2021-09-30 23:56:53 +02:00
4569d9c0c3 Compositor: Fix Movie Distortion node rendering an empty image
Input area of interest calculation was incorrect because `m_margin`
was uninitialized.
Only "Full Frame" mode was affected.
2021-09-30 23:10:27 +02:00
Pratik Borhade
33dc584b37 Fix T91285: Bad tooltip for VSE Slip operator
This patch is created to change the tooltip for Slip Strip Contents
As per the present info, only active strip will be affected.
But in reality selected strips can be trimmed with this operator.

Word Trim changed to Slip in tooltip

Reviewed By: campbellbarton

Differential Revision: https://developer.blender.org/D12450
2021-09-30 21:11:09 +02:00
Josef Raschen
213554f24a VSE: Add ASC CDL color correction method
Add Offset/Slope/Power controls to the color balance modifier. This is
already available in compositor.

Reviewed By: sergey, ISS

Differential Revision: https://developer.blender.org/D12575
2021-09-30 21:09:47 +02:00
8d60ac2bb0 Cleanup: Fix unused variable warning 2021-09-30 14:01:56 -05:00
Brecht Van Lommel
1a134c4c30 Cycles: refactor API for render output
* Add OutputDriver, replacing function callbacks in Session.
* Add PathTraceTile, replacing tile access methods in Session.
* Add more detailed comments about how this driver should be implemented.
* Add OIIOOutputDriver for Cycles standalone to output an image.

Differential Revision: https://developer.blender.org/D12627
2021-09-30 20:53:27 +02:00
a754e35198 Cycles: refactor API for GPU display
* Split GPUDisplay into two classes. PathTraceDisplay to implement the Cycles side,
  and DisplayDriver to implement the host application side. The DisplayDriver is now
  a fully abstract base class, embedded in the PathTraceDisplay.
* Move copy_pixels_to_texture implementation out of the host side into the Cycles side,
  since it can be implemented in terms of the texture buffer mapping.
* Move definition of DeviceGraphicsInteropDestination into display driver header, so
  that we do not need to expose private device headers in the public API.
* Add more detailed comments about how the DisplayDriver should be implemented.

The "driver" terminology might not be obvious, but is also used in other renderers.

Differential Revision: https://developer.blender.org/D12626
2021-09-30 20:48:08 +02:00
ac582056e2 Geometry Nodes: Swap order of geometry proximity inputs
"Target" is the most important, so it goes at the top.
2021-09-30 13:41:55 -05:00
Charlie Jolly
be70827e6f Nodes: Add Float Curve for GN and Shader nodes.
Replacement for float curve in legacy Attribute Curve Map node.

Float Curve defaults to [0.0-1.0] range.

Reviewed By: JacquesLucke, brecht

Differential Revision: https://developer.blender.org/D12683
2021-09-30 19:24:40 +01:00
827e30bd15 Geometry Nodes: Change default for mesh to points node
While "Vertices" may be less useful since mesh vertices are already
points, the output is more easily understandable, so it's a better
default.
2021-09-30 11:53:48 -05:00
6cff1d6480 Fix T91734: Crash snapping mesh if a beveled curve is present
`BKE_mesh_boundbox_get` cannot be called for objects of type Curve.

The BoundBox however does not match the object seen in the scene.
This will be dealt with in another commit.
2021-09-30 02:48:02 -03:00
dd3391dd99 Asset Catalogs: create missing parent catalogs
For every known catalog, ensure its parent catalog also exists. This
ensures that assets can be assigned to parent catalogs, even when they
didn't exist in the Catalog Definition File yet.
2021-09-30 17:34:58 +02:00
4389067929 Fix possible use-after-free in drag-drop handling logic
Would happen when there were multiple drag items in parallel. There was
a listbase constructed with twice the same item, even though that item
would be deleted after it was handled the first time.
2021-09-30 16:39:09 +02:00
4ee2d9df42 UI: Support easy dropping into/onto rows in new tree-view API
Adds an easy way to add drop support for tree-view rows.

Most of the work is handled by the tree-view UI code. The tree items can
simply override a few functions (`can_drop()`, `on_drop()`,
`drop_tooltip()`) to implement their custom drop behavior.

While dragging over a tree-view item that can be dropped into/onto, the
item can show a custom and dynamic tooltip explaining what's gonna
happen on drop.

This isn't used yet, but will soon be for asset catalogs.

See documentation here:
https://wiki.blender.org/wiki/Source/Interface/Views#Further_Customizations
2021-09-30 16:39:09 +02:00
42ce88f15c Cleanup: remove CatalogPath alias
The `CatalogPath` name was an alias for `std::string`, so that it could
be easily switched over to something else. This happened in the previous
commit (switched to `AssetCatalogPath`), so the alias is no longer
necessary.

This commit removes the `CatalogPath` alias.

No functional changes.
2021-09-30 16:34:30 +02:00
628fab696c Asset Catalog: introduce AssetCatalogPath class
So far we have used `std::string` for asset catalog paths. Some
operations are better described on a dedicated class for this, though.
This commits switches catalog paths from using `std::string` to a
dedicated `blender::bke::AssetCatalogPath` class.

The `using CatalogPath = AssetCatalogPath` alias is still there, and
will be removed in a following cleanup commit.

New `AssetCatalogPath` code reviewed by @severin in D12710.
2021-09-30 16:29:14 +02:00
5d42ea0369 GPencil: Change default template for better contrast in header
Patch created by Pablo Vazquez

This change darkens the header area a bit to create more contrast with the texts.

Differential Revision: https://developer.blender.org/D12711
2021-09-30 16:14:44 +02:00
d754d85845 Fix RigidBodyWorld copy using NO_MAIN instead of COW flag for cache handling.
We only want to share caches in case of CoW copying for the depsgraph,
not for regular `NO_MAIN` data.
2021-09-30 16:00:29 +02:00
1a72744ddc Fix T90246: Full Copy'ing a scene confuses physics in the original scene.
Handling of RigidBody data in duplicate of scenes/collections was very
wrong. This commit:
 - Add handling of duplication of RB collections when fully duplicating
   a scene.
 - Fix Object duplication trying to add duplicated RB objects to
   matching RBW collections.

While the later behavior is desired when only duplicated objects, when
duplicating their collections and/or scenes it is actually very bad, as
it would add back new object duplicates to old (RBW) collections.
2021-09-30 16:00:29 +02:00
779ea49af7 Cleanup: move node_common.c to c++
Buildbot compiled without problems.
2021-09-30 15:44:08 +02:00
07c5d02a11 Asset Browser: Support activating catalogs in the "Current File" library
If the "Current File" asset library is selected in the Asset Browser,
now asssets are filtered based on the active asset catalog. Previously
it would just show all assets. This was marked as a TODO in the code
already.

Maniphest Task: https://developer.blender.org/T91820
2021-09-30 15:23:13 +02:00
dda9762a16 Remove old TinyGLTF files 2021-09-22 19:38:33 -07:00
fa23369373 Merge branch 'master' into xr-controller-support 2021-09-22 19:24:20 -07:00
1ef02d8f4b Update with changes from D10948, D12472 2021-09-22 19:16:52 -07:00
6d7113c363 Merge branch 'master' into xr-controller-support 2021-09-10 17:57:57 +09:00
33306067da Update based on D10948 2021-09-10 17:23:56 +09:00
ff5733ef2f Merge branch 'master' into xr-controller-support 2021-09-02 13:39:59 +09:00
c996926c05 Remove XrSessionState.reset_navigation() RNA func
Function was unnecessary since XrSessionState.reset_to_base_pose()
will also reset navigation deltas.
2021-09-02 13:27:55 +09:00
593621fdfb Merge branch 'master' into xr-controller-support 2021-08-31 18:05:42 +09:00
07a92c616f XR: Add reset navigation operator
Resets XR navigation deltas relative to session base pose. Primarily
useful for undoing elevation/scale changes. Will be added to the
default VR actions.
2021-08-31 17:58:22 +09:00
99ce2f3e4d XR: Replace invoke/modal_3d for XR operators
Special 3D-to-2D handling (as invoke/modal_3d is used for) is not
necessary for regular XR operators, so they can use the normal
invoke/modal callbacks.
2021-08-31 17:48:29 +09:00
c8c782fbf4 Cleanup: remove unused struct member, null checks 2021-08-31 17:42:55 +09:00
0f1ef110a4 Merge branch 'master' into xr-controller-support 2021-08-26 19:29:20 +09:00
5fd158bb87 XR: Refactor motion capture objects
This refactors the feature of binding objects to controllers
and recording auto-keyframes, or "motion capture objects", by
providing a more complete API for managing objects and moving the
implementation out of wm_xr_session.c and into its own file
(wm_xr_mocap.c).

In addition, motion capture objects are now stored as a dynamic array
instead of a fixed array, which was previously hard-coded for
specific devices. Each entry in this dynamic array is uniquely
identified by an Object pointer and is mapped to a VR device via an
OpenXR user path. For example, an object can be bound to the headset
via "/user/head", and the left controller via "/user/hand/left".
2021-08-26 19:23:14 +09:00
21a24aa61e Fix view3d.toggle_shading() not updating VR view
Reason was that the WM notifier did not set the NS_VIEW3D_SHADING
subtype, which the VR view listens for for a shading update.

In the case of view3d.toggle_xray(), a notifier was absent
altogether.
2021-08-26 18:55:50 +09:00
c9a4c29589 Cleanup: remove unnecessary #ifdefs 2021-08-26 18:36:29 +09:00
a063d58080 Merge branch 'master' into xr-controller-support 2021-08-20 20:50:24 +09:00
f58bebf688 XR: Fix controller model world transforms
Fixes incorrect display of Reverb G2 controller model/animated parts.

Thanks to Werner Trunk for help with debugging.
2021-08-20 20:46:15 +09:00
ba79625f9e Merge branch 'master' into xr-controller-support 2021-08-16 18:57:48 +09:00
72381da800 XR: Don't store controller node world transforms
Removes unnecessary storage of world space transforms for controller
model nodes. Now, only local space transforms are stored and the
world transforms are calculated from these local transforms when
needed.
2021-08-16 18:55:36 +09:00
9d68c34a75 Merge branch 'master' into xr-controller-support 2021-08-16 12:04:19 +09:00
e41cc8162a Merge branch 'master' into xr-controller-support 2021-08-15 15:21:01 +09:00
1990bb921f XR: Support dynamic controller model components
Updating the transforms, or "animating", supported controller
model parts (trigger, grip, thumbstick, etc.) provides better visual
feedback for the user and adds little overhead due to caching of node
transforms/indices.
2021-08-15 15:18:34 +09:00
567c22f0d8 Merge branch 'master' into xr-controller-support 2021-08-13 07:28:34 +09:00
be0a272d91 XR: Fix crash/assert on loading controller model
When calculating controller model component transforms from glTF node
data, it was previously wrongly assumed that the glTF nodes would
always contain transform matrix values. However, nodes can instead
store their transforms as separate translation/rotation/scale values,
in which case the transform matrix needs to be explicitly calculated.
This was the case for the Reverb G2 controller models using the
Windows Mixed Reality runtime.

Special thanks to Werner Trunk for help with testing/debugging.
2021-08-13 06:44:50 +09:00
5f5289512c XR: Improve "Invalid stage ref space" warning
Originally mentioned that absolute tracking was disabled, which is
wrong because absolute tracking (skipping application of eye offsets)
is always available, although it may not give the expected result of
persistent tracking origins across sessions if the stage space is
unavailable (hence the need for a warning).

Now, the warning makes no mention of absolute tracking, instead
informing the user that the local space fallback will be used and
that they should define tracking bounds via the XR runtime if they
wish to use the stage space.
2021-08-13 06:23:01 +09:00
05c3e5c433 Cleanup
- Remove unused wmSurface.is_xr member
- Rename "actionmap" to "action_set" in wmXrActionData
- Use BLI_findstring()
- Add modifications note to tinygltf readme
2021-08-08 13:17:18 +09:00
9f6b8bc3a1 Re-update pipeline config
Accidentally updated wrong submodule in previous commit.
2021-08-07 20:55:00 +09:00
d870b85dab Update pipeline config 2021-08-07 20:46:41 +09:00
2f4712841a Merge branch 'master' into xr-controller-support 2021-08-07 18:36:55 +09:00
6dec69daa8 XR: Fix crash on file read with active session
Add null check for runtime data since it could already have been
freed via wm_xr_exit() prior to the session exit callback.
2021-08-07 18:31:12 +09:00
be7653fe52 Cleanup: remove unnecessary double pointer usage 2021-08-07 17:30:27 +09:00
c600251eae XR: Use regular window queues for XR events
Removes special queueing of events to the XR surface, which was
originally done to bypass filtering by mouse region when
invoking/executing XR operators.

Now, finding an appropriate region for XR events will simply be done in
the regular window event handling by checking the event type and
skipping mouse-specific processing.
2021-08-07 17:04:34 +09:00
d2c4094425 Merge branch 'master' into xr-controller-support 2021-08-06 16:20:44 +09:00
14fc1c73e8 Update with changes from D10943 2021-08-06 15:58:41 +09:00
ef07af330a Add missing unused variable 2021-08-02 17:00:57 +09:00
9561a0a0a3 Merge branch 'master' into xr-controller-support 2021-08-02 16:56:42 +09:00
9f5089b67a XR: Simplify action creation API
By passing an XrActionMap struct instead of individual action
parameters, action creation from Python can be reduced to a simple
call to XrSessionState.create_action_set().

This will also set any controller pose actions and optionally the
active action set for the session.
2021-08-02 16:53:04 +09:00
f34cb9f8bf Merge branch 'master' into xr-controller-support 2021-07-29 13:13:12 +09:00
5a640b94b4 Cleanup: use LISTBASE_FOREACH_INDEX macro 2021-07-29 13:12:30 +09:00
9025f2adc6 XR: Fix action map load/save
Forgot to add action bindings list.
2021-07-29 13:10:43 +09:00
d5989308fb Fix build warnings 2021-07-28 13:40:12 +09:00
8a103470c9 Merge branch 'master' into xr-controller-support 2021-07-28 13:08:26 +09:00
be59b7699e XR: Fix crash on close with active session
Fixes an issue introduced in 99beac7b3f due to duplicate call to
wm_xr_session_data_free().
2021-07-28 13:06:41 +09:00
6b67760bd8 XR: Refactor action maps, bindings
This removes the previous limitation of one interaction profile
(device) per action map by moving the profiles to action bindings.
By doing so, there is no longer the need for users to manually select
an action map, as the XR runtime will automatically select the most
appropriate profile.

An additional DNA action map layer, "XrActionMapBinding", was also
added to store profiles and input-specific properties for actions,
allowing for flexible inputs across devices.
2021-07-28 13:02:37 +09:00
fb3154447b Fix build warnings 2021-07-24 19:10:19 +09:00
c56b73277e Merge branch 'master' into xr-controller-support 2021-07-24 18:20:19 +09:00
99beac7b3f XR: Use dynamic arrays for eye, controller data
Makes API/functionality more adaptable to different systems.
2021-07-24 18:08:29 +09:00
c4c4b1a03d XR: Move controller batch creation to draw func
Ensures that a valid GPU context is available during batch creation,
which fixes potential access violation issues.
2021-07-24 15:51:23 +09:00
af90b8aaa3 Update with changes from master
36c0649d32, c41b93bda5
2021-07-24 14:23:07 +09:00
2c5241ad01 Merge branch 'master' into xr-controller-support 2021-07-22 17:36:49 +09:00
6daab062b4 Fix build errors/warnings on some platforms 2021-07-22 17:35:14 +09:00
4b3aaa76bd Merge branch 'master' into xr-controller-support 2021-07-22 15:36:54 +09:00
707bc260d8 XR: Add basic controller model drawing
Uses the OpenXR XR_MSFT_controller_model extension to load a glTF
model provided by the XR runtime. The model's vertex data is then
used to create a GPUBatch in the XR session state. Finally, this
batch is drawn via an XR surface draw callback.

Currently does not use the model's texture data, but this can be
supported in the future.
2021-07-22 15:31:36 +09:00
5b45070024 XR: Refactor controller poses
Separates controller poses into two components, "grip" and "aim",
which are both required to accurately represent the controllers
without manual offsets.

Following their OpenXR definitions, the grip pose represents the
user's hand when holding the controller, and the aim pose represents
the controller's aiming source.
2021-07-22 00:58:17 +09:00
bd8b3f57df Fix build warnings 2021-07-09 13:58:12 +09:00
4040ebd1ff Merge branch 'master' into xr-controller-support 2021-07-09 11:28:47 +09:00
3feb3a4707 XR: Fix grab interp/offsets for parented objects 2021-07-09 11:23:02 +09:00
aa92c23430 Merge branch 'master' into xr-controller-support 2021-07-07 20:04:13 +09:00
7863f2fbe0 XR: Add teleport offset option 2021-07-07 19:22:36 +09:00
872484dfaa XR: Fix action state querying from Python
Credit: Jacob Merrill
2021-07-07 19:22:10 +09:00
1185259db8 Merge branch 'master' into xr-controller-support 2021-07-04 15:48:26 +09:00
1f1589b4df XR: Show selection/controller overlays by default 2021-07-04 15:42:55 +09:00
6ee46d5e83 XR: Fix mirror view not matching navigation
Forgot to update eye poses with viewer pose.
2021-07-04 15:40:38 +09:00
a177eb3dd2 Merge branch 'master' into xr-controller-support 2021-07-03 13:06:04 +09:00
f7d42065eb XR: Fix render artifacts with viewport denoising
Addresses T76003. When using VR with Eevee and viewport denoising,
scene geometry could sometimes be occluded for one eye. Solution was,
as Clement suggested, to use a separate GPUViewport/GPUOffscreen for
each VR view instead of reusing a single one for rendering.
2021-07-03 13:05:32 +09:00
7a7ee11f13 XR: Direction lock, frame based speed fly options 2021-07-03 13:05:10 +09:00
5465112930 Cleanup: RNA text 2021-07-03 13:04:49 +09:00
f89f63eefd Merge branch 'master' into xr-controller-support 2021-07-02 18:17:29 +09:00
714224ee1f XR: Support haptic feedback for actions
Adds haptic settings to float (button) actions that can be used to
apply haptics when an action is active. Users can configure the
duration, frequency, and amplitude of the feedback as well as when
it will be applied (press, release, press/release, or repeat).

The haptic output path/target is specified via the name of an
existing action of type "haptic". This allows float actions to target
the same output paths without the need for multiple haptic actions.
2021-07-02 18:10:34 +09:00
1184da2974 XR: Add cubic interpolation option for fly speed 2021-07-02 18:09:46 +09:00
e8926d40c8 XR: Add option to raycast from viewer pose
Useful when using a gamepad or for systems without motion
controllers.
2021-07-02 18:07:55 +09:00
004034f8c5 XR: Fix grab transform for parented objects 2021-07-02 17:59:14 +09:00
7b867dd00a Merge branch 'master' into xr-controller-support 2021-06-26 19:33:17 +09:00
e022f99e5c XR: Allow multiple modal actions at a time
This was disabled before since modal handlers for action operators
could receive events from other actions and exit prematurely. Now,
the XR operators check for events with the matching operator and
properties to avoid this. In addition, a list of active modal actions
is stored in the session state so that duplicate operators can be
filtered out when dispatching events.
2021-06-26 19:25:52 +09:00
80c80f5e18 XR: Use prior nav data to calculate viewer pose
Fixes an issue where draw callbacks were not drawn correctly if an
operator changed the navigation. Now, the navigation transforms are
saved before dispatching action events and are later used to
calculate the viewer/controller poses.
2021-06-26 19:11:48 +09:00
0d6c6a6787 Merge branch 'master' into xr-controller-support 2021-06-23 20:35:06 +09:00
e3cab5c206 XR: Add fly navigation operator
Navigates the scene by moving/turning relative to navigation space
or the XR viewer/controller. Users can select from a variety of
these modes as well as specify the min/max speed and whether to lock
elevation.

Also: pass float threshold to XR action events.
2021-06-23 20:30:32 +09:00
8ace7897a8 XR: Update controller data again in viewer update
This is needed to draw the controllers with correct transforms in the
event that an operator changed the navigation.
2021-06-23 20:24:31 +09:00
e5089d3924 Merge branch 'master' into xr-controller-support 2021-06-19 18:18:39 +09:00
65804b203e XR: Add location/rotation locks for navigation 2021-06-19 18:12:43 +09:00
2a768ffe43 Merge branch 'master' into xr-controller-support 2021-06-18 21:36:13 +09:00
2585d7f1b8 XR: Add grab navigation operator
Similar to the XR grab transform operator but applies deltas to the
navigation matrix instead of objects. Supports bimanual interaction
for viewer/navigation scaling and locks for rotation and scale.
2021-06-18 21:32:18 +09:00
eed31613e5 XR: Smooth transition between modal actions
Although only one modal action is allowed at a time, if another
modal action is pressed when the current modal action ends, then
transition immediately to that one.
2021-06-18 21:26:38 +09:00
6732d31f18 XR: Fixes/adjustments when viewer scale != 1.0
This corrects some clipping and shading issues when a scale factor
is applied to the XR viewmat. Also fixes the viewer pose calculation
with VR navigation.
2021-06-18 21:22:15 +09:00
1bd403430d Remove duplicate file
Was renamed to "wm_xr_action.c".
2021-06-18 21:14:13 +09:00
9926553255 Merge branch 'master' into xr-controller-support 2021-06-12 19:32:20 +09:00
0c92bf5e37 XR: Fix viewer pose and teleport calculation 2021-06-12 19:28:07 +09:00
9ce38d17ed XR: Add bimanual scale mode for grab operator
Also renamed operator to "Transform Grab".
2021-06-12 19:25:42 +09:00
40741a8942 Merge branch 'master' into xr-controller-support 2021-06-11 21:05:57 +09:00
eeb948f428 XR: Add axis region, bimanual action flags
The axis flags allow binding actions to certain regions of an input
axis (positive or negative). In this way, two different actions can
be bound to the input path "/input/thumbstick/x" without overlapping.

The bimanual flag signifies that the action depends on inputs from
both of its subaction paths, and both states, if available, will then
be passed to the operator. This will be used in the future for
two-handed navigation and transform.
2021-06-11 20:48:41 +09:00
9c3ac44c89 Merge branch 'master' into xr-controller-support 2021-06-08 19:35:24 +09:00
819a7e7900 Update pipeline config 2021-06-08 19:31:24 +09:00
5544ffabd5 Merge branch 'master' into xr-controller-support 2021-06-08 13:52:45 +09:00
2a3b8880e3 Fix build warnings 2021-06-08 13:51:22 +09:00
05f5d2791f Merge branch 'master' into xr-controller-support 2021-06-04 19:18:44 +09:00
29702c6d8b XR: Add teleport navigation operator
Mostly the same as the XR raycast select operator. Users can
optionally constrain the result to specific axes, for example to
achieve "elevation snapping" behavior by constraining to the Z-axis.

Credit to KISKA for the elevation snapping concept.
2021-06-04 19:10:53 +09:00
36463a16bb XR: Add initial navigation support
Adds navigation transforms (pose, scale) to the XR session state
that will be applied to the viewer/controller poses.

Users can access these transforms via Python
(xr_session_state.navigation_location/rotation/scale) to use with
custom operators.
2021-06-04 19:01:50 +09:00
b57cc27ec9 Fix build errors on some platforms 2021-05-26 18:13:35 +09:00
8b53855371 Clang format 2021-05-26 18:12:14 +09:00
af1c2869a6 Merge branch 'master' into xr-controller-support 2021-05-26 17:34:35 +09:00
9df2fae994 XR: Enable controller profile extensions 2021-05-26 17:08:35 +09:00
daf5570c42 Cleanup GHOST_Xr 2021-05-26 17:06:14 +09:00
795ae6433d XR: Change raycast color 2021-05-26 17:05:19 +09:00
1e8b3692b9 XR: Deactivate draw callbacks on session end 2021-05-26 17:04:39 +09:00
544c6fd1b6 Merge branch 'master' into xr-controller-support 2021-05-18 22:14:12 +09:00
963a6c3c12 Update addons submodule 2021-05-18 22:13:22 +09:00
f799fc3033 Merge branch 'master' into temp-xr-actions-D9124 2021-05-18 19:28:04 +09:00
5a786038cb Fix RNA error 2021-05-18 19:14:34 +09:00
7343845d7b Update with changes from D10943 2021-05-18 18:47:17 +09:00
ce5fc090a8 Move customdata wrapper to GHOST_Util.h 2021-05-16 03:13:07 +09:00
2fa8e2686e Rename "threshold" to "float_threshold" 2021-05-16 03:12:56 +09:00
f89460a872 Use RAII customdata wrapper for ctor exceptions 2021-05-16 01:09:39 +09:00
8c8bc114b8 Fix errors/warnings on Linux, gcc 2021-05-15 22:15:06 +09:00
11a63417ea Update with changes from D10942
- GHOST_XrException: Use std::string instead of const char*
- GHOST_XrActionProfile: Move default dtor declaration to header
- GHOST_XrActionSet/Action: Fix mem leak on exception in ctor
2021-05-15 10:58:53 +09:00
ed869a2609 Revert changes to wm_xr_controller_pose_to_mat() 2021-05-15 08:45:16 +09:00
79ed8f21f7 Fix merge-related error 2021-05-14 22:02:58 +09:00
a5d1f3e4c5 Merge branch 'master' into temp-xr-actions-D9124 2021-05-14 21:36:39 +09:00
7c05339597 Update with changes from D10942
Mainly cleanup-related but also adds wmXrPose and wmXrActionState
structs to replace float[7] and void* uses, respectively.
2021-05-14 21:32:23 +09:00
cd3030bb8d Fix RNG_TYPE_LEN value 2021-05-14 21:30:16 +09:00
5677c02954 Pass context to wm_xr_session_actions_update()
Restores motion capture object autokeying functionality.
2021-05-03 11:07:00 +09:00
3e7524b52b Clang format 2021-05-03 11:04:07 +09:00
c34d0fbee2 Update with changes from D10942
Except for motion capture object autokeying (will be added again once
a good way to get the blender context from wm_xr_session is found),
functionality remains the same as before.
2021-04-30 19:54:03 +09:00
63f0dcf6b7 Merge branch 'master' into temp-xr-actions-D9124 2021-04-29 15:36:47 +09:00
517a3cdad1 Adjust CMakeLists, fix typo 2021-04-29 15:32:27 +09:00
585f98784c Merge branch 'master' into xr-actions-D9124 2021-04-10 13:28:17 +09:00
c9c052aae0 Merge branch 'master' into xr-actions-D9124 2021-03-21 20:37:56 +09:00
9cfc8ebcc9 XR: Add op_name property to XrActionMapItem
Improves readability over operator ID string when displaying action
properties.
2021-03-21 20:37:05 +09:00
dfc8860502 Revert edit to blender_default.py
Leftover from keymap->actionmap refactor.
2021-03-21 20:12:32 +09:00
97c8878753 Fix warnings on Linux / gcc 2021-03-14 19:54:43 +09:00
49707f5d83 Merge branch 'master' into xr-actions-D9124 2021-03-14 19:14:50 +09:00
334114d287 XR: Cleanup after actionmap refactoring
Aside from general cleanup, changed that only user actionconfigs and
the builtin user config can be saved to blend files (the builtin
default and addon configs are excluded).
2021-03-14 19:02:34 +09:00
034dd0d702 XR: Move actions from keymaps to actionmaps
Previously, properties for XR actions were stored in keymaps but they
are now stored in a separate XR "actionmaps" system. Although the
actionmap system/API has many similarities to the keymaps, it is
significantly less complex since it does not involve any diff-ing of
default/addon/user configurations (at least at the moment).

Another big distinction between keymaps and actionmaps is that
actionmaps and properties are saved to blend files. This allows users
to set up a VR scene with actions and properties and share working
versions with others, without the need to import/export config files.
However, actionmap import/export is also supported via the addon.

There is still a fair amount of refactoring left to do but at least
this commit removes XR involvement from the keymaps while preserving
most of the existing XR action functionality (i.e. default actions
and user-configurable actions).
2021-03-07 21:40:38 +09:00
d8cf5e7f4d Merge branch 'master' into xr-actions-D9124 2021-03-07 18:54:26 +09:00
854d115d68 XR: "deselect_all" by default for raycast select 2021-03-07 18:33:19 +09:00
886cba7fe8 Fix build error when disabling WITH_XR_OPENXR 2021-03-07 18:29:46 +09:00
f21eb52ecc Merge branch 'master' into xr-actions-D9124 2021-02-16 20:15:34 +09:00
81944920a7 Fix build error on Linux / gcc 2021-02-16 20:12:57 +09:00
cf12206311 Merge branch 'master' into xr-actions-D9124 2021-02-09 00:44:48 +09:00
247267dee4 XR: Use common functions for modal_3d operators
Reduces the amount of similar code for XR invoke_3d/modal_3d
operators that manipulate view3d params.
2021-02-09 00:11:49 +09:00
2370781389 Add TODO for merging into master 2021-02-08 23:57:52 +09:00
1aecb31acc Merge branch 'master' into xr-actions-D9124 2021-01-31 23:53:02 +09:00
2092d39dfd Fix merge-related error 2021-01-31 23:51:36 +09:00
412ea63063 Merge branch 'master' into xr-actions-D9124 2021-01-31 22:31:07 +09:00
0f236af817 XR: Remove remaining use of GHOST types in RNA
It might be good to have a WM equivalent for GHOST_XrPose (e.g.
wmXrPose) since converting from location/rotation or float[7] to
GHOST_XrPose can be pretty awkward.
2021-01-31 22:07:13 +09:00
798c33ab3b XR: Start refactoring internal API
Remove unnecessary data from GHOST types and mostly eliminate use of
GHOST types at RNA level. At the WM level, action creation functions
deal with single actions (instead of multiple actions at once) to
simplify the API.
2021-01-30 12:17:50 +09:00
a87b142e51 Fix build errors after merge
Also fixes incorrect use of RNA_def_struct_name_property().
2021-01-17 19:19:41 +09:00
2ddf55f358 Merge branch 'master' into xr-actions-D9124 2021-01-17 18:45:20 +09:00
84f821f271 Merge branch 'master' into xr-actions-D9124 2020-12-13 13:52:25 +09:00
29ed6a6872 Merge branch 'master' into xr-actions-D9124 2020-11-26 13:32:35 +09:00
78563e9bf1 XR: Add "controller draw style" session setting
Allows users to choose their preferred controller visualization
(controller local axes or -Z axis ray). The enum can be extended in
the future for additional visualizations.
2020-11-25 22:57:33 +09:00
8916a04df8 XR: Restore XR object transforms at session end
If an object was constrained to a headset/controller pose during the
session, then its original transform will be restored at session end
to prevent unwanted changes to the scene. This will also occur when
toggling the constraint or changing the constrained object.
2020-11-25 22:51:58 +09:00
c9f0da5b20 XR: Adjust raycast select property behavior
Combinations of properties (extend/deselect/toggle/deselect on empty)
now match view3d.select behavior.
2020-11-25 22:32:43 +09:00
f7a72a238b Merge branch 'master' into xr-actions-D9124 2020-11-23 16:17:12 +09:00
c8db91fc93 XR: Only allow one active modal action/subaction
Prevents unwanted behavior when a modal operator is bound to multiple
inputs (e.g. when an action has multiple subaction paths). Can be
refactored in the future to support "bimanual" interaction for some
operators.
2020-11-23 16:16:41 +09:00
e089ad2b4b Merge branch 'master' into xr-actions-D9124 2020-11-22 19:40:36 +09:00
6c67d808b7 XR: Add raycast option to ignore non-selectables
Useful when user does not want non-selectable objects to block
selectable objects in raycast result.
2020-11-22 19:35:16 +09:00
d3e352a7ec Keymap I/O: Add "exec" versions of import/export
The new "exec" functions are added for the sake of the VR Scene
Inspection add-on and do not affect the behavior of the existing
keymap I/O functions.

keyconfig_export_as_data_exec() enables exporting a specified keymap
instead of all user-modified keymaps. The VR add-on uses this to
selectively export the "XR Session" add-on keymap.

keyconfig_import_as_data_exec() enables adding keymap data to an
existing keyconfig instead of creating a new keyconfig. The VR add-on
uses this to add the "XR Session" keymap to the add-on keyconfig.
2020-11-22 18:52:17 +09:00
4423ab787b XR: Improve error logs when creating actions
Include action set name in case multiple action sets have an action
with the same name.
2020-11-22 18:29:14 +09:00
57f7e1ff97 Merge branch 'master' into xr-actions-D9124 2020-11-16 18:15:35 +09:00
f8ad9c696e Merge branch 'master' into xr-actions-D9124 2020-11-15 23:52:40 +09:00
79733505bf XR: Enable switching action sets during session
Since xrSuggestInteractionProfileBindings() overwrites any existing
profile bindings, the solution is to suggest bindings from all action
sets at the same time (done just before attaching action sets to the
session).
2020-11-15 23:46:40 +09:00
b4a21355d7 Cleanup: Comments 2020-11-15 23:38:37 +09:00
d685b9ca57 Cleanup: comments, variables 2020-11-14 22:22:46 +09:00
d3b07d5ad5 XR: Support axis (vector2f) input actions 2020-11-14 15:52:41 +09:00
86faaaa934 XR: Improve raycast select in edit mode
Limit raycast test to selected objects.
2020-11-14 15:50:56 +09:00
4d5f104faf Merge branch 'master' into xr-actions-D9124 2020-11-14 01:45:59 +09:00
6fdf493028 XR: Support mesh editing with grab operator
Uses similar mechanics as object mode, but interpolation and offsets
are not yet supported.
2020-11-14 01:26:50 +09:00
43dd6ba330 XR: Ensure mesh object for edit mode raycast 2020-11-14 01:23:09 +09:00
423619fd3d Increase line width for controllers / raycast 2020-11-14 01:10:31 +09:00
6489002982 XR: Remove Z-up conversion for pose actions
Not applying an implicit Z-up conversion for OpenXR coordinates may
be better since poses may have different orientations / tracking
spaces. Users can instead apply custom pose offsets via the UI /
Python API if necessary.
2020-11-14 01:08:28 +09:00
634812e579 Merge branch 'master' into xr-actions-D9124 2020-11-11 23:42:43 +09:00
8ccdd43fc1 XR: Implement XR grab operator
Transforms selected objects' location / rotation relative to a
controller's pose. Very basic compared to the existing transform
operators (which can also be used in VR via modal_3d), but provides
a more natural interaction in VR.
2020-11-11 23:30:24 +09:00
78d7dbbad9 Merge branch 'master' into xr-actions-D9124 2020-11-08 22:55:44 +09:00
4ec85983af XR: Remove controller overlay object
Object was originally meant to visualize the controller and be drawn
by the overlay engine, however it was unused since controllers are
now drawn via draw handlers. It might be added again in the future
for drawing more complex controller geometry.
2020-11-07 21:08:17 +09:00
dc0b81bc99 XR: Add XR region type to View3D space type
Enables add-ons to draw to the XR surface and mirror window by
adding a View3D draw handler of region type 'XR' and draw type
'POST_VIEW'. Also allows individual toggling of controller drawing
and custom overlays.
2020-11-07 19:04:36 +09:00
f6e71179fe XR: Draw controller overlays in mirror view 2020-11-07 17:44:56 +09:00
36bdbe6ce3 XR: Store multiple paths in GHOST_XrActionBinding
Facilitates action binding creation debugging by narrowing
errors down to an interaction profile and action.
2020-11-07 16:46:33 +09:00
5f7285d4af XR: Check for valid stage reference space bounds
Even if the stage reference space is supported by the XR runtime, the
bounds may be invalid (0, 0) if the user did not define a tracking
space via the runtime's UI. In this case, fall back to the local
tracking space.
2020-11-07 15:19:38 +09:00
1625bb3e71 Merge branch 'master' into xr-actions-D9124 2020-11-06 23:58:06 +09:00
a399445b3c XR: Fix projection selection in edit mode
Use window region dimensions instead of XR surface dimensions. Add
winrct offset for box select.
2020-11-06 23:56:31 +09:00
f32d320e90 XR: Fix raycast deselect empty for edit mode 2020-11-06 23:42:28 +09:00
78cd4e8cee Merge branch 'master' into xr-actions-D9124 2020-11-06 21:48:15 +09:00
848c87c6d9 Fix errors after merge 2020-11-06 21:46:56 +09:00
f5ff515085 Merge branch 'master' into xr-actions-D9124 2020-11-06 21:40:58 +09:00
07a7c88918 XR: Use stage ref space for absolute tracking 2020-11-06 21:31:43 +09:00
48ec546911 XR: Add axis property to raycast select operator
Allows users to adjust the raycast axis to fit different poses and
controllers.
2020-11-06 17:13:17 +09:00
8335da48f9 Cleanup: Comments 2020-11-06 17:08:42 +09:00
8fae3debf3 Merge branch 'master' into xr-actions-D9124 2020-11-05 22:33:39 +09:00
2ed0965e97 XR: Add "absolute tracking" session setting
Enables users to define the tracking origin in a way that is not
linked to the headset position. Instead, the tracking values given by
the XR runtime are left unadjusted and a user can manually calibrate
an "origin" landmark object to adjust to their real world space.

Can be useful for applications that use external tracking systems
and those that primarily only need to use controllers (e.g. for
motion capture).
2020-11-05 22:31:34 +09:00
2b338373d7 XR: Ensure valid region for invoking operators
Check for a valid v3d, even if the area / region uses a viewport.
2020-11-04 21:33:14 +09:00
c565bb8985 XR: Use TransInfo viewmat for transform modal_3d
Fixes static variable TODO.
2020-11-04 21:30:03 +09:00
03bb02337f XR: Compare absolute input value with threshold
Trackpad / thumbstick input values range from -1 to 1, so the
absolute value must be compared against the input threshold.
2020-11-04 21:24:43 +09:00
618df6b0b7 Fix build warnings on Linux / gcc 2020-11-04 21:13:42 +09:00
0c246b2b52 Merge branch 'master' into xr-actions-D9124 2020-11-03 18:50:07 +09:00
ff2dcdb447 XR: Implement XR constraints toggle operator
Useful for quickly playing back recorded animations without
overwriting existing data.
2020-11-03 18:47:11 +09:00
73daf1f721 XR: Support auto keying XR constraint objects
If specified, headset / controller objects will auto key on animation
playback. This can be useful for virtual camera applications.
2020-11-03 17:37:41 +09:00
9c28e6e62f XR: Fix crash when reading "constraint" objects
Pointers were not being relinked from file.
2020-11-03 10:46:42 +09:00
9c288d3e20 Merge branch 'master' into xr-actions-D9124 2020-11-02 00:09:10 +09:00
86f76f46f9 XR: Remove view space limitation for transform op
Not needed now that XR supports calling operators with properties.
2020-11-02 00:04:53 +09:00
498804edc8 XR: Implement raycast select operator
Uses the same raycast method as Scene.ray_cast() and works in object
and edit mode. Although it gives users an alternative to the existing
projection-based VR selection method, it is limited to objects with
real geometry (i.e. meshes).
2020-11-01 23:50:46 +09:00
3d24ef827c Merge branch 'master' into xr-actions-D9124 2020-10-31 23:38:14 +09:00
c8be04b7f8 Cleanup: Rename controller pose properties
Makes naming consistent with controller#_object session settings.
2020-10-31 23:31:33 +09:00
58ecc0d1bf XR: Add "constraint" objects to session settings
These objects (3 in total, corresponding to headset and left/right
controllers) will have their location and rotation determined by the
associated XR pose. This provides a way for add-ons to achieve
this "VR constraint" behavior without the need to constantly query
pose information.
2020-10-31 23:00:43 +09:00
1a6d76c5c7 Remove unused parameter 2020-10-31 22:27:00 +09:00
a1333cf2c3 Merge branch 'master' into xr-actions-D9124 2020-10-29 23:51:32 +09:00
9be27de831 XR: Improve error handling / messages
Originally thought that not terminating the session for certain "non-
fatal" errors (e.g. an incorrect action path) would make sense.
However, realized that error messages can pile up this way and it
is better for user feedback and debugging to always exit the session
when encountering an OpenXR-level error.
2020-10-29 23:49:59 +09:00
a90bfa17a9 XR: Include action set name in XR event data
Needed to match keymap items with XR events.
2020-10-29 21:01:49 +09:00
d72fafd62a Merge branch 'master' into xr-actions-D9124 2020-10-28 01:05:45 +09:00
62c5f71da1 XR: Add XR identifiers to key config I/O
Allows XR action properties to be shared with others.
2020-10-28 01:04:01 +09:00
09377b4c9c Merge branch 'master' into xr-actions-D9124 2020-10-27 02:17:20 +09:00
b596d70a1f Apply formatting 2020-10-27 02:05:24 +09:00
1d86e1ff46 XR: Support operator properties for actions
Pointers to op properties are stored in the XR session state as
IDProperty* and applied upon op execution. The actual properties
themselves are stored as key map items (new "XR" and "XR Session"
key maps were added to the blender default key maps).

The "XR" key map is intended to store op properties for action sets
that are saved in add-on (e.g. the VR Scene Inspection add-on) prefs.
The "XR Session" key map is intended to store op properties for scene
action sets, with these properties used for the XR session.

In this way, scene action sets can configure properties without
worrying about overwriting properties for action sets saved in prefs.
2020-10-27 01:54:03 +09:00
3e0acdfe94 Fix build error when disabling WITH_XR_OPENXR 2020-10-27 01:30:47 +09:00
a6c5a1a2dc Merge branch 'master' into xr-actions-D9124 2020-10-21 23:59:08 +09:00
ceab20d5e5 Cleanup: Comment style 2020-10-21 23:57:09 +09:00
cb75a83df0 XR: Draw controllers via draw handler
Callbacks for controllers and other custom drawing will be registered
with the XR surface's region type (RGN_TYPE_XR_DISPLAY) and called
post-view.
2020-10-21 23:47:29 +09:00
db1cdcdafe Merge branch 'master' into xr-actions-D9124 2020-10-20 00:59:21 +09:00
297decbe1f XR: Add input threshold for float actions
Fixes button release events not being sent due to non-zero state.
2020-10-20 00:52:49 +09:00
8af91a18e0 XR: Support basic transform operators
Uses same invoke_3d / modal_3d approach as select operators.
2020-10-20 00:48:29 +09:00
9ec8ca2d55 Merge branch 'master' into xr-actions-D9124 2020-10-19 01:06:04 +09:00
5561fbf1f8 XR: Apply VR clip settings when selecting
Fixes projection matrix calculation for GPU select.
2020-10-19 01:04:04 +09:00
de026d1395 Remove unused function
Was meant to be removed in 2eb44677.
2020-10-18 15:31:15 +09:00
07e5488c61 Merge branch 'master' into xr-actions-D9124 2020-10-18 15:18:51 +09:00
2eb44677dd Fix build error when disabling WITH_XR_OPENXR 2020-10-18 15:11:12 +09:00
3e33c89828 Fix build error on Linux / gcc 2020-10-18 15:06:12 +09:00
f2385a3f05 Merge branch 'master' into xr-actions-D9124
# Conflicts:
#	source/blender/windowmanager/intern/wm_event_system.c
2020-10-17 23:42:47 +09:00
f6a7d5336b XR: Add "selection eye" session setting
The "selection eye" is the XR eye (view) used for 3D-to-2D projection.
Its parameters are used to override the window view when selecting
with VR controllers.
2020-10-17 11:59:50 +09:00
336c8a628a Merge branch 'master' into xr-actions-D9124 2020-10-15 00:43:31 +09:00
66e22a2ed7 Operators: Add modal_3d() and implement for box select
This follows the same logic as invoke_3d(). That is, we wrap an
operator's modal() with modal_3d() and project 3D controller coords to
2D mouse coords.
2020-10-15 00:41:34 +09:00
6920906f75 XR: Remove "replace existing" param from action bindings creation
Since action bindings are only created once at the beginning of an XR
session, overwriting all existing bindings will not be needed.
2020-10-15 00:23:48 +09:00
ae1d8f4c35 Fix: Use correct add-on branch for 'make update'
Credit to Julian Eisel for the fix.
2020-10-14 08:47:14 +09:00
e0eafd2757 XR: Add selection outline offscreen draw flag 2020-10-13 21:39:03 +09:00
5e8d3f6d28 Operators: Add invoke_3d() and implement for VIEW3D_OT_select 2020-10-13 21:39:03 +09:00
090c1b7dda Disable XR object saving and (for now) creation 2020-10-13 21:39:03 +09:00
f126ce2d5b Add safety checks when accessing XR window and controller objects. 2020-10-13 21:39:03 +09:00
3b3955d9e2 Update comment. 2020-10-13 21:39:03 +09:00
9c9081bf83 Fix annotations not being shown in XR view. 2020-10-13 21:39:03 +09:00
cfc5c1b46d Improve error logging and controller data management. 2020-10-13 21:39:03 +09:00
45194962c1 Add wm_xr_operators.c. 2020-10-13 21:39:03 +09:00
921510f34f Add xr overlay and controller object. 2020-10-13 21:39:02 +09:00
d7083de46d Update controller data when setting controller pose action. 2020-10-13 21:39:02 +09:00
b44173754f Fix condition for processing xr actions. 2020-10-13 21:39:02 +09:00
e0da725075 Add basic controller visualization.
Differential Revision: https://developer.blender.org/D9124
2020-10-13 21:39:02 +09:00
256 changed files with 12739 additions and 2208 deletions

View File

@@ -5,7 +5,7 @@
update-code:
git:
submodules:
- branch: master
- branch: xr-controller-support
commit_id: HEAD
path: release/scripts/addons
- branch: master

View File

@@ -178,7 +178,7 @@ def submodules_update(args, release_version, branch):
branch = branch_fallback
submodules = [
("release/scripts/addons", branch, branch_fallback),
("release/scripts/addons", "xr-controller-support", branch_fallback),
("release/scripts/addons_contrib", branch, branch_fallback),
("release/datafiles/locale", branch, branch_fallback),
("source/tools", branch, branch_fallback),

View File

@@ -0,0 +1,40 @@
"""
This method enables conversions between Local and Pose space for bones in
the middle of updating the armature without having to update dependencies
after each change, by manually carrying updated matrices in a recursive walk.
"""
def set_pose_matrices(obj, matrix_map):
"Assign pose space matrices of all bones at once, ignoring constraints."
def rec(pbone, parent_matrix):
matrix = matrix_map[pbone.name]
## Instead of:
# pbone.matrix = matrix
# bpy.context.view_layer.update()
# Compute and assign local matrix, using the new parent matrix
if pbone.parent:
pbone.matrix_basis = pbone.bone.convert_local_to_pose(
matrix,
pbone.bone.matrix_local,
parent_matrix=parent_matrix,
parent_matrix_local=pbone.parent.bone.matrix_local,
invert=True
)
else:
pbone.matrix_basis = pbone.bone.convert_local_to_pose(
matrix,
pbone.bone.matrix_local,
invert=True
)
# Recursively process children, passing the new matrix through
for child in pbone.children:
rec(child, matrix)
# Scan all bone trees from their roots
for pbone in obj.pose.bones:
if not pbone.parent:
rec(pbone, None)

View File

@@ -64,6 +64,8 @@ if(WITH_CYCLES_STANDALONE)
cycles_standalone.cpp
cycles_xml.cpp
cycles_xml.h
oiio_output_driver.cpp
oiio_output_driver.h
)
add_executable(cycles ${SRC} ${INC} ${INC_SYS})
unset(SRC)
@@ -73,7 +75,7 @@ if(WITH_CYCLES_STANDALONE)
if(APPLE)
if(WITH_OPENCOLORIO)
set_property(TARGET cycles APPEND_STRING PROPERTY LINK_FLAGS " -framework IOKit")
set_property(TARGET cycles APPEND_STRING PROPERTY LINK_FLAGS " -framework IOKit -framework Carbon")
endif()
if(WITH_OPENIMAGEDENOISE AND "${CMAKE_OSX_ARCHITECTURES}" STREQUAL "arm64")
# OpenImageDenoise uses BNNS from the Accelerate framework.

View File

@@ -36,6 +36,9 @@
#include "util/util_unique_ptr.h"
#include "util/util_version.h"
#include "app/cycles_xml.h"
#include "app/oiio_output_driver.h"
#ifdef WITH_CYCLES_STANDALONE_GUI
# include "util/util_view.h"
#endif
@@ -54,6 +57,7 @@ struct Options {
bool quiet;
bool show_help, interactive, pause;
string output_filepath;
string output_pass;
} options;
static void session_print(const string &str)
@@ -89,30 +93,6 @@ static void session_print_status()
session_print(status);
}
static bool write_render(const uchar *pixels, int w, int h, int channels)
{
string msg = string_printf("Writing image %s", options.output_path.c_str());
session_print(msg);
unique_ptr<ImageOutput> out = unique_ptr<ImageOutput>(ImageOutput::create(options.output_path));
if (!out) {
return false;
}
ImageSpec spec(w, h, channels, TypeDesc::UINT8);
if (!out->open(options.output_path, spec)) {
return false;
}
/* conversion for different top/bottom convention */
out->write_image(
TypeDesc::UINT8, pixels + (h - 1) * w * channels, AutoStride, -w * channels, AutoStride);
out->close();
return true;
}
static BufferParams &session_buffer_params()
{
static BufferParams buffer_params;
@@ -147,9 +127,14 @@ static void scene_init()
static void session_init()
{
options.session_params.write_render_cb = write_render;
options.output_pass = "combined";
options.session = new Session(options.session_params, options.scene_params);
if (!options.output_filepath.empty()) {
options.session->set_output_driver(make_unique<OIIOOutputDriver>(
options.output_filepath, options.output_pass, session_print));
}
if (options.session_params.background && !options.quiet)
options.session->progress.set_update_callback(function_bind(&session_print_status));
#ifdef WITH_CYCLES_STANDALONE_GUI
@@ -160,6 +145,11 @@ static void session_init()
/* load scene */
scene_init();
/* add pass for output. */
Pass *pass = options.scene->create_node<Pass>();
pass->set_name(ustring(options.output_pass.c_str()));
pass->set_type(PASS_COMBINED);
options.session->reset(options.session_params, session_buffer_params());
options.session->start();
}

View File

@@ -333,6 +333,7 @@ static void xml_read_shader_graph(XMLReadState &state, Shader *shader, xml_node
}
snode = (ShaderNode *)node_type->create(node_type);
snode->set_owner(graph);
}
xml_read_node(graph_reader, snode, node);

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2021 Blender Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "app/oiio_output_driver.h"
CCL_NAMESPACE_BEGIN
OIIOOutputDriver::OIIOOutputDriver(const string_view filepath,
const string_view pass,
LogFunction log)
: filepath_(filepath), pass_(pass), log_(log)
{
}
OIIOOutputDriver::~OIIOOutputDriver()
{
}
void OIIOOutputDriver::write_render_tile(const Tile &tile)
{
/* Only write the full buffer, no intermediate tiles. */
if (!(tile.size == tile.full_size)) {
return;
}
log_(string_printf("Writing image %s", filepath_.c_str()));
unique_ptr<ImageOutput> image_output(ImageOutput::create(filepath_));
if (image_output == nullptr) {
log_("Failed to create image file");
return;
}
const int width = tile.size.x;
const int height = tile.size.y;
ImageSpec spec(width, height, 4, TypeDesc::FLOAT);
if (!image_output->open(filepath_, spec)) {
log_("Failed to create image file");
return;
}
vector<float> pixels(width * height * 4);
if (!tile.get_pass_pixels(pass_, 4, pixels.data())) {
log_("Failed to read render pass pixels");
return;
}
/* Manipulate offset and stride to convert from bottom-up to top-down convention. */
image_output->write_image(TypeDesc::FLOAT,
pixels.data() + (height - 1) * width * 4,
AutoStride,
-width * 4 * sizeof(float),
AutoStride);
image_output->close();
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2021 Blender Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "render/output_driver.h"
#include "util/util_function.h"
#include "util/util_image.h"
#include "util/util_string.h"
#include "util/util_unique_ptr.h"
#include "util/util_vector.h"
CCL_NAMESPACE_BEGIN
class OIIOOutputDriver : public OutputDriver {
public:
typedef function<void(const string &)> LogFunction;
OIIOOutputDriver(const string_view filepath, const string_view pass, LogFunction log);
virtual ~OIIOOutputDriver();
void write_render_tile(const Tile &tile) override;
protected:
string filepath_;
string pass_;
LogFunction log_;
};
CCL_NAMESPACE_END

View File

@@ -31,13 +31,14 @@ set(INC_SYS
set(SRC
blender_camera.cpp
blender_device.cpp
blender_display_driver.cpp
blender_image.cpp
blender_geometry.cpp
blender_gpu_display.cpp
blender_light.cpp
blender_mesh.cpp
blender_object.cpp
blender_object_cull.cpp
blender_output_driver.cpp
blender_particles.cpp
blender_curves.cpp
blender_logging.cpp
@@ -51,10 +52,11 @@ set(SRC
CCL_api.h
blender_device.h
blender_gpu_display.h
blender_display_driver.h
blender_id_map.h
blender_image.h
blender_object_cull.h
blender_output_driver.h
blender_sync.h
blender_session.h
blender_texture.h

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
#include "blender/blender_gpu_display.h"
#include "blender/blender_display_driver.h"
#include "device/device.h"
#include "util/util_logging.h"
@@ -273,17 +273,17 @@ uint BlenderDisplaySpaceShader::get_shader_program()
}
/* --------------------------------------------------------------------
* BlenderGPUDisplay.
* BlenderDisplayDriver.
*/
BlenderGPUDisplay::BlenderGPUDisplay(BL::RenderEngine &b_engine, BL::Scene &b_scene)
BlenderDisplayDriver::BlenderDisplayDriver(BL::RenderEngine &b_engine, BL::Scene &b_scene)
: b_engine_(b_engine), display_shader_(BlenderDisplayShader::create(b_engine, b_scene))
{
/* Create context while on the main thread. */
gl_context_create();
}
BlenderGPUDisplay::~BlenderGPUDisplay()
BlenderDisplayDriver::~BlenderDisplayDriver()
{
gl_resources_destroy();
}
@@ -292,19 +292,18 @@ BlenderGPUDisplay::~BlenderGPUDisplay()
* Update procedure.
*/
bool BlenderGPUDisplay::do_update_begin(const GPUDisplayParams &params,
bool BlenderDisplayDriver::update_begin(const Params &params,
int texture_width,
int texture_height)
{
/* Note that it's the responsibility of BlenderGPUDisplay to ensure updating and drawing
/* Note that it's the responsibility of BlenderDisplayDriver to ensure updating and drawing
* the texture does not happen at the same time. This is achieved indirectly.
*
* When enabling the OpenGL context, it uses an internal mutex lock DST.gl_context_lock.
* This same lock is also held when do_draw() is called, which together ensure mutual
* exclusion.
*
* This locking is not performed at the GPU display level, because that would cause lock
* inversion. */
* This locking is not performed on the Cycles side, because that would cause lock inversion. */
if (!gl_context_enable()) {
return false;
}
@@ -361,7 +360,7 @@ bool BlenderGPUDisplay::do_update_begin(const GPUDisplayParams &params,
return true;
}
void BlenderGPUDisplay::do_update_end()
void BlenderDisplayDriver::update_end()
{
gl_upload_sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
glFlush();
@@ -369,54 +368,18 @@ void BlenderGPUDisplay::do_update_end()
gl_context_disable();
}
/* --------------------------------------------------------------------
* Texture update from CPU buffer.
*/
void BlenderGPUDisplay::do_copy_pixels_to_texture(
const half4 *rgba_pixels, int texture_x, int texture_y, int pixels_width, int pixels_height)
{
/* This call copies pixels to a Pixel Buffer Object (PBO) which is much cheaper from CPU time
* point of view than to copy data directly to the OpenGL texture.
*
* The possible downside of this approach is that it might require a higher peak memory when
* doing partial updates of the texture (although, in practice even partial updates might peak
* with a full-frame buffer stored on the CPU if the GPU is currently occupied). */
half4 *mapped_rgba_pixels = map_texture_buffer();
if (!mapped_rgba_pixels) {
return;
}
if (texture_x == 0 && texture_y == 0 && pixels_width == texture_.width &&
pixels_height == texture_.height) {
const size_t size_in_bytes = sizeof(half4) * texture_.width * texture_.height;
memcpy(mapped_rgba_pixels, rgba_pixels, size_in_bytes);
}
else {
const half4 *rgba_row = rgba_pixels;
half4 *mapped_rgba_row = mapped_rgba_pixels + texture_y * texture_.width + texture_x;
for (int y = 0; y < pixels_height;
++y, rgba_row += pixels_width, mapped_rgba_row += texture_.width) {
memcpy(mapped_rgba_row, rgba_row, sizeof(half4) * pixels_width);
}
}
unmap_texture_buffer();
}
/* --------------------------------------------------------------------
* Texture buffer mapping.
*/
half4 *BlenderGPUDisplay::do_map_texture_buffer()
half4 *BlenderDisplayDriver::map_texture_buffer()
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture_.gl_pbo_id);
half4 *mapped_rgba_pixels = reinterpret_cast<half4 *>(
glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY));
if (!mapped_rgba_pixels) {
LOG(ERROR) << "Error mapping BlenderGPUDisplay pixel buffer object.";
LOG(ERROR) << "Error mapping BlenderDisplayDriver pixel buffer object.";
}
if (texture_.need_clear) {
@@ -431,7 +394,7 @@ half4 *BlenderGPUDisplay::do_map_texture_buffer()
return mapped_rgba_pixels;
}
void BlenderGPUDisplay::do_unmap_texture_buffer()
void BlenderDisplayDriver::unmap_texture_buffer()
{
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
@@ -442,9 +405,9 @@ void BlenderGPUDisplay::do_unmap_texture_buffer()
* Graphics interoperability.
*/
DeviceGraphicsInteropDestination BlenderGPUDisplay::do_graphics_interop_get()
BlenderDisplayDriver::GraphicsInterop BlenderDisplayDriver::graphics_interop_get()
{
DeviceGraphicsInteropDestination interop_dst;
GraphicsInterop interop_dst;
interop_dst.buffer_width = texture_.buffer_width;
interop_dst.buffer_height = texture_.buffer_height;
@@ -456,12 +419,12 @@ DeviceGraphicsInteropDestination BlenderGPUDisplay::do_graphics_interop_get()
return interop_dst;
}
void BlenderGPUDisplay::graphics_interop_activate()
void BlenderDisplayDriver::graphics_interop_activate()
{
gl_context_enable();
}
void BlenderGPUDisplay::graphics_interop_deactivate()
void BlenderDisplayDriver::graphics_interop_deactivate()
{
gl_context_disable();
}
@@ -470,17 +433,17 @@ void BlenderGPUDisplay::graphics_interop_deactivate()
* Drawing.
*/
void BlenderGPUDisplay::clear()
void BlenderDisplayDriver::clear()
{
texture_.need_clear = true;
}
void BlenderGPUDisplay::set_zoom(float zoom_x, float zoom_y)
void BlenderDisplayDriver::set_zoom(float zoom_x, float zoom_y)
{
zoom_ = make_float2(zoom_x, zoom_y);
}
void BlenderGPUDisplay::do_draw(const GPUDisplayParams &params)
void BlenderDisplayDriver::draw(const Params &params)
{
/* See do_update_begin() for why no locking is required here. */
const bool transparent = true; // TODO(sergey): Derive this from Film.
@@ -497,7 +460,7 @@ void BlenderGPUDisplay::do_draw(const GPUDisplayParams &params)
/* Texture is requested to be cleared and was not yet cleared.
*
* Do early return which should be equivalent of drawing all-zero texture.
* Watchout for the lock though so that the clear happening during update is properly
* Watch out for the lock though so that the clear happening during update is properly
* synchronized here. */
gl_context_mutex_.unlock();
return;
@@ -584,7 +547,7 @@ void BlenderGPUDisplay::do_draw(const GPUDisplayParams &params)
}
}
void BlenderGPUDisplay::gl_context_create()
void BlenderDisplayDriver::gl_context_create()
{
/* When rendering in viewport there is no render context available via engine.
* Check whether own context is to be created here.
@@ -613,7 +576,7 @@ void BlenderGPUDisplay::gl_context_create()
}
}
bool BlenderGPUDisplay::gl_context_enable()
bool BlenderDisplayDriver::gl_context_enable()
{
if (use_gl_context_) {
if (!gl_context_) {
@@ -628,7 +591,7 @@ bool BlenderGPUDisplay::gl_context_enable()
return true;
}
void BlenderGPUDisplay::gl_context_disable()
void BlenderDisplayDriver::gl_context_disable()
{
if (use_gl_context_) {
if (gl_context_) {
@@ -641,7 +604,7 @@ void BlenderGPUDisplay::gl_context_disable()
RE_engine_render_context_disable(reinterpret_cast<RenderEngine *>(b_engine_.ptr.data));
}
void BlenderGPUDisplay::gl_context_dispose()
void BlenderDisplayDriver::gl_context_dispose()
{
if (gl_context_) {
const bool drw_state = DRW_opengl_context_release();
@@ -653,7 +616,7 @@ void BlenderGPUDisplay::gl_context_dispose()
}
}
bool BlenderGPUDisplay::gl_draw_resources_ensure()
bool BlenderDisplayDriver::gl_draw_resources_ensure()
{
if (!texture_.gl_id) {
/* If there is no texture allocated, there is nothing to draw. Inform the draw call that it can
@@ -680,7 +643,7 @@ bool BlenderGPUDisplay::gl_draw_resources_ensure()
return true;
}
void BlenderGPUDisplay::gl_resources_destroy()
void BlenderDisplayDriver::gl_resources_destroy()
{
gl_context_enable();
@@ -703,7 +666,7 @@ void BlenderGPUDisplay::gl_resources_destroy()
gl_context_dispose();
}
bool BlenderGPUDisplay::gl_texture_resources_ensure()
bool BlenderDisplayDriver::gl_texture_resources_ensure()
{
if (texture_.creation_attempted) {
return texture_.is_created;
@@ -740,7 +703,7 @@ bool BlenderGPUDisplay::gl_texture_resources_ensure()
return true;
}
void BlenderGPUDisplay::texture_update_if_needed()
void BlenderDisplayDriver::texture_update_if_needed()
{
if (!texture_.need_update) {
return;
@@ -754,7 +717,7 @@ void BlenderGPUDisplay::texture_update_if_needed()
texture_.need_update = false;
}
void BlenderGPUDisplay::vertex_buffer_update(const GPUDisplayParams &params)
void BlenderDisplayDriver::vertex_buffer_update(const Params &params)
{
/* Invalidate old contents - avoids stalling if the buffer is still waiting in queue to be
* rendered. */
@@ -767,23 +730,23 @@ void BlenderGPUDisplay::vertex_buffer_update(const GPUDisplayParams &params)
vpointer[0] = 0.0f;
vpointer[1] = 0.0f;
vpointer[2] = params.offset.x;
vpointer[3] = params.offset.y;
vpointer[2] = params.full_offset.x;
vpointer[3] = params.full_offset.y;
vpointer[4] = 1.0f;
vpointer[5] = 0.0f;
vpointer[6] = (float)params.size.x + params.offset.x;
vpointer[7] = params.offset.y;
vpointer[6] = (float)params.size.x + params.full_offset.x;
vpointer[7] = params.full_offset.y;
vpointer[8] = 1.0f;
vpointer[9] = 1.0f;
vpointer[10] = (float)params.size.x + params.offset.x;
vpointer[11] = (float)params.size.y + params.offset.y;
vpointer[10] = (float)params.size.x + params.full_offset.x;
vpointer[11] = (float)params.size.y + params.full_offset.y;
vpointer[12] = 0.0f;
vpointer[13] = 1.0f;
vpointer[14] = params.offset.x;
vpointer[15] = (float)params.size.y + params.offset.y;
vpointer[14] = params.full_offset.x;
vpointer[15] = (float)params.size.y + params.full_offset.y;
glUnmapBuffer(GL_ARRAY_BUFFER);
}

View File

@@ -22,12 +22,14 @@
#include "RNA_blender_cpp.h"
#include "render/gpu_display.h"
#include "render/display_driver.h"
#include "util/util_thread.h"
#include "util/util_unique_ptr.h"
CCL_NAMESPACE_BEGIN
/* Base class of shader used for GPU display rendering. */
/* Base class of shader used for display driver rendering. */
class BlenderDisplayShader {
public:
static constexpr const char *position_attribute_name = "pos";
@@ -96,11 +98,11 @@ class BlenderDisplaySpaceShader : public BlenderDisplayShader {
uint shader_program_ = 0;
};
/* GPU display implementation which is specific for Blender viewport integration. */
class BlenderGPUDisplay : public GPUDisplay {
/* Display driver implementation which is specific for Blender viewport integration. */
class BlenderDisplayDriver : public DisplayDriver {
public:
BlenderGPUDisplay(BL::RenderEngine &b_engine, BL::Scene &b_scene);
~BlenderGPUDisplay();
BlenderDisplayDriver(BL::RenderEngine &b_engine, BL::Scene &b_scene);
~BlenderDisplayDriver();
virtual void graphics_interop_activate() override;
virtual void graphics_interop_deactivate() override;
@@ -110,22 +112,15 @@ class BlenderGPUDisplay : public GPUDisplay {
void set_zoom(float zoom_x, float zoom_y);
protected:
virtual bool do_update_begin(const GPUDisplayParams &params,
int texture_width,
int texture_height) override;
virtual void do_update_end() override;
virtual bool update_begin(const Params &params, int texture_width, int texture_height) override;
virtual void update_end() override;
virtual void do_copy_pixels_to_texture(const half4 *rgba_pixels,
int texture_x,
int texture_y,
int pixels_width,
int pixels_height) override;
virtual void do_draw(const GPUDisplayParams &params) override;
virtual half4 *map_texture_buffer() override;
virtual void unmap_texture_buffer() override;
virtual half4 *do_map_texture_buffer() override;
virtual void do_unmap_texture_buffer() override;
virtual GraphicsInterop graphics_interop_get() override;
virtual DeviceGraphicsInteropDestination do_graphics_interop_get() override;
virtual void draw(const Params &params) override;
/* Helper function which allocates new GPU context. */
void gl_context_create();
@@ -152,13 +147,13 @@ class BlenderGPUDisplay : public GPUDisplay {
* This buffer is used to render texture in the viewport.
*
* NOTE: The buffer needs to be bound. */
void vertex_buffer_update(const GPUDisplayParams &params);
void vertex_buffer_update(const Params &params);
BL::RenderEngine b_engine_;
/* OpenGL context which is used the render engine doesn't have its own. */
void *gl_context_ = nullptr;
/* The when Blender RenderEngine side context is not available and the GPUDisplay is to create
/* The when Blender RenderEngine side context is not available and the DisplayDriver is to create
* its own context. */
bool use_gl_context_ = false;
/* Mutex used to guard the `gl_context_`. */

View File

@@ -0,0 +1,127 @@
/*
* Copyright 2021 Blender Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "blender/blender_output_driver.h"
CCL_NAMESPACE_BEGIN
BlenderOutputDriver::BlenderOutputDriver(BL::RenderEngine &b_engine) : b_engine_(b_engine)
{
}
BlenderOutputDriver::~BlenderOutputDriver()
{
}
bool BlenderOutputDriver::read_render_tile(const Tile &tile)
{
/* Get render result. */
BL::RenderResult b_rr = b_engine_.begin_result(tile.offset.x,
tile.offset.y,
tile.size.x,
tile.size.y,
tile.layer.c_str(),
tile.view.c_str());
/* Can happen if the intersected rectangle gives 0 width or height. */
if (b_rr.ptr.data == NULL) {
return false;
}
BL::RenderResult::layers_iterator b_single_rlay;
b_rr.layers.begin(b_single_rlay);
/* layer will be missing if it was disabled in the UI */
if (b_single_rlay == b_rr.layers.end()) {
return false;
}
BL::RenderLayer b_rlay = *b_single_rlay;
vector<float> pixels(tile.size.x * tile.size.y * 4);
/* Copy each pass.
* TODO:copy only the required ones for better performance? */
for (BL::RenderPass &b_pass : b_rlay.passes) {
tile.set_pass_pixels(b_pass.name(), b_pass.channels(), (float *)b_pass.rect());
}
b_engine_.end_result(b_rr, false, false, false);
return true;
}
bool BlenderOutputDriver::update_render_tile(const Tile &tile)
{
/* Use final write for preview renders, otherwise render result wouldn't be be updated
* quickly on Blender side. For all other cases we use the display driver. */
if (b_engine_.is_preview()) {
write_render_tile(tile);
return true;
}
else {
/* Don't highlight full-frame tile. */
if (!(tile.size == tile.full_size)) {
b_engine_.tile_highlight_clear_all();
b_engine_.tile_highlight_set(tile.offset.x, tile.offset.y, tile.size.x, tile.size.y, true);
}
return false;
}
}
void BlenderOutputDriver::write_render_tile(const Tile &tile)
{
b_engine_.tile_highlight_clear_all();
/* Get render result. */
BL::RenderResult b_rr = b_engine_.begin_result(tile.offset.x,
tile.offset.y,
tile.size.x,
tile.size.y,
tile.layer.c_str(),
tile.view.c_str());
/* Can happen if the intersected rectangle gives 0 width or height. */
if (b_rr.ptr.data == NULL) {
return;
}
BL::RenderResult::layers_iterator b_single_rlay;
b_rr.layers.begin(b_single_rlay);
/* Layer will be missing if it was disabled in the UI. */
if (b_single_rlay == b_rr.layers.end()) {
return;
}
BL::RenderLayer b_rlay = *b_single_rlay;
vector<float> pixels(tile.size.x * tile.size.y * 4);
/* Copy each pass. */
for (BL::RenderPass &b_pass : b_rlay.passes) {
if (!tile.get_pass_pixels(b_pass.name(), b_pass.channels(), &pixels[0])) {
memset(&pixels[0], 0, pixels.size() * sizeof(float));
}
b_pass.rect(&pixels[0]);
}
b_engine_.end_result(b_rr, true, false, true);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2021 Blender Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "MEM_guardedalloc.h"
#include "RNA_blender_cpp.h"
#include "render/output_driver.h"
CCL_NAMESPACE_BEGIN
class BlenderOutputDriver : public OutputDriver {
public:
BlenderOutputDriver(BL::RenderEngine &b_engine);
~BlenderOutputDriver();
virtual void write_render_tile(const Tile &tile) override;
virtual bool update_render_tile(const Tile &tile) override;
virtual bool read_render_tile(const Tile &tile) override;
protected:
BL::RenderEngine b_engine_;
};
CCL_NAMESPACE_END

View File

@@ -42,7 +42,8 @@
#include "util/util_progress.h"
#include "util/util_time.h"
#include "blender/blender_gpu_display.h"
#include "blender/blender_display_driver.h"
#include "blender/blender_output_driver.h"
#include "blender/blender_session.h"
#include "blender/blender_sync.h"
#include "blender/blender_util.h"
@@ -157,11 +158,13 @@ void BlenderSession::create_session()
b_v3d, b_rv3d, scene->camera, width, height);
session->reset(session_params, buffer_params);
/* Create GPU display. */
/* Create GPU display.
* TODO(sergey): Investigate whether DisplayDriver can be used for the preview as well. */
if (!b_engine.is_preview() && !headless) {
unique_ptr<BlenderGPUDisplay> gpu_display = make_unique<BlenderGPUDisplay>(b_engine, b_scene);
gpu_display_ = gpu_display.get();
session->set_gpu_display(move(gpu_display));
unique_ptr<BlenderDisplayDriver> display_driver = make_unique<BlenderDisplayDriver>(b_engine,
b_scene);
display_driver_ = display_driver.get();
session->set_display_driver(move(display_driver));
}
/* Viewport and preview (as in, material preview) does not do tiled rendering, so can inform
@@ -278,96 +281,6 @@ void BlenderSession::free_session()
session = nullptr;
}
void BlenderSession::read_render_tile()
{
const int2 tile_offset = session->get_render_tile_offset();
const int2 tile_size = session->get_render_tile_size();
/* get render result */
BL::RenderResult b_rr = b_engine.begin_result(tile_offset.x,
tile_offset.y,
tile_size.x,
tile_size.y,
b_rlay_name.c_str(),
b_rview_name.c_str());
/* can happen if the intersected rectangle gives 0 width or height */
if (b_rr.ptr.data == NULL) {
return;
}
BL::RenderResult::layers_iterator b_single_rlay;
b_rr.layers.begin(b_single_rlay);
/* layer will be missing if it was disabled in the UI */
if (b_single_rlay == b_rr.layers.end())
return;
BL::RenderLayer b_rlay = *b_single_rlay;
vector<float> pixels(tile_size.x * tile_size.y * 4);
/* Copy each pass.
* TODO:copy only the required ones for better performance? */
for (BL::RenderPass &b_pass : b_rlay.passes) {
session->set_render_tile_pixels(b_pass.name(), b_pass.channels(), (float *)b_pass.rect());
}
b_engine.end_result(b_rr, false, false, false);
}
void BlenderSession::write_render_tile()
{
const int2 tile_offset = session->get_render_tile_offset();
const int2 tile_size = session->get_render_tile_size();
const string_view render_layer_name = session->get_render_tile_layer();
const string_view render_view_name = session->get_render_tile_view();
b_engine.tile_highlight_clear_all();
/* get render result */
BL::RenderResult b_rr = b_engine.begin_result(tile_offset.x,
tile_offset.y,
tile_size.x,
tile_size.y,
render_layer_name.c_str(),
render_view_name.c_str());
/* can happen if the intersected rectangle gives 0 width or height */
if (b_rr.ptr.data == NULL) {
return;
}
BL::RenderResult::layers_iterator b_single_rlay;
b_rr.layers.begin(b_single_rlay);
/* layer will be missing if it was disabled in the UI */
if (b_single_rlay == b_rr.layers.end()) {
return;
}
BL::RenderLayer b_rlay = *b_single_rlay;
write_render_result(b_rlay);
b_engine.end_result(b_rr, true, false, true);
}
void BlenderSession::update_render_tile()
{
if (!session->has_multiple_render_tiles()) {
/* Don't highlight full-frame tile. */
return;
}
const int2 tile_offset = session->get_render_tile_offset();
const int2 tile_size = session->get_render_tile_size();
b_engine.tile_highlight_clear_all();
b_engine.tile_highlight_set(tile_offset.x, tile_offset.y, tile_size.x, tile_size.y, true);
}
void BlenderSession::full_buffer_written(string_view filename)
{
full_buffer_files_.emplace_back(filename);
@@ -441,18 +354,8 @@ void BlenderSession::render(BL::Depsgraph &b_depsgraph_)
return;
}
/* set callback to write out render results */
session->write_render_tile_cb = [&]() { write_render_tile(); };
/* Use final write for preview renders, otherwise render result wouldn't be be updated on Blender
* side. */
/* TODO(sergey): Investigate whether GPUDisplay can be used for the preview as well. */
if (b_engine.is_preview()) {
session->update_render_tile_cb = [&]() { write_render_tile(); };
}
else {
session->update_render_tile_cb = [&]() { update_render_tile(); };
}
/* Create driver to write out render results. */
session->set_output_driver(make_unique<BlenderOutputDriver>(b_engine));
session->full_buffer_written_cb = [&](string_view filename) { full_buffer_written(filename); };
@@ -598,9 +501,8 @@ void BlenderSession::render_frame_finish()
path_remove(filename);
}
/* clear callback */
session->write_render_tile_cb = function_null;
session->update_render_tile_cb = function_null;
/* Clear driver. */
session->set_output_driver(nullptr);
session->full_buffer_written_cb = function_null;
}
@@ -706,9 +608,8 @@ void BlenderSession::bake(BL::Depsgraph &b_depsgraph_,
pass->set_type(bake_type_to_pass(bake_type, bake_filter));
pass->set_include_albedo((bake_filter & BL::BakeSettings::pass_filter_COLOR));
session->read_render_tile_cb = [&]() { read_render_tile(); };
session->write_render_tile_cb = [&]() { write_render_tile(); };
session->set_gpu_display(nullptr);
session->set_display_driver(nullptr);
session->set_output_driver(make_unique<BlenderOutputDriver>(b_engine));
if (!session->progress.get_cancel()) {
/* Sync scene. */
@@ -751,43 +652,7 @@ void BlenderSession::bake(BL::Depsgraph &b_depsgraph_,
session->wait();
}
session->read_render_tile_cb = function_null;
session->write_render_tile_cb = function_null;
}
void BlenderSession::write_render_result(BL::RenderLayer &b_rlay)
{
if (!session->copy_render_tile_from_device()) {
return;
}
const int2 tile_size = session->get_render_tile_size();
vector<float> pixels(tile_size.x * tile_size.y * 4);
/* Copy each pass. */
for (BL::RenderPass &b_pass : b_rlay.passes) {
if (!session->get_render_tile_pixels(b_pass.name(), b_pass.channels(), &pixels[0])) {
memset(&pixels[0], 0, pixels.size() * sizeof(float));
}
b_pass.rect(&pixels[0]);
}
}
void BlenderSession::update_render_result(BL::RenderLayer &b_rlay)
{
if (!session->copy_render_tile_from_device()) {
return;
}
const int2 tile_size = session->get_render_tile_size();
vector<float> pixels(tile_size.x * tile_size.y * 4);
/* Copy combined pass. */
BL::RenderPass b_combined_pass(b_rlay.passes.find_by_name("Combined", b_rview_name.c_str()));
if (session->get_render_tile_pixels("Combined", b_combined_pass.channels(), &pixels[0])) {
b_combined_pass.rect(&pixels[0]);
}
session->set_output_driver(nullptr);
}
void BlenderSession::synchronize(BL::Depsgraph &b_depsgraph_)
@@ -895,7 +760,7 @@ void BlenderSession::draw(BL::SpaceImageEditor &space_image)
}
BL::Array<float, 2> zoom = space_image.zoom();
gpu_display_->set_zoom(zoom[0], zoom[1]);
display_driver_->set_zoom(zoom[0], zoom[1]);
session->draw();
}

View File

@@ -29,7 +29,7 @@
CCL_NAMESPACE_BEGIN
class BlenderGPUDisplay;
class BlenderDisplayDriver;
class BlenderSync;
class ImageMetaData;
class Scene;
@@ -70,20 +70,7 @@ class BlenderSession {
const int bake_width,
const int bake_height);
void write_render_result(BL::RenderLayer &b_rlay);
void write_render_tile();
void update_render_tile();
void full_buffer_written(string_view filename);
/* update functions are used to update display buffer only after sample was rendered
* only needed for better visual feedback */
void update_render_result(BL::RenderLayer &b_rlay);
/* read functions for baking input */
void read_render_tile();
/* interactive updates */
void synchronize(BL::Depsgraph &b_depsgraph);
@@ -164,8 +151,8 @@ class BlenderSession {
int last_pass_index = -1;
} draw_state_;
/* NOTE: The BlenderSession references the GPU display. */
BlenderGPUDisplay *gpu_display_ = nullptr;
/* NOTE: The BlenderSession references the display driver. */
BlenderDisplayDriver *display_driver_ = nullptr;
vector<string> full_buffer_files_;
};

View File

@@ -279,7 +279,7 @@ static ShaderNode *add_node(Scene *scene,
array<float3> curve_mapping_curves;
float min_x, max_x;
curvemapping_color_to_array(mapping, curve_mapping_curves, RAMP_TABLE_SIZE, true);
curvemapping_minmax(mapping, true, &min_x, &max_x);
curvemapping_minmax(mapping, 4, &min_x, &max_x);
curves->set_min_x(min_x);
curves->set_max_x(max_x);
curves->set_curves(curve_mapping_curves);
@@ -292,12 +292,25 @@ static ShaderNode *add_node(Scene *scene,
array<float3> curve_mapping_curves;
float min_x, max_x;
curvemapping_color_to_array(mapping, curve_mapping_curves, RAMP_TABLE_SIZE, false);
curvemapping_minmax(mapping, false, &min_x, &max_x);
curvemapping_minmax(mapping, 3, &min_x, &max_x);
curves->set_min_x(min_x);
curves->set_max_x(max_x);
curves->set_curves(curve_mapping_curves);
node = curves;
}
else if (b_node.is_a(&RNA_ShaderNodeFloatCurve)) {
BL::ShaderNodeFloatCurve b_curve_node(b_node);
BL::CurveMapping mapping(b_curve_node.mapping());
FloatCurveNode *curve = graph->create_node<FloatCurveNode>();
array<float> curve_mapping_curve;
float min_x, max_x;
curvemapping_float_to_array(mapping, curve_mapping_curve, RAMP_TABLE_SIZE);
curvemapping_minmax(mapping, 1, &min_x, &max_x);
curve->set_min_x(min_x);
curve->set_max_x(max_x);
curve->set_curve(curve_mapping_curve);
node = curve;
}
else if (b_node.is_a(&RNA_ShaderNodeValToRGB)) {
RGBRampNode *ramp = graph->create_node<RGBRampNode>();
BL::ShaderNodeValToRGB b_ramp_node(b_node);

View File

@@ -171,12 +171,11 @@ static inline void curvemap_minmax_curve(/*const*/ BL::CurveMap &curve, float *m
}
static inline void curvemapping_minmax(/*const*/ BL::CurveMapping &cumap,
bool rgb_curve,
int num_curves,
float *min_x,
float *max_x)
{
// const int num_curves = cumap.curves.length(); /* Gives linking error so far. */
const int num_curves = rgb_curve ? 4 : 3;
*min_x = FLT_MAX;
*max_x = -FLT_MAX;
for (int i = 0; i < num_curves; ++i) {
@@ -196,6 +195,28 @@ static inline void curvemapping_to_array(BL::CurveMapping &cumap, array<float> &
}
}
static inline void curvemapping_float_to_array(BL::CurveMapping &cumap,
array<float> &data,
int size)
{
float min = 0.0f, max = 1.0f;
curvemapping_minmax(cumap, 1, &min, &max);
const float range = max - min;
cumap.update();
BL::CurveMap map = cumap.curves[0];
data.resize(size);
for (int i = 0; i < size; i++) {
float t = min + (float)i / (float)(size - 1) * range;
data[i] = cumap.evaluate(map, t);
}
}
static inline void curvemapping_color_to_array(BL::CurveMapping &cumap,
array<float3> &data,
int size,
@@ -214,7 +235,8 @@ static inline void curvemapping_color_to_array(BL::CurveMapping &cumap,
*
* There might be some better estimations here tho.
*/
curvemapping_minmax(cumap, rgb_curve, &min_x, &max_x);
const int num_curves = rgb_curve ? 4 : 3;
curvemapping_minmax(cumap, num_curves, &min_x, &max_x);
const float range_x = max_x - min_x;

View File

@@ -37,14 +37,15 @@ CUDADeviceGraphicsInterop::~CUDADeviceGraphicsInterop()
}
}
void CUDADeviceGraphicsInterop::set_destination(
const DeviceGraphicsInteropDestination &destination)
void CUDADeviceGraphicsInterop::set_display_interop(
const DisplayDriver::GraphicsInterop &display_interop)
{
const int64_t new_buffer_area = int64_t(destination.buffer_width) * destination.buffer_height;
const int64_t new_buffer_area = int64_t(display_interop.buffer_width) *
display_interop.buffer_height;
need_clear_ = destination.need_clear;
need_clear_ = display_interop.need_clear;
if (opengl_pbo_id_ == destination.opengl_pbo_id && buffer_area_ == new_buffer_area) {
if (opengl_pbo_id_ == display_interop.opengl_pbo_id && buffer_area_ == new_buffer_area) {
return;
}
@@ -55,12 +56,12 @@ void CUDADeviceGraphicsInterop::set_destination(
}
const CUresult result = cuGraphicsGLRegisterBuffer(
&cu_graphics_resource_, destination.opengl_pbo_id, CU_GRAPHICS_MAP_RESOURCE_FLAGS_NONE);
&cu_graphics_resource_, display_interop.opengl_pbo_id, CU_GRAPHICS_MAP_RESOURCE_FLAGS_NONE);
if (result != CUDA_SUCCESS) {
LOG(ERROR) << "Error registering OpenGL buffer: " << cuewErrorString(result);
}
opengl_pbo_id_ = destination.opengl_pbo_id;
opengl_pbo_id_ = display_interop.opengl_pbo_id;
buffer_area_ = new_buffer_area;
}

View File

@@ -41,7 +41,7 @@ class CUDADeviceGraphicsInterop : public DeviceGraphicsInterop {
CUDADeviceGraphicsInterop &operator=(const CUDADeviceGraphicsInterop &other) = delete;
CUDADeviceGraphicsInterop &operator=(CUDADeviceGraphicsInterop &&other) = delete;
virtual void set_destination(const DeviceGraphicsInteropDestination &destination) override;
virtual void set_display_interop(const DisplayDriver::GraphicsInterop &display_interop) override;
virtual device_ptr map() override;
virtual void unmap() override;

View File

@@ -16,25 +16,12 @@
#pragma once
#include "render/display_driver.h"
#include "util/util_types.h"
CCL_NAMESPACE_BEGIN
/* Information about interoperability destination.
* Is provided by the GPUDisplay. */
class DeviceGraphicsInteropDestination {
public:
/* Dimensions of the buffer, in pixels. */
int buffer_width = 0;
int buffer_height = 0;
/* OpenGL pixel buffer object. */
int opengl_pbo_id = 0;
/* Clear the entire destination before doing partial write to it. */
bool need_clear = false;
};
/* Device-side graphics interoperability support.
*
* Takes care of holding all the handlers needed by the device to implement interoperability with
@@ -46,7 +33,7 @@ class DeviceGraphicsInterop {
/* Update this device-side graphics interoperability object with the given destination resource
* information. */
virtual void set_destination(const DeviceGraphicsInteropDestination &destination) = 0;
virtual void set_display_interop(const DisplayDriver::GraphicsInterop &display_interop) = 0;
virtual device_ptr map() = 0;
virtual void unmap() = 0;

View File

@@ -37,11 +37,15 @@ HIPDeviceGraphicsInterop::~HIPDeviceGraphicsInterop()
}
}
void HIPDeviceGraphicsInterop::set_destination(const DeviceGraphicsInteropDestination &destination)
void HIPDeviceGraphicsInterop::set_display_interop(
const DisplayDriver::GraphicsInterop &display_interop)
{
const int64_t new_buffer_area = int64_t(destination.buffer_width) * destination.buffer_height;
const int64_t new_buffer_area = int64_t(display_interop.buffer_width) *
display_interop.buffer_height;
if (opengl_pbo_id_ == destination.opengl_pbo_id && buffer_area_ == new_buffer_area) {
need_clear_ = display_interop.need_clear;
if (opengl_pbo_id_ == display_interop.opengl_pbo_id && buffer_area_ == new_buffer_area) {
return;
}
@@ -52,12 +56,12 @@ void HIPDeviceGraphicsInterop::set_destination(const DeviceGraphicsInteropDestin
}
const hipError_t result = hipGraphicsGLRegisterBuffer(
&hip_graphics_resource_, destination.opengl_pbo_id, hipGraphicsRegisterFlagsNone);
&hip_graphics_resource_, display_interop.opengl_pbo_id, hipGraphicsRegisterFlagsNone);
if (result != hipSuccess) {
LOG(ERROR) << "Error registering OpenGL buffer: " << hipewErrorString(result);
}
opengl_pbo_id_ = destination.opengl_pbo_id;
opengl_pbo_id_ = display_interop.opengl_pbo_id;
buffer_area_ = new_buffer_area;
}
@@ -77,6 +81,14 @@ device_ptr HIPDeviceGraphicsInterop::map()
hip_device_assert(
device_, hipGraphicsResourceGetMappedPointer(&hip_buffer, &bytes, hip_graphics_resource_));
if (need_clear_) {
hip_device_assert(
device_,
hipMemsetD8Async(static_cast<hipDeviceptr_t>(hip_buffer), 0, bytes, queue_->stream()));
need_clear_ = false;
}
return static_cast<device_ptr>(hip_buffer);
}

View File

@@ -39,7 +39,7 @@ class HIPDeviceGraphicsInterop : public DeviceGraphicsInterop {
HIPDeviceGraphicsInterop &operator=(const HIPDeviceGraphicsInterop &other) = delete;
HIPDeviceGraphicsInterop &operator=(HIPDeviceGraphicsInterop &&other) = delete;
virtual void set_destination(const DeviceGraphicsInteropDestination &destination) override;
virtual void set_display_interop(const DisplayDriver::GraphicsInterop &display_interop) override;
virtual device_ptr map() override;
virtual void unmap() override;
@@ -53,6 +53,9 @@ class HIPDeviceGraphicsInterop : public DeviceGraphicsInterop {
/* Buffer area in pixels of the corresponding PBO. */
int64_t buffer_area_ = 0;
/* The destination was requested to be cleared. */
bool need_clear_ = false;
hipGraphicsResource hip_graphics_resource_ = nullptr;
};

View File

@@ -28,7 +28,7 @@ void HIPDeviceKernels::load(HIPDevice *device)
for (int i = 0; i < (int)DEVICE_KERNEL_NUM; i++) {
HIPDeviceKernel &kernel = kernels_[i];
/* No megakernel used for GPU. */
/* No mega-kernel used for GPU. */
if (i == DEVICE_KERNEL_INTEGRATOR_MEGAKERNEL) {
continue;
}

View File

@@ -1419,7 +1419,7 @@ void OptiXDevice::build_bvh(BVH *bvh, Progress &progress, bool refit)
}
else {
/* Can disable __anyhit__kernel_optix_visibility_test by default (except for thick curves,
* since it needs to filter out endcaps there).
* since it needs to filter out end-caps there).
* It is enabled where necessary (visibility mask exceeds 8 bits or the other any-hit
* programs like __anyhit__kernel_optix_shadow_all_hit) via OPTIX_RAY_FLAG_ENFORCE_ANYHIT.
*/

View File

@@ -27,6 +27,8 @@ set(SRC
pass_accessor.cpp
pass_accessor_cpu.cpp
pass_accessor_gpu.cpp
path_trace_display.cpp
path_trace_tile.cpp
path_trace_work.cpp
path_trace_work_cpu.cpp
path_trace_work_gpu.cpp
@@ -47,6 +49,8 @@ set(SRC_HEADERS
pass_accessor.h
pass_accessor_cpu.h
pass_accessor_gpu.h
path_trace_display.h
path_trace_tile.h
path_trace_work.h
path_trace_work_cpu.h
path_trace_work_gpu.h

View File

@@ -19,8 +19,9 @@
#include "device/cpu/device.h"
#include "device/device.h"
#include "integrator/pass_accessor.h"
#include "integrator/path_trace_display.h"
#include "integrator/path_trace_tile.h"
#include "integrator/render_scheduler.h"
#include "render/gpu_display.h"
#include "render/pass.h"
#include "render/scene.h"
#include "render/tile.h"
@@ -67,11 +68,11 @@ PathTrace::PathTrace(Device *device,
PathTrace::~PathTrace()
{
/* Destroy any GPU resource which was used for graphics interop.
* Need to have access to the GPUDisplay as it is the only source of drawing context which is
* used for interop. */
if (gpu_display_) {
* Need to have access to the PathTraceDisplay as it is the only source of drawing context which
* is used for interop. */
if (display_) {
for (auto &&path_trace_work : path_trace_works_) {
path_trace_work->destroy_gpu_resources(gpu_display_.get());
path_trace_work->destroy_gpu_resources(display_.get());
}
}
}
@@ -94,7 +95,7 @@ bool PathTrace::ready_to_reset()
{
/* The logic here is optimized for the best feedback in the viewport, which implies having a GPU
* display. Of there is no such display, the logic here will break. */
DCHECK(gpu_display_);
DCHECK(display_);
/* The logic here tries to provide behavior which feels the most interactive feel to artists.
* General idea is to be able to reset as quickly as possible, while still providing interactive
@@ -126,8 +127,8 @@ void PathTrace::reset(const BufferParams &full_params, const BufferParams &big_t
/* NOTE: GPU display checks for buffer modification and avoids unnecessary re-allocation.
* It is requires to inform about reset whenever it happens, so that the redraw state tracking is
* properly updated. */
if (gpu_display_) {
gpu_display_->reset(full_params);
if (display_) {
display_->reset(full_params);
}
render_state_.has_denoised_result = false;
@@ -535,25 +536,35 @@ void PathTrace::denoise(const RenderWork &render_work)
render_scheduler_.report_denoise_time(render_work, time_dt() - start_time);
}
void PathTrace::set_gpu_display(unique_ptr<GPUDisplay> gpu_display)
void PathTrace::set_output_driver(unique_ptr<OutputDriver> driver)
{
gpu_display_ = move(gpu_display);
output_driver_ = move(driver);
}
void PathTrace::clear_gpu_display()
void PathTrace::set_display_driver(unique_ptr<DisplayDriver> driver)
{
if (gpu_display_) {
gpu_display_->clear();
if (driver) {
display_ = make_unique<PathTraceDisplay>(move(driver));
}
else {
display_ = nullptr;
}
}
void PathTrace::clear_display()
{
if (display_) {
display_->clear();
}
}
void PathTrace::draw()
{
if (!gpu_display_) {
if (!display_) {
return;
}
did_draw_after_reset_ |= gpu_display_->draw();
did_draw_after_reset_ |= display_->draw();
}
void PathTrace::update_display(const RenderWork &render_work)
@@ -562,31 +573,32 @@ void PathTrace::update_display(const RenderWork &render_work)
return;
}
if (!gpu_display_ && !tile_buffer_update_cb) {
if (!display_ && !output_driver_) {
VLOG(3) << "Ignore display update.";
return;
}
if (full_params_.width == 0 || full_params_.height == 0) {
VLOG(3) << "Skipping GPUDisplay update due to 0 size of the render buffer.";
VLOG(3) << "Skipping PathTraceDisplay update due to 0 size of the render buffer.";
return;
}
const double start_time = time_dt();
if (tile_buffer_update_cb) {
if (output_driver_) {
VLOG(3) << "Invoke buffer update callback.";
tile_buffer_update_cb();
PathTraceTile tile(*this);
output_driver_->update_render_tile(tile);
}
if (gpu_display_) {
if (display_) {
VLOG(3) << "Perform copy to GPUDisplay work.";
const int resolution_divider = render_work.resolution_divider;
const int texture_width = max(1, full_params_.width / resolution_divider);
const int texture_height = max(1, full_params_.height / resolution_divider);
if (!gpu_display_->update_begin(texture_width, texture_height)) {
if (!display_->update_begin(texture_width, texture_height)) {
LOG(ERROR) << "Error beginning GPUDisplay update.";
return;
}
@@ -600,10 +612,10 @@ void PathTrace::update_display(const RenderWork &render_work)
* all works in parallel. */
const int num_samples = get_num_samples_in_buffer();
for (auto &&path_trace_work : path_trace_works_) {
path_trace_work->copy_to_gpu_display(gpu_display_.get(), pass_mode, num_samples);
path_trace_work->copy_to_display(display_.get(), pass_mode, num_samples);
}
gpu_display_->update_end();
display_->update_end();
}
render_scheduler_.report_display_update_time(render_work, time_dt() - start_time);
@@ -753,20 +765,26 @@ bool PathTrace::is_cancel_requested()
void PathTrace::tile_buffer_write()
{
if (!tile_buffer_write_cb) {
if (!output_driver_) {
return;
}
tile_buffer_write_cb();
PathTraceTile tile(*this);
output_driver_->write_render_tile(tile);
}
void PathTrace::tile_buffer_read()
{
if (!tile_buffer_read_cb) {
if (!device_scene_->data.bake.use) {
return;
}
if (tile_buffer_read_cb()) {
if (!output_driver_) {
return;
}
PathTraceTile tile(*this);
if (output_driver_->read_render_tile(tile)) {
tbb::parallel_for_each(path_trace_works_, [](unique_ptr<PathTraceWork> &path_trace_work) {
path_trace_work->copy_render_buffers_to_device();
});
@@ -1005,6 +1023,11 @@ int2 PathTrace::get_render_tile_offset() const
return make_int2(tile.x, tile.y);
}
int2 PathTrace::get_render_size() const
{
return tile_manager_.get_size();
}
const BufferParams &PathTrace::get_render_tile_params() const
{
if (full_frame_state_.render_buffers) {

View File

@@ -31,12 +31,14 @@ CCL_NAMESPACE_BEGIN
class AdaptiveSampling;
class Device;
class DeviceScene;
class DisplayDriver;
class Film;
class RenderBuffers;
class RenderScheduler;
class RenderWork;
class PathTraceDisplay;
class OutputDriver;
class Progress;
class GPUDisplay;
class TileManager;
/* PathTrace class takes care of kernel graph and scheduling on a (multi)device. It takes care of
@@ -98,13 +100,16 @@ class PathTrace {
* Use this to configure the adaptive sampler before rendering any samples. */
void set_adaptive_sampling(const AdaptiveSampling &adaptive_sampling);
/* Set GPU display which takes care of drawing the render result. */
void set_gpu_display(unique_ptr<GPUDisplay> gpu_display);
/* Sets output driver for render buffer output. */
void set_output_driver(unique_ptr<OutputDriver> driver);
/* Clear the GPU display by filling it in with all zeroes. */
void clear_gpu_display();
/* Set display driver for interactive render buffer display. */
void set_display_driver(unique_ptr<DisplayDriver> driver);
/* Perform drawing of the current state of the GPUDisplay. */
/* Clear the display buffer by filling it in with all zeroes. */
void clear_display();
/* Perform drawing of the current state of the DisplayDriver. */
void draw();
/* Cancel rendering process as soon as possible, without waiting for full tile to be sampled.
@@ -157,6 +162,7 @@ class PathTrace {
* instead. */
int2 get_render_tile_size() const;
int2 get_render_tile_offset() const;
int2 get_render_size() const;
/* Get buffer parameters of the current tile.
*
@@ -168,18 +174,6 @@ class PathTrace {
* times, and so on. */
string full_report() const;
/* Callback which communicates an updates state of the render buffer of the current big tile.
* Is called during path tracing to communicate work-in-progress state of the final buffer. */
function<void(void)> tile_buffer_update_cb;
/* Callback which communicates final rendered buffer. Is called after path-tracing is done. */
function<void(void)> tile_buffer_write_cb;
/* Callback which initializes rendered buffer. Is called before path-tracing starts.
*
* This is used for baking. */
function<bool(void)> tile_buffer_read_cb;
/* Callback which is called to report current rendering progress.
*
* It is supposed to be cheaper than buffer update/write, hence can be called more often.
@@ -252,7 +246,11 @@ class PathTrace {
RenderScheduler &render_scheduler_;
TileManager &tile_manager_;
unique_ptr<GPUDisplay> gpu_display_;
/* Display driver for interactive render buffer display. */
unique_ptr<PathTraceDisplay> display_;
/* Output driver to write render buffer to. */
unique_ptr<OutputDriver> output_driver_;
/* Per-compute device descriptors of work which is responsible for path tracing on its configured
* device. */

View File

@@ -14,20 +14,25 @@
* limitations under the License.
*/
#include "render/gpu_display.h"
#include "integrator/path_trace_display.h"
#include "render/buffers.h"
#include "util/util_logging.h"
CCL_NAMESPACE_BEGIN
void GPUDisplay::reset(const BufferParams &buffer_params)
PathTraceDisplay::PathTraceDisplay(unique_ptr<DisplayDriver> driver) : driver_(move(driver))
{
}
void PathTraceDisplay::reset(const BufferParams &buffer_params)
{
thread_scoped_lock lock(mutex_);
const GPUDisplayParams old_params = params_;
const DisplayDriver::Params old_params = params_;
params_.offset = make_int2(buffer_params.full_x, buffer_params.full_y);
params_.full_offset = make_int2(buffer_params.full_x, buffer_params.full_y);
params_.full_size = make_int2(buffer_params.full_width, buffer_params.full_height);
params_.size = make_int2(buffer_params.width, buffer_params.height);
@@ -44,7 +49,7 @@ void GPUDisplay::reset(const BufferParams &buffer_params)
texture_state_.is_outdated = true;
}
void GPUDisplay::mark_texture_updated()
void PathTraceDisplay::mark_texture_updated()
{
texture_state_.is_outdated = false;
texture_state_.is_usable = true;
@@ -54,7 +59,7 @@ void GPUDisplay::mark_texture_updated()
* Update procedure.
*/
bool GPUDisplay::update_begin(int texture_width, int texture_height)
bool PathTraceDisplay::update_begin(int texture_width, int texture_height)
{
DCHECK(!update_state_.is_active);
@@ -66,15 +71,15 @@ bool GPUDisplay::update_begin(int texture_width, int texture_height)
/* Get parameters within a mutex lock, to avoid reset() modifying them at the same time.
* The update itself is non-blocking however, for better performance and to avoid
* potential deadlocks due to locks held by the subclass. */
GPUDisplayParams params;
DisplayDriver::Params params;
{
thread_scoped_lock lock(mutex_);
params = params_;
texture_state_.size = make_int2(texture_width, texture_height);
}
if (!do_update_begin(params, texture_width, texture_height)) {
LOG(ERROR) << "GPUDisplay implementation could not begin update.";
if (!driver_->update_begin(params, texture_width, texture_height)) {
LOG(ERROR) << "PathTraceDisplay implementation could not begin update.";
return false;
}
@@ -83,7 +88,7 @@ bool GPUDisplay::update_begin(int texture_width, int texture_height)
return true;
}
void GPUDisplay::update_end()
void PathTraceDisplay::update_end()
{
DCHECK(update_state_.is_active);
@@ -92,12 +97,12 @@ void GPUDisplay::update_end()
return;
}
do_update_end();
driver_->update_end();
update_state_.is_active = false;
}
int2 GPUDisplay::get_texture_size() const
int2 PathTraceDisplay::get_texture_size() const
{
return texture_state_.size;
}
@@ -106,25 +111,54 @@ int2 GPUDisplay::get_texture_size() const
* Texture update from CPU buffer.
*/
void GPUDisplay::copy_pixels_to_texture(
void PathTraceDisplay::copy_pixels_to_texture(
const half4 *rgba_pixels, int texture_x, int texture_y, int pixels_width, int pixels_height)
{
DCHECK(update_state_.is_active);
if (!update_state_.is_active) {
LOG(ERROR) << "Attempt to copy pixels data outside of GPUDisplay update.";
LOG(ERROR) << "Attempt to copy pixels data outside of PathTraceDisplay update.";
return;
}
mark_texture_updated();
do_copy_pixels_to_texture(rgba_pixels, texture_x, texture_y, pixels_width, pixels_height);
/* This call copies pixels to a mapped texture buffer which is typically much cheaper from CPU
* time point of view than to copy data directly to a texture.
*
* The possible downside of this approach is that it might require a higher peak memory when
* doing partial updates of the texture (although, in practice even partial updates might peak
* with a full-frame buffer stored on the CPU if the GPU is currently occupied). */
half4 *mapped_rgba_pixels = map_texture_buffer();
if (!mapped_rgba_pixels) {
return;
}
const int texture_width = texture_state_.size.x;
const int texture_height = texture_state_.size.y;
if (texture_x == 0 && texture_y == 0 && pixels_width == texture_width &&
pixels_height == texture_height) {
const size_t size_in_bytes = sizeof(half4) * texture_width * texture_height;
memcpy(mapped_rgba_pixels, rgba_pixels, size_in_bytes);
}
else {
const half4 *rgba_row = rgba_pixels;
half4 *mapped_rgba_row = mapped_rgba_pixels + texture_y * texture_width + texture_x;
for (int y = 0; y < pixels_height;
++y, rgba_row += pixels_width, mapped_rgba_row += texture_width) {
memcpy(mapped_rgba_row, rgba_row, sizeof(half4) * pixels_width);
}
}
unmap_texture_buffer();
}
/* --------------------------------------------------------------------
* Texture buffer mapping.
*/
half4 *GPUDisplay::map_texture_buffer()
half4 *PathTraceDisplay::map_texture_buffer()
{
DCHECK(!texture_buffer_state_.is_mapped);
DCHECK(update_state_.is_active);
@@ -135,11 +169,11 @@ half4 *GPUDisplay::map_texture_buffer()
}
if (!update_state_.is_active) {
LOG(ERROR) << "Attempt to copy pixels data outside of GPUDisplay update.";
LOG(ERROR) << "Attempt to copy pixels data outside of PathTraceDisplay update.";
return nullptr;
}
half4 *mapped_rgba_pixels = do_map_texture_buffer();
half4 *mapped_rgba_pixels = driver_->map_texture_buffer();
if (mapped_rgba_pixels) {
texture_buffer_state_.is_mapped = true;
@@ -148,7 +182,7 @@ half4 *GPUDisplay::map_texture_buffer()
return mapped_rgba_pixels;
}
void GPUDisplay::unmap_texture_buffer()
void PathTraceDisplay::unmap_texture_buffer()
{
DCHECK(texture_buffer_state_.is_mapped);
@@ -160,14 +194,14 @@ void GPUDisplay::unmap_texture_buffer()
texture_buffer_state_.is_mapped = false;
mark_texture_updated();
do_unmap_texture_buffer();
driver_->unmap_texture_buffer();
}
/* --------------------------------------------------------------------
* Graphics interoperability.
*/
DeviceGraphicsInteropDestination GPUDisplay::graphics_interop_get()
DisplayDriver::GraphicsInterop PathTraceDisplay::graphics_interop_get()
{
DCHECK(!texture_buffer_state_.is_mapped);
DCHECK(update_state_.is_active);
@@ -175,38 +209,45 @@ DeviceGraphicsInteropDestination GPUDisplay::graphics_interop_get()
if (texture_buffer_state_.is_mapped) {
LOG(ERROR)
<< "Attempt to use graphics interoperability mode while the texture buffer is mapped.";
return DeviceGraphicsInteropDestination();
return DisplayDriver::GraphicsInterop();
}
if (!update_state_.is_active) {
LOG(ERROR) << "Attempt to use graphics interoperability outside of GPUDisplay update.";
return DeviceGraphicsInteropDestination();
LOG(ERROR) << "Attempt to use graphics interoperability outside of PathTraceDisplay update.";
return DisplayDriver::GraphicsInterop();
}
/* Assume that interop will write new values to the texture. */
mark_texture_updated();
return do_graphics_interop_get();
return driver_->graphics_interop_get();
}
void GPUDisplay::graphics_interop_activate()
void PathTraceDisplay::graphics_interop_activate()
{
driver_->graphics_interop_activate();
}
void GPUDisplay::graphics_interop_deactivate()
void PathTraceDisplay::graphics_interop_deactivate()
{
driver_->graphics_interop_deactivate();
}
/* --------------------------------------------------------------------
* Drawing.
*/
bool GPUDisplay::draw()
void PathTraceDisplay::clear()
{
driver_->clear();
}
bool PathTraceDisplay::draw()
{
/* Get parameters within a mutex lock, to avoid reset() modifying them at the same time.
* The drawing itself is non-blocking however, for better performance and to avoid
* potential deadlocks due to locks held by the subclass. */
GPUDisplayParams params;
DisplayDriver::Params params;
bool is_usable;
bool is_outdated;
@@ -218,7 +259,7 @@ bool GPUDisplay::draw()
}
if (is_usable) {
do_draw(params);
driver_->draw(params);
}
return !is_outdated;

View File

@@ -16,52 +16,30 @@
#pragma once
#include "device/device_graphics_interop.h"
#include "render/display_driver.h"
#include "util/util_half.h"
#include "util/util_thread.h"
#include "util/util_types.h"
#include "util/util_unique_ptr.h"
CCL_NAMESPACE_BEGIN
class BufferParams;
/* GPUDisplay class takes care of drawing render result in a viewport. The render result is stored
* in a GPU-side texture, which is updated from a path tracer and drawn by an application.
/* PathTraceDisplay is used for efficient render buffer display.
*
* The base GPUDisplay does some special texture state tracking, which allows render Session to
* make decisions on whether reset for an updated state is possible or not. This state should only
* be tracked in a base class and a particular implementation should not worry about it.
* The host applications implements a DisplayDriver, storing a render pass in a GPU-side
* textures. This texture is continuously updated by the path tracer and drawn by the host
* application.
*
* The subclasses should only implement the pure virtual methods, which allows them to not worry
* about parent method calls, which helps them to be as small and reliable as possible. */
* PathTraceDisplay is a wrapper around the DisplayDriver, adding thread safety, state tracking
* and error checking. */
class GPUDisplayParams {
class PathTraceDisplay {
public:
/* Offset of the display within a viewport.
* For example, set to a lower-bottom corner of border render in Blender's viewport. */
int2 offset = make_int2(0, 0);
/* Full viewport size.
*
* NOTE: Is not affected by the resolution divider. */
int2 full_size = make_int2(0, 0);
/* Effective viewport size.
* In the case of border render, size of the border rectangle.
*
* NOTE: Is not affected by the resolution divider. */
int2 size = make_int2(0, 0);
bool modified(const GPUDisplayParams &other) const
{
return !(offset == other.offset && full_size == other.full_size && size == other.size);
}
};
class GPUDisplay {
public:
GPUDisplay() = default;
virtual ~GPUDisplay() = default;
PathTraceDisplay(unique_ptr<DisplayDriver> driver);
virtual ~PathTraceDisplay() = default;
/* Reset the display for the new state of render session. Is called whenever session is reset,
* which happens on changes like viewport navigation or viewport dimension change.
@@ -69,11 +47,6 @@ class GPUDisplay {
* This call will configure parameters for a changed buffer and reset the texture state. */
void reset(const BufferParams &buffer_params);
const GPUDisplayParams &get_params() const
{
return params_;
}
/* --------------------------------------------------------------------
* Update procedure.
*
@@ -94,7 +67,8 @@ class GPUDisplay {
/* --------------------------------------------------------------------
* Texture update from CPU buffer.
*
* NOTE: The GPUDisplay should be marked for an update being in process with `update_begin()`.
* NOTE: The PathTraceDisplay should be marked for an update being in process with
* `update_begin()`.
*
* Most portable implementation, which must be supported by all platforms. Might not be the most
* efficient one.
@@ -115,7 +89,8 @@ class GPUDisplay {
* This functionality is used to update GPU-side texture content without need to maintain CPU
* side buffer on the caller.
*
* NOTE: The GPUDisplay should be marked for an update being in process with `update_begin()`.
* NOTE: The PathTraceDisplay should be marked for an update being in process with
* `update_begin()`.
*
* NOTE: Texture buffer can not be mapped while graphics interoperability is active. This means
* that `map_texture_buffer()` is not allowed between `graphics_interop_begin()` and
@@ -145,14 +120,14 @@ class GPUDisplay {
* that `graphics_interop_get()` is not allowed between `map_texture_buffer()` and
* `unmap_texture_buffer()` calls. */
/* Get GPUDisplay graphics interoperability information which acts as a destination for the
/* Get PathTraceDisplay graphics interoperability information which acts as a destination for the
* device API. */
DeviceGraphicsInteropDestination graphics_interop_get();
DisplayDriver::GraphicsInterop graphics_interop_get();
/* (De)activate GPU display for graphics interoperability outside of regular display update
* routines. */
virtual void graphics_interop_activate();
virtual void graphics_interop_deactivate();
void graphics_interop_activate();
void graphics_interop_deactivate();
/* --------------------------------------------------------------------
* Drawing.
@@ -168,42 +143,21 @@ class GPUDisplay {
* after clear will write new pixel values for an updating area, leaving everything else zeroed.
*
* If the GPU display supports graphics interoperability then the zeroing the display is to be
* delegated to the device via the `DeviceGraphicsInteropDestination`. */
virtual void clear() = 0;
* delegated to the device via the `DisplayDriver::GraphicsInterop`. */
void clear();
/* Draw the current state of the texture.
*
* Returns true if this call did draw an updated state of the texture. */
bool draw();
protected:
/* Implementation-specific calls which subclasses are to implement.
* These `do_foo()` method corresponds to their `foo()` calls, but they are purely virtual to
* simplify their particular implementation. */
virtual bool do_update_begin(const GPUDisplayParams &params,
int texture_width,
int texture_height) = 0;
virtual void do_update_end() = 0;
virtual void do_copy_pixels_to_texture(const half4 *rgba_pixels,
int texture_x,
int texture_y,
int pixels_width,
int pixels_height) = 0;
virtual half4 *do_map_texture_buffer() = 0;
virtual void do_unmap_texture_buffer() = 0;
/* Note that this might be called in parallel to do_update_begin() and do_update_end(),
* the subclass is responsible for appropriate mutex locks to avoid multiple threads
* editing and drawing the texture at the same time. */
virtual void do_draw(const GPUDisplayParams &params) = 0;
virtual DeviceGraphicsInteropDestination do_graphics_interop_get() = 0;
private:
/* Display driver implemented by the host application. */
unique_ptr<DisplayDriver> driver_;
/* Current display parameters */
thread_mutex mutex_;
GPUDisplayParams params_;
DisplayDriver::Params params_;
/* Mark texture as its content has been updated.
* Used from places which knows that the texture content has been brought up-to-date, so that the

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2021 Blender Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "integrator/path_trace_tile.h"
#include "integrator/pass_accessor_cpu.h"
#include "integrator/path_trace.h"
#include "render/buffers.h"
#include "render/film.h"
#include "render/pass.h"
#include "render/scene.h"
CCL_NAMESPACE_BEGIN
PathTraceTile::PathTraceTile(PathTrace &path_trace)
: OutputDriver::Tile(path_trace.get_render_tile_offset(),
path_trace.get_render_tile_size(),
path_trace.get_render_size(),
path_trace.get_render_tile_params().layer,
path_trace.get_render_tile_params().view),
path_trace_(path_trace),
copied_from_device_(false)
{
}
bool PathTraceTile::get_pass_pixels(const string_view pass_name,
const int num_channels,
float *pixels) const
{
/* NOTE: The code relies on a fact that session is fully update and no scene/buffer modification
* is happening while this function runs. */
if (!copied_from_device_) {
/* Copy from device on demand. */
path_trace_.copy_render_tile_from_device();
const_cast<PathTraceTile *>(this)->copied_from_device_ = true;
}
const BufferParams &buffer_params = path_trace_.get_render_tile_params();
const BufferPass *pass = buffer_params.find_pass(pass_name);
if (pass == nullptr) {
return false;
}
const bool has_denoised_result = path_trace_.has_denoised_result();
if (pass->mode == PassMode::DENOISED && !has_denoised_result) {
pass = buffer_params.find_pass(pass->type);
if (pass == nullptr) {
/* Happens when denoised result pass is requested but is never written by the kernel. */
return false;
}
}
pass = buffer_params.get_actual_display_pass(pass);
const float exposure = buffer_params.exposure;
const int num_samples = path_trace_.get_num_render_tile_samples();
PassAccessor::PassAccessInfo pass_access_info(*pass);
pass_access_info.use_approximate_shadow_catcher = buffer_params.use_approximate_shadow_catcher;
pass_access_info.use_approximate_shadow_catcher_background =
pass_access_info.use_approximate_shadow_catcher && !buffer_params.use_transparent_background;
const PassAccessorCPU pass_accessor(pass_access_info, exposure, num_samples);
const PassAccessor::Destination destination(pixels, num_channels);
return path_trace_.get_render_tile_pixels(pass_accessor, destination);
}
bool PathTraceTile::set_pass_pixels(const string_view pass_name,
const int num_channels,
const float *pixels) const
{
/* NOTE: The code relies on a fact that session is fully update and no scene/buffer modification
* is happening while this function runs. */
const BufferParams &buffer_params = path_trace_.get_render_tile_params();
const BufferPass *pass = buffer_params.find_pass(pass_name);
if (!pass) {
return false;
}
const float exposure = buffer_params.exposure;
const int num_samples = 1;
const PassAccessor::PassAccessInfo pass_access_info(*pass);
PassAccessorCPU pass_accessor(pass_access_info, exposure, num_samples);
PassAccessor::Source source(pixels, num_channels);
return path_trace_.set_render_tile_pixels(pass_accessor, source);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2021 Blender Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "render/output_driver.h"
CCL_NAMESPACE_BEGIN
/* PathTraceTile
*
* Implementation of OutputDriver::Tile interface for path tracer. */
class PathTrace;
class PathTraceTile : public OutputDriver::Tile {
public:
PathTraceTile(PathTrace &path_trace);
bool get_pass_pixels(const string_view pass_name, const int num_channels, float *pixels) const;
bool set_pass_pixels(const string_view pass_name,
const int num_channels,
const float *pixels) const;
private:
PathTrace &path_trace_;
bool copied_from_device_;
};
CCL_NAMESPACE_END

View File

@@ -16,12 +16,12 @@
#include "device/device.h"
#include "integrator/path_trace_display.h"
#include "integrator/path_trace_work.h"
#include "integrator/path_trace_work_cpu.h"
#include "integrator/path_trace_work_gpu.h"
#include "render/buffers.h"
#include "render/film.h"
#include "render/gpu_display.h"
#include "render/scene.h"
#include "kernel/kernel_types.h"
@@ -185,12 +185,12 @@ PassAccessor::PassAccessInfo PathTraceWork::get_display_pass_access_info(PassMod
return pass_access_info;
}
PassAccessor::Destination PathTraceWork::get_gpu_display_destination_template(
const GPUDisplay *gpu_display) const
PassAccessor::Destination PathTraceWork::get_display_destination_template(
const PathTraceDisplay *display) const
{
PassAccessor::Destination destination(film_->get_display_pass());
const int2 display_texture_size = gpu_display->get_texture_size();
const int2 display_texture_size = display->get_texture_size();
const int texture_x = effective_buffer_params_.full_x - effective_full_params_.full_x;
const int texture_y = effective_buffer_params_.full_y - effective_full_params_.full_y;

View File

@@ -28,7 +28,7 @@ class BufferParams;
class Device;
class DeviceScene;
class Film;
class GPUDisplay;
class PathTraceDisplay;
class RenderBuffers;
class PathTraceWork {
@@ -83,11 +83,9 @@ class PathTraceWork {
* noisy pass mode will be passed here when it is known that the buffer does not have denoised
* passes yet (because denoiser did not run). If the denoised pass is requested and denoiser is
* not used then this function will fall-back to the noisy pass instead. */
virtual void copy_to_gpu_display(GPUDisplay *gpu_display,
PassMode pass_mode,
int num_samples) = 0;
virtual void copy_to_display(PathTraceDisplay *display, PassMode pass_mode, int num_samples) = 0;
virtual void destroy_gpu_resources(GPUDisplay *gpu_display) = 0;
virtual void destroy_gpu_resources(PathTraceDisplay *display) = 0;
/* Copy data from/to given render buffers.
* Will copy pixels from a corresponding place (from multi-device point of view) of the render
@@ -162,8 +160,8 @@ class PathTraceWork {
/* Get destination which offset and stride are configured so that writing to it will write to a
* proper location of GPU display texture, taking current tile and device slice into account. */
PassAccessor::Destination get_gpu_display_destination_template(
const GPUDisplay *gpu_display) const;
PassAccessor::Destination get_display_destination_template(
const PathTraceDisplay *display) const;
/* Device which will be used for path tracing.
* Note that it is an actual render device (and never is a multi-device). */

View File

@@ -22,9 +22,9 @@
#include "kernel/kernel_path_state.h"
#include "integrator/pass_accessor_cpu.h"
#include "integrator/path_trace_display.h"
#include "render/buffers.h"
#include "render/gpu_display.h"
#include "render/scene.h"
#include "util/util_atomic.h"
@@ -161,14 +161,14 @@ void PathTraceWorkCPU::render_samples_full_pipeline(KernelGlobals *kernel_global
}
}
void PathTraceWorkCPU::copy_to_gpu_display(GPUDisplay *gpu_display,
PassMode pass_mode,
int num_samples)
void PathTraceWorkCPU::copy_to_display(PathTraceDisplay *display,
PassMode pass_mode,
int num_samples)
{
half4 *rgba_half = gpu_display->map_texture_buffer();
half4 *rgba_half = display->map_texture_buffer();
if (!rgba_half) {
/* TODO(sergey): Look into using copy_to_gpu_display() if mapping failed. Might be needed for
* some implementations of GPUDisplay which can not map memory? */
/* TODO(sergey): Look into using copy_to_display() if mapping failed. Might be needed for
* some implementations of PathTraceDisplay which can not map memory? */
return;
}
@@ -178,7 +178,7 @@ void PathTraceWorkCPU::copy_to_gpu_display(GPUDisplay *gpu_display,
const PassAccessorCPU pass_accessor(pass_access_info, kfilm.exposure, num_samples);
PassAccessor::Destination destination = get_gpu_display_destination_template(gpu_display);
PassAccessor::Destination destination = get_display_destination_template(display);
destination.pixels_half_rgba = rgba_half;
tbb::task_arena local_arena = local_tbb_arena_create(device_);
@@ -186,10 +186,10 @@ void PathTraceWorkCPU::copy_to_gpu_display(GPUDisplay *gpu_display,
pass_accessor.get_render_tile_pixels(buffers_.get(), effective_buffer_params_, destination);
});
gpu_display->unmap_texture_buffer();
display->unmap_texture_buffer();
}
void PathTraceWorkCPU::destroy_gpu_resources(GPUDisplay * /*gpu_display*/)
void PathTraceWorkCPU::destroy_gpu_resources(PathTraceDisplay * /*display*/)
{
}

View File

@@ -50,10 +50,10 @@ class PathTraceWorkCPU : public PathTraceWork {
int start_sample,
int samples_num) override;
virtual void copy_to_gpu_display(GPUDisplay *gpu_display,
PassMode pass_mode,
int num_samples) override;
virtual void destroy_gpu_resources(GPUDisplay *gpu_display) override;
virtual void copy_to_display(PathTraceDisplay *display,
PassMode pass_mode,
int num_samples) override;
virtual void destroy_gpu_resources(PathTraceDisplay *display) override;
virtual bool copy_render_buffers_from_device() override;
virtual bool copy_render_buffers_to_device() override;

View File

@@ -15,12 +15,12 @@
*/
#include "integrator/path_trace_work_gpu.h"
#include "integrator/path_trace_display.h"
#include "device/device.h"
#include "integrator/pass_accessor_gpu.h"
#include "render/buffers.h"
#include "render/gpu_display.h"
#include "render/scene.h"
#include "util/util_logging.h"
#include "util/util_tbb.h"
@@ -46,7 +46,7 @@ PathTraceWorkGPU::PathTraceWorkGPU(Device *device,
queued_paths_(device, "queued_paths", MEM_READ_WRITE),
num_queued_paths_(device, "num_queued_paths", MEM_READ_WRITE),
work_tiles_(device, "work_tiles", MEM_READ_WRITE),
gpu_display_rgba_half_(device, "display buffer half", MEM_READ_WRITE),
display_rgba_half_(device, "display buffer half", MEM_READ_WRITE),
max_num_paths_(queue_->num_concurrent_states(sizeof(IntegratorStateCPU))),
min_num_active_paths_(queue_->num_concurrent_busy_states()),
max_active_path_index_(0)
@@ -652,7 +652,7 @@ int PathTraceWorkGPU::get_num_active_paths()
bool PathTraceWorkGPU::should_use_graphics_interop()
{
/* There are few aspects with the graphics interop when using multiple devices caused by the fact
* that the GPUDisplay has a single texture:
* that the PathTraceDisplay has a single texture:
*
* CUDA will return `CUDA_ERROR_NOT_SUPPORTED` from `cuGraphicsGLRegisterBuffer()` when
* attempting to register OpenGL PBO which has been mapped. Which makes sense, because
@@ -678,9 +678,9 @@ bool PathTraceWorkGPU::should_use_graphics_interop()
return interop_use_;
}
void PathTraceWorkGPU::copy_to_gpu_display(GPUDisplay *gpu_display,
PassMode pass_mode,
int num_samples)
void PathTraceWorkGPU::copy_to_display(PathTraceDisplay *display,
PassMode pass_mode,
int num_samples)
{
if (device_->have_error()) {
/* Don't attempt to update GPU display if the device has errors: the error state will make
@@ -694,7 +694,7 @@ void PathTraceWorkGPU::copy_to_gpu_display(GPUDisplay *gpu_display,
}
if (should_use_graphics_interop()) {
if (copy_to_gpu_display_interop(gpu_display, pass_mode, num_samples)) {
if (copy_to_display_interop(display, pass_mode, num_samples)) {
return;
}
@@ -703,12 +703,12 @@ void PathTraceWorkGPU::copy_to_gpu_display(GPUDisplay *gpu_display,
interop_use_ = false;
}
copy_to_gpu_display_naive(gpu_display, pass_mode, num_samples);
copy_to_display_naive(display, pass_mode, num_samples);
}
void PathTraceWorkGPU::copy_to_gpu_display_naive(GPUDisplay *gpu_display,
PassMode pass_mode,
int num_samples)
void PathTraceWorkGPU::copy_to_display_naive(PathTraceDisplay *display,
PassMode pass_mode,
int num_samples)
{
const int full_x = effective_buffer_params_.full_x;
const int full_y = effective_buffer_params_.full_y;
@@ -725,44 +725,42 @@ void PathTraceWorkGPU::copy_to_gpu_display_naive(GPUDisplay *gpu_display,
* NOTE: allocation happens to the final resolution so that no re-allocation happens on every
* change of the resolution divider. However, if the display becomes smaller, shrink the
* allocated memory as well. */
if (gpu_display_rgba_half_.data_width != final_width ||
gpu_display_rgba_half_.data_height != final_height) {
gpu_display_rgba_half_.alloc(final_width, final_height);
if (display_rgba_half_.data_width != final_width ||
display_rgba_half_.data_height != final_height) {
display_rgba_half_.alloc(final_width, final_height);
/* TODO(sergey): There should be a way to make sure device-side memory is allocated without
* transferring zeroes to the device. */
queue_->zero_to_device(gpu_display_rgba_half_);
queue_->zero_to_device(display_rgba_half_);
}
PassAccessor::Destination destination(film_->get_display_pass());
destination.d_pixels_half_rgba = gpu_display_rgba_half_.device_pointer;
destination.d_pixels_half_rgba = display_rgba_half_.device_pointer;
get_render_tile_film_pixels(destination, pass_mode, num_samples);
queue_->copy_from_device(gpu_display_rgba_half_);
queue_->copy_from_device(display_rgba_half_);
queue_->synchronize();
gpu_display->copy_pixels_to_texture(
gpu_display_rgba_half_.data(), texture_x, texture_y, width, height);
display->copy_pixels_to_texture(display_rgba_half_.data(), texture_x, texture_y, width, height);
}
bool PathTraceWorkGPU::copy_to_gpu_display_interop(GPUDisplay *gpu_display,
PassMode pass_mode,
int num_samples)
bool PathTraceWorkGPU::copy_to_display_interop(PathTraceDisplay *display,
PassMode pass_mode,
int num_samples)
{
if (!device_graphics_interop_) {
device_graphics_interop_ = queue_->graphics_interop_create();
}
const DeviceGraphicsInteropDestination graphics_interop_dst =
gpu_display->graphics_interop_get();
device_graphics_interop_->set_destination(graphics_interop_dst);
const DisplayDriver::GraphicsInterop graphics_interop_dst = display->graphics_interop_get();
device_graphics_interop_->set_display_interop(graphics_interop_dst);
const device_ptr d_rgba_half = device_graphics_interop_->map();
if (!d_rgba_half) {
return false;
}
PassAccessor::Destination destination = get_gpu_display_destination_template(gpu_display);
PassAccessor::Destination destination = get_display_destination_template(display);
destination.d_pixels_half_rgba = d_rgba_half;
get_render_tile_film_pixels(destination, pass_mode, num_samples);
@@ -772,14 +770,14 @@ bool PathTraceWorkGPU::copy_to_gpu_display_interop(GPUDisplay *gpu_display,
return true;
}
void PathTraceWorkGPU::destroy_gpu_resources(GPUDisplay *gpu_display)
void PathTraceWorkGPU::destroy_gpu_resources(PathTraceDisplay *display)
{
if (!device_graphics_interop_) {
return;
}
gpu_display->graphics_interop_activate();
display->graphics_interop_activate();
device_graphics_interop_ = nullptr;
gpu_display->graphics_interop_deactivate();
display->graphics_interop_deactivate();
}
void PathTraceWorkGPU::get_render_tile_film_pixels(const PassAccessor::Destination &destination,

View File

@@ -48,10 +48,10 @@ class PathTraceWorkGPU : public PathTraceWork {
int start_sample,
int samples_num) override;
virtual void copy_to_gpu_display(GPUDisplay *gpu_display,
PassMode pass_mode,
int num_samples) override;
virtual void destroy_gpu_resources(GPUDisplay *gpu_display) override;
virtual void copy_to_display(PathTraceDisplay *display,
PassMode pass_mode,
int num_samples) override;
virtual void destroy_gpu_resources(PathTraceDisplay *display) override;
virtual bool copy_render_buffers_from_device() override;
virtual bool copy_render_buffers_to_device() override;
@@ -88,16 +88,16 @@ class PathTraceWorkGPU : public PathTraceWork {
int get_num_active_paths();
/* Check whether graphics interop can be used for the GPUDisplay update. */
/* Check whether graphics interop can be used for the PathTraceDisplay update. */
bool should_use_graphics_interop();
/* Naive implementation of the `copy_to_gpu_display()` which performs film conversion on the
* device, then copies pixels to the host and pushes them to the `gpu_display`. */
void copy_to_gpu_display_naive(GPUDisplay *gpu_display, PassMode pass_mode, int num_samples);
/* Naive implementation of the `copy_to_display()` which performs film conversion on the
* device, then copies pixels to the host and pushes them to the `display`. */
void copy_to_display_naive(PathTraceDisplay *display, PassMode pass_mode, int num_samples);
/* Implementation of `copy_to_gpu_display()` which uses driver's OpenGL/GPU interoperability
/* Implementation of `copy_to_display()` which uses driver's OpenGL/GPU interoperability
* functionality, avoiding copy of pixels to the host. */
bool copy_to_gpu_display_interop(GPUDisplay *gpu_display, PassMode pass_mode, int num_samples);
bool copy_to_display_interop(PathTraceDisplay *display, PassMode pass_mode, int num_samples);
/* Synchronously run film conversion kernel and store display result in the given destination. */
void get_render_tile_film_pixels(const PassAccessor::Destination &destination,
@@ -139,9 +139,9 @@ class PathTraceWorkGPU : public PathTraceWork {
/* Temporary buffer for passing work tiles to kernel. */
device_vector<KernelWorkTile> work_tiles_;
/* Temporary buffer used by the copy_to_gpu_display() whenever graphics interoperability is not
/* Temporary buffer used by the copy_to_display() whenever graphics interoperability is not
* available. Is allocated on-demand. */
device_vector<half4> gpu_display_rgba_half_;
device_vector<half4> display_rgba_half_;
unique_ptr<DeviceGraphicsInterop> device_graphics_interop_;

View File

@@ -344,7 +344,7 @@ class RenderScheduler {
/* Number of rendered samples on top of the start sample. */
int num_rendered_samples = 0;
/* Point in time the latest GPUDisplay work has been scheduled. */
/* Point in time the latest PathTraceDisplay work has been scheduled. */
double last_display_update_time = 0.0;
/* Value of -1 means display was never updated. */
int last_display_update_sample = -1;

View File

@@ -186,8 +186,8 @@ ccl_device_inline float _shader_bsdf_multi_eval(const KernelGlobals *kg,
float sum_sample_weight,
const uint light_shader_flags)
{
/* this is the veach one-sample model with balance heuristic, some pdf
* factors drop out when using balance heuristic weighting */
/* This is the veach one-sample model with balance heuristic,
* some PDF factors drop out when using balance heuristic weighting. */
for (int i = 0; i < sd->num_closure; i++) {
const ShaderClosure *sc = &sd->closure[i];

View File

@@ -41,6 +41,7 @@ set(SRC_OSL
node_vector_displacement.osl
node_emission.osl
node_environment_texture.osl
node_float_curve.osl
node_fresnel.osl
node_gamma.osl
node_geometry.osl

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2011-2021 Blender Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "node_ramp_util.h"
#include "stdcycles.h"
shader node_float_curve(float ramp[] = {0.0},
float min_x = 0.0,
float max_x = 1.0,
float ValueIn = 0.0,
float Factor = 0.0,
output float ValueOut = 0.0)
{
float c = (ValueIn - min_x) / (max_x - min_x);
ValueOut = rgb_ramp_lookup(ramp, c, 1, 1);
ValueOut = mix(ValueIn, ValueOut, Factor);
}

View File

@@ -493,11 +493,13 @@ ccl_device void svm_eval_nodes(INTEGRATOR_STATE_CONST_ARGS,
case NODE_IES:
svm_node_ies(kg, sd, stack, node);
break;
case NODE_RGB_CURVES:
case NODE_VECTOR_CURVES:
offset = svm_node_curves(kg, sd, stack, node, offset);
break;
case NODE_FLOAT_CURVE:
offset = svm_node_curve(kg, sd, stack, node, offset);
break;
case NODE_TANGENT:
svm_node_tangent(kg, sd, stack, node);
break;

View File

@@ -21,6 +21,48 @@ CCL_NAMESPACE_BEGIN
/* NOTE: svm_ramp.h, svm_ramp_util.h and node_ramp_util.h must stay consistent */
ccl_device_inline float fetch_float(const KernelGlobals *kg, int offset)
{
uint4 node = kernel_tex_fetch(__svm_nodes, offset);
return __uint_as_float(node.x);
}
ccl_device_inline float float_ramp_lookup(const KernelGlobals *kg,
int offset,
float f,
bool interpolate,
bool extrapolate,
int table_size)
{
if ((f < 0.0f || f > 1.0f) && extrapolate) {
float t0, dy;
if (f < 0.0f) {
t0 = fetch_float(kg, offset);
dy = t0 - fetch_float(kg, offset + 1);
f = -f;
}
else {
t0 = fetch_float(kg, offset + table_size - 1);
dy = t0 - fetch_float(kg, offset + table_size - 2);
f = f - 1.0f;
}
return t0 + dy * f * (table_size - 1);
}
f = saturate(f) * (table_size - 1);
/* clamp int as well in case of NaN */
int i = clamp(float_to_int(f), 0, table_size - 1);
float t = f - (float)i;
float a = fetch_float(kg, offset + i);
if (interpolate && t > 0.0f)
a = (1.0f - t) * a + t * fetch_float(kg, offset + i + 1);
return a;
}
ccl_device_inline float4 rgb_ramp_lookup(const KernelGlobals *kg,
int offset,
float f,
@@ -105,6 +147,30 @@ ccl_device_noinline int svm_node_curves(
return offset;
}
ccl_device_noinline int svm_node_curve(
const KernelGlobals *kg, ShaderData *sd, float *stack, uint4 node, int offset)
{
uint fac_offset, value_in_offset, out_offset;
svm_unpack_node_uchar3(node.y, &fac_offset, &value_in_offset, &out_offset);
uint table_size = read_node(kg, &offset).x;
float fac = stack_load_float(stack, fac_offset);
float in = stack_load_float(stack, value_in_offset);
const float min = __int_as_float(node.z), max = __int_as_float(node.w);
const float range = max - min;
const float relpos = (in - min) / range;
float v = float_ramp_lookup(kg, offset, relpos, true, true, table_size);
in = (1.0f - fac) * in + fac * v;
stack_store_float(stack, out_offset, in);
offset += table_size;
return offset;
}
CCL_NAMESPACE_END
#endif /* __SVM_RAMP_H__ */

View File

@@ -122,6 +122,7 @@ typedef enum ShaderNodeType {
NODE_AOV_START,
NODE_AOV_COLOR,
NODE_AOV_VALUE,
NODE_FLOAT_CURVE,
/* NOTE: for best OpenCL performance, item definition in the enum must
* match the switch case order in svm.h. */
} ShaderNodeType;

View File

@@ -35,7 +35,6 @@ set(SRC
denoising.cpp
film.cpp
geometry.cpp
gpu_display.cpp
graph.cpp
hair.cpp
image.cpp
@@ -78,9 +77,10 @@ set(SRC_HEADERS
colorspace.h
constant_fold.h
denoising.h
display_driver.h
output_driver.h
film.h
geometry.h
gpu_display.h
graph.h
hair.h
image.h

View File

@@ -0,0 +1,131 @@
/*
* Copyright 2021 Blender Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "util/util_half.h"
#include "util/util_types.h"
CCL_NAMESPACE_BEGIN
/* Display driver for efficient interactive display of renders.
*
* Host applications implement this interface for viewport rendering. For best performance, we
* recommend:
* - Allocating a texture on the GPU to be interactively updated
* - Using the graphics interop mechanism to avoid CPU-GPU copying overhead
* - Using a dedicated or thread-safe graphics API context for updates, to avoid
* blocking the host application.
*/
class DisplayDriver {
public:
DisplayDriver() = default;
virtual ~DisplayDriver() = default;
/* Render buffer parameters. */
struct Params {
public:
/* Render resolution, ignoring progressive resolution changes.
* The texture buffer should be allocated with this size. */
int2 size = make_int2(0, 0);
/* For border rendering, the full resolution of the render, and the offset within that larger
* render. */
int2 full_size = make_int2(0, 0);
int2 full_offset = make_int2(0, 0);
bool modified(const Params &other) const
{
return !(full_offset == other.full_offset && full_size == other.full_size &&
size == other.size);
}
};
/* Update the render from the rendering thread.
*
* Cycles periodically updates the render to be displayed. For multithreaded updates with
* potentially multiple rendering devices, it will call these methods as follows.
*
* if (driver.update_begin(params, width, height)) {
* parallel_for_each(rendering_device) {
* buffer = driver.map_texture_buffer();
* if (buffer) {
* fill(buffer);
* driver.unmap_texture_buffer();
* }
* }
* driver.update_end();
* }
*
* The parameters may dynamically change due to camera changes in the scene, and resources should
* be re-allocated accordingly.
*
* The width and height passed to update_begin() are the effective render resolution taking into
* account progressive resolution changes, which may be equal to or smaller than the params.size.
* For efficiency, changes in this resolution should be handled without re-allocating resources,
* but rather by using a subset of the full resolution buffer. */
virtual bool update_begin(const Params &params, int width, int height) = 0;
virtual void update_end() = 0;
virtual half4 *map_texture_buffer() = 0;
virtual void unmap_texture_buffer() = 0;
/* Optionally return a handle to a native graphics API texture buffer. If supported,
* the rendering device may write directly to this buffer instead of calling
* map_texture_buffer() and unmap_texture_buffer(). */
class GraphicsInterop {
public:
/* Dimensions of the buffer, in pixels. */
int buffer_width = 0;
int buffer_height = 0;
/* OpenGL pixel buffer object. */
int opengl_pbo_id = 0;
/* Clear the entire buffer before doing partial write to it. */
bool need_clear = false;
};
virtual GraphicsInterop graphics_interop_get()
{
return GraphicsInterop();
}
/* (De)activate graphics context required for editing or deleting the graphics interop
* object.
*
* For example, destruction of the CUDA object associated with an OpenGL requires the
* OpenGL context to be active. */
virtual void graphics_interop_activate(){};
virtual void graphics_interop_deactivate(){};
/* Clear the display buffer by filling it with zeros. */
virtual void clear() = 0;
/* Draw the render using the native graphics API.
*
* Note that this may be called in parallel to updates. The implementation is responsible for
* mutex locking or other mechanisms to avoid conflicts.
*
* The parameters may have changed since the last update. The implementation is responsible for
* deciding to skip or adjust render display for such changes.
*
* Host application drawing the render buffer should use Session.draw(), which will
* call this method. */
virtual void draw(const Params &params) = 0;
};
CCL_NAMESPACE_END

View File

@@ -6382,7 +6382,7 @@ void BumpNode::constant_fold(const ConstantFolder &folder)
/* TODO(sergey): Ignore bump with zero strength. */
}
/* Curve node */
/* Curves node */
CurvesNode::CurvesNode(const NodeType *node_type) : ShaderNode(node_type)
{
@@ -6531,6 +6531,83 @@ void VectorCurvesNode::compile(OSLCompiler &compiler)
CurvesNode::compile(compiler, "node_vector_curves");
}
/* FloatCurveNode */
NODE_DEFINE(FloatCurveNode)
{
NodeType *type = NodeType::add("float_curve", create, NodeType::SHADER);
SOCKET_FLOAT_ARRAY(curve, "Curve", array<float>());
SOCKET_FLOAT(min_x, "Min X", 0.0f);
SOCKET_FLOAT(max_x, "Max X", 1.0f);
SOCKET_IN_FLOAT(fac, "Factor", 0.0f);
SOCKET_IN_FLOAT(value, "Value", 0.0f);
SOCKET_OUT_FLOAT(value, "Value");
return type;
}
FloatCurveNode::FloatCurveNode() : ShaderNode(get_node_type())
{
}
void FloatCurveNode::constant_fold(const ConstantFolder &folder)
{
ShaderInput *value_in = input("Value");
ShaderInput *fac_in = input("Factor");
/* evaluate fully constant node */
if (folder.all_inputs_constant()) {
if (curve.size() == 0) {
return;
}
float pos = (value - min_x) / (max_x - min_x);
float result = float_ramp_lookup(curve.data(), pos, true, true, curve.size());
folder.make_constant(value + fac * (result - value));
}
/* remove no-op node */
else if (!fac_in->link && fac == 0.0f) {
/* link is not null because otherwise all inputs are constant */
folder.bypass(value_in->link);
}
}
void FloatCurveNode::compile(SVMCompiler &compiler)
{
if (curve.size() == 0)
return;
ShaderInput *value_in = input("Value");
ShaderInput *fac_in = input("Factor");
ShaderOutput *value_out = output("Value");
compiler.add_node(NODE_FLOAT_CURVE,
compiler.encode_uchar4(compiler.stack_assign(fac_in),
compiler.stack_assign(value_in),
compiler.stack_assign(value_out)),
__float_as_int(min_x),
__float_as_int(max_x));
compiler.add_node(curve.size());
for (int i = 0; i < curve.size(); i++)
compiler.add_node(make_float4(curve[i]));
}
void FloatCurveNode::compile(OSLCompiler &compiler)
{
if (curve.size() == 0)
return;
compiler.parameter_array("ramp", curve.data(), curve.size());
compiler.parameter(this, "min_x");
compiler.parameter(this, "max_x");
compiler.add(this, "node_float_curve");
}
/* RGBRampNode */
NODE_DEFINE(RGBRampNode)

View File

@@ -1398,6 +1398,18 @@ class VectorCurvesNode : public CurvesNode {
void constant_fold(const ConstantFolder &folder);
};
class FloatCurveNode : public ShaderNode {
public:
SHADER_NODE_CLASS(FloatCurveNode)
void constant_fold(const ConstantFolder &folder);
NODE_SOCKET_API_ARRAY(array<float>, curve)
NODE_SOCKET_API(float, min_x)
NODE_SOCKET_API(float, max_x)
NODE_SOCKET_API(float, fac)
NODE_SOCKET_API(float, value)
};
class RGBRampNode : public ShaderNode {
public:
SHADER_NODE_CLASS(RGBRampNode)

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2021 Blender Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "util/util_math.h"
#include "util/util_string.h"
#include "util/util_types.h"
CCL_NAMESPACE_BEGIN
/* Output driver for reading render buffers.
*
* Host applications implement this interface for outputting render buffers for offline rendering.
* Drivers can be used to copy the buffers into the host application or write them directly to
* disk. This interface may also be used for interactive display, however the DisplayDriver is more
* efficient for that purpose.
*/
class OutputDriver {
public:
OutputDriver() = default;
virtual ~OutputDriver() = default;
class Tile {
public:
Tile(const int2 offset,
const int2 size,
const int2 full_size,
const string_view layer,
const string_view view)
: offset(offset), size(size), full_size(full_size), layer(layer), view(view)
{
}
virtual ~Tile() = default;
const int2 offset;
const int2 size;
const int2 full_size;
const string layer;
const string view;
virtual bool get_pass_pixels(const string_view pass_name,
const int num_channels,
float *pixels) const = 0;
virtual bool set_pass_pixels(const string_view pass_name,
const int num_channels,
const float *pixels) const = 0;
};
/* Write tile once it has finished rendering. */
virtual void write_render_tile(const Tile &tile) = 0;
/* Update tile while rendering is in progress. Return true if any update
* was performed. */
virtual bool update_render_tile(const Tile & /* tile */)
{
return false;
}
/* For baking, read render pass PASS_BAKE_PRIMITIVE and PASS_BAKE_DIFFERENTIAL
* to determine which shading points to use for baking at each pixel. Return
* true if any data was read. */
virtual bool read_render_tile(const Tile & /* tile */)
{
return false;
}
};
CCL_NAMESPACE_END

View File

@@ -25,12 +25,13 @@
#include "render/bake.h"
#include "render/buffers.h"
#include "render/camera.h"
#include "render/gpu_display.h"
#include "render/display_driver.h"
#include "render/graph.h"
#include "render/integrator.h"
#include "render/light.h"
#include "render/mesh.h"
#include "render/object.h"
#include "render/output_driver.h"
#include "render/scene.h"
#include "render/session.h"
@@ -64,25 +65,6 @@ Session::Session(const SessionParams &params_, const SceneParams &scene_params)
path_trace_ = make_unique<PathTrace>(
device, scene->film, &scene->dscene, render_scheduler_, tile_manager_);
path_trace_->set_progress(&progress);
path_trace_->tile_buffer_update_cb = [&]() {
if (!update_render_tile_cb) {
return;
}
update_render_tile_cb();
};
path_trace_->tile_buffer_write_cb = [&]() {
if (!write_render_tile_cb) {
return;
}
write_render_tile_cb();
};
path_trace_->tile_buffer_read_cb = [&]() -> bool {
if (!read_render_tile_cb) {
return false;
}
read_render_tile_cb();
return true;
};
path_trace_->progress_update_cb = [&]() { update_status_time(); };
tile_manager_.full_buffer_written_cb = [&](string_view filename) {
@@ -97,24 +79,6 @@ Session::~Session()
{
cancel();
/* TODO(sergey): Bring the passes in viewport back.
* It is unclear why there is such an exception needed though. */
#if 0
if (buffers && params.write_render_cb) {
/* Copy to display buffer and write out image if requested */
delete display;
display = new DisplayBuffer(device, false);
display->reset(buffers->params);
copy_to_display_buffer(params.samples);
int w = display->draw_width;
int h = display->draw_height;
uchar4 *pixels = display->rgba_byte.copy_from_device(0, w, h);
params.write_render_cb((uchar *)pixels, w, h, 4);
}
#endif
/* Make sure path tracer is destroyed before the device. This is needed because destruction might
* need to access device for device memory free. */
/* TODO(sergey): Convert device to be unique_ptr, and rely on C++ to destruct objects in the
@@ -162,7 +126,7 @@ bool Session::ready_to_reset()
void Session::run_main_render_loop()
{
path_trace_->clear_gpu_display();
path_trace_->clear_display();
while (true) {
RenderWork render_work = run_update_for_next_iteration();
@@ -514,9 +478,14 @@ void Session::set_pause(bool pause)
}
}
void Session::set_gpu_display(unique_ptr<GPUDisplay> gpu_display)
void Session::set_output_driver(unique_ptr<OutputDriver> driver)
{
path_trace_->set_gpu_display(move(gpu_display));
path_trace_->set_output_driver(move(driver));
}
void Session::set_display_driver(unique_ptr<DisplayDriver> driver)
{
path_trace_->set_display_driver(move(driver));
}
double Session::get_estimated_remaining_time() const
@@ -636,101 +605,6 @@ void Session::collect_statistics(RenderStats *render_stats)
}
}
/* --------------------------------------------------------------------
* Tile and tile pixels access.
*/
bool Session::has_multiple_render_tiles() const
{
return tile_manager_.has_multiple_tiles();
}
int2 Session::get_render_tile_size() const
{
return path_trace_->get_render_tile_size();
}
int2 Session::get_render_tile_offset() const
{
return path_trace_->get_render_tile_offset();
}
string_view Session::get_render_tile_layer() const
{
const BufferParams &buffer_params = path_trace_->get_render_tile_params();
return buffer_params.layer;
}
string_view Session::get_render_tile_view() const
{
const BufferParams &buffer_params = path_trace_->get_render_tile_params();
return buffer_params.view;
}
bool Session::copy_render_tile_from_device()
{
return path_trace_->copy_render_tile_from_device();
}
bool Session::get_render_tile_pixels(const string &pass_name, int num_components, float *pixels)
{
/* NOTE: The code relies on a fact that session is fully update and no scene/buffer modification
* is happening while this function runs. */
const BufferParams &buffer_params = path_trace_->get_render_tile_params();
const BufferPass *pass = buffer_params.find_pass(pass_name);
if (pass == nullptr) {
return false;
}
const bool has_denoised_result = path_trace_->has_denoised_result();
if (pass->mode == PassMode::DENOISED && !has_denoised_result) {
pass = buffer_params.find_pass(pass->type);
if (pass == nullptr) {
/* Happens when denoised result pass is requested but is never written by the kernel. */
return false;
}
}
pass = buffer_params.get_actual_display_pass(pass);
const float exposure = buffer_params.exposure;
const int num_samples = path_trace_->get_num_render_tile_samples();
PassAccessor::PassAccessInfo pass_access_info(*pass);
pass_access_info.use_approximate_shadow_catcher = buffer_params.use_approximate_shadow_catcher;
pass_access_info.use_approximate_shadow_catcher_background =
pass_access_info.use_approximate_shadow_catcher && !buffer_params.use_transparent_background;
const PassAccessorCPU pass_accessor(pass_access_info, exposure, num_samples);
const PassAccessor::Destination destination(pixels, num_components);
return path_trace_->get_render_tile_pixels(pass_accessor, destination);
}
bool Session::set_render_tile_pixels(const string &pass_name,
int num_components,
const float *pixels)
{
/* NOTE: The code relies on a fact that session is fully update and no scene/buffer modification
* is happening while this function runs. */
const BufferPass *pass = buffer_params_.find_pass(pass_name);
if (!pass) {
return false;
}
const float exposure = scene->film->get_exposure();
const int num_samples = render_scheduler_.get_num_rendered_samples();
const PassAccessor::PassAccessInfo pass_access_info(*pass);
PassAccessorCPU pass_accessor(pass_access_info, exposure, num_samples);
PassAccessor::Source source(pixels, num_components);
return path_trace_->set_render_tile_pixels(pass_accessor, source);
}
/* --------------------------------------------------------------------
* Full-frame on-disk storage.
*/

View File

@@ -35,9 +35,10 @@ CCL_NAMESPACE_BEGIN
class BufferParams;
class Device;
class DeviceScene;
class DisplayDriver;
class OutputDriver;
class PathTrace;
class Progress;
class GPUDisplay;
class RenderBuffers;
class Scene;
class SceneParams;
@@ -67,8 +68,6 @@ class SessionParams {
ShadingSystem shadingsystem;
function<bool(const uchar *pixels, int width, int height, int channels)> write_render_cb;
SessionParams()
{
headless = false;
@@ -114,10 +113,6 @@ class Session {
Stats stats;
Profiler profiler;
function<void(void)> write_render_tile_cb;
function<void(void)> update_render_tile_cb;
function<void(void)> read_render_tile_cb;
/* Callback is invoked by tile manager whenever on-dist tiles storage file is closed after
* writing. Allows an engine integration to keep track of those files without worry about
* transferring the information when it needs to re-create session during rendering. */
@@ -143,7 +138,8 @@ class Session {
void set_samples(int samples);
void set_time_limit(double time_limit);
void set_gpu_display(unique_ptr<GPUDisplay> gpu_display);
void set_output_driver(unique_ptr<OutputDriver> driver);
void set_display_driver(unique_ptr<DisplayDriver> driver);
double get_estimated_remaining_time() const;
@@ -155,24 +151,6 @@ class Session {
void collect_statistics(RenderStats *stats);
/* --------------------------------------------------------------------
* Tile and tile pixels access.
*/
bool has_multiple_render_tiles() const;
/* Get size and offset (relative to the buffer's full x/y) of the currently rendering tile. */
int2 get_render_tile_size() const;
int2 get_render_tile_offset() const;
string_view get_render_tile_layer() const;
string_view get_render_tile_view() const;
bool copy_render_tile_from_device();
bool get_render_tile_pixels(const string &pass_name, int num_components, float *pixels);
bool set_render_tile_pixels(const string &pass_name, int num_components, const float *pixels);
/* --------------------------------------------------------------------
* Full-frame on-disk storage.
*/

View File

@@ -420,6 +420,11 @@ const Tile &TileManager::get_current_tile() const
return tile_state_.current_tile;
}
const int2 TileManager::get_size() const
{
return make_int2(buffer_params_.width, buffer_params_.height);
}
bool TileManager::open_tile_output()
{
write_state_.filename = path_temp_get("cycles-tile-buffer-" + tile_file_unique_part_ + "-" +

View File

@@ -82,6 +82,7 @@ class TileManager {
bool done();
const Tile &get_current_tile() const;
const int2 get_size() const;
/* Write render buffer of a tile to a file on disk.
*

View File

@@ -473,6 +473,7 @@ if(WITH_XR_OPENXR)
intern/GHOST_Xr.cpp
intern/GHOST_XrAction.cpp
intern/GHOST_XrContext.cpp
intern/GHOST_XrControllerModel.cpp
intern/GHOST_XrEvent.cpp
intern/GHOST_XrGraphicsBinding.cpp
intern/GHOST_XrSession.cpp
@@ -482,13 +483,19 @@ if(WITH_XR_OPENXR)
intern/GHOST_IXrGraphicsBinding.h
intern/GHOST_XrAction.h
intern/GHOST_XrContext.h
intern/GHOST_XrControllerModel.h
intern/GHOST_XrException.h
intern/GHOST_XrSession.h
intern/GHOST_XrSwapchain.h
intern/GHOST_Xr_intern.h
intern/GHOST_Xr_openxr_includes.h
)
list(APPEND INC
../../extern/json/include
../../extern/tinygltf
)
list(APPEND INC_SYS
${EIGEN3_INCLUDE_DIRS}
${XR_OPENXR_SDK_INCLUDE_DIR}
)
list(APPEND LIB

View File

@@ -1140,6 +1140,30 @@ void GHOST_XrGetActionCustomdataArray(GHOST_XrContextHandle xr_context,
const char *action_set_name,
void **r_customdata_array);
/* controller model */
/**
* Load the OpenXR controller model.
*/
int GHOST_XrLoadControllerModel(GHOST_XrContextHandle xr_context, const char *subaction_path);
/**
* Unload the OpenXR controller model.
*/
void GHOST_XrUnloadControllerModel(GHOST_XrContextHandle xr_context, const char *subaction_path);
/**
* Update component transforms for the OpenXR controller model.
*/
int GHOST_XrUpdateControllerModelComponents(GHOST_XrContextHandle xr_context,
const char *subaction_path);
/**
* Get vertex data for the OpenXR controller model.
*/
int GHOST_XrGetControllerModelData(GHOST_XrContextHandle xr_context,
const char *subaction_path,
GHOST_XrControllerModelData *r_data);
#endif /* WITH_XR_OPENXR */
#ifdef __cplusplus

View File

@@ -643,7 +643,7 @@ typedef void (*GHOST_XrDrawViewFn)(const struct GHOST_XrDrawViewInfo *draw_view,
*/
typedef const GHOST_TXrGraphicsBinding *GHOST_XrGraphicsBindingCandidates;
typedef struct {
typedef struct GHOST_XrPose {
float position[3];
/* Blender convention (w, x, y, z) */
float orientation_quat[4];
@@ -753,8 +753,31 @@ typedef struct GHOST_XrActionProfileInfo {
const char *profile_path;
uint32_t count_subaction_paths;
const char **subaction_paths;
/* Bindings for each subaction path. */
/** Bindings for each subaction path. */
const GHOST_XrActionBindingInfo *bindings;
} GHOST_XrActionProfileInfo;
typedef struct GHOST_XrControllerModelVertex {
float position[3];
float normal[3];
} GHOST_XrControllerModelVertex;
typedef struct GHOST_XrControllerModelComponent {
/** World space transform. */
float transform[4][4];
uint32_t vertex_offset;
uint32_t vertex_count;
uint32_t index_offset;
uint32_t index_count;
} GHOST_XrControllerModelComponent;
typedef struct GHOST_XrControllerModelData {
uint32_t count_vertices;
const GHOST_XrControllerModelVertex *vertices;
uint32_t count_indices;
const uint32_t *indices;
uint32_t count_components;
const GHOST_XrControllerModelComponent *components;
} GHOST_XrControllerModelData;
#endif /* WITH_XR_OPENXR */

View File

@@ -1069,4 +1069,39 @@ void GHOST_XrGetActionCustomdataArray(GHOST_XrContextHandle xr_contexthandle,
xr_context);
}
int GHOST_XrLoadControllerModel(GHOST_XrContextHandle xr_contexthandle, const char *subaction_path)
{
GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
GHOST_XrSession *xr_session = xr_context->getSession();
GHOST_XR_CAPI_CALL_RET(xr_session->loadControllerModel(subaction_path), xr_context);
return 0;
}
void GHOST_XrUnloadControllerModel(GHOST_XrContextHandle xr_contexthandle,
const char *subaction_path)
{
GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
GHOST_XrSession *xr_session = xr_context->getSession();
GHOST_XR_CAPI_CALL(xr_session->unloadControllerModel(subaction_path), xr_context);
}
int GHOST_XrUpdateControllerModelComponents(GHOST_XrContextHandle xr_contexthandle,
const char *subaction_path)
{
GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
GHOST_XrSession *xr_session = xr_context->getSession();
GHOST_XR_CAPI_CALL_RET(xr_session->updateControllerModelComponents(subaction_path), xr_context);
return 0;
}
int GHOST_XrGetControllerModelData(GHOST_XrContextHandle xr_contexthandle,
const char *subaction_path,
GHOST_XrControllerModelData *r_data)
{
GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
GHOST_XrSession *xr_session = xr_context->getSession();
GHOST_XR_CAPI_CALL_RET(xr_session->getControllerModelData(subaction_path, *r_data), xr_context);
return 0;
}
#endif /* WITH_XR_OPENXR */

View File

@@ -101,8 +101,7 @@ GHOST_TSuccess GHOST_DisplayManagerSDL::setCurrentDisplaySetting(
uint8_t display, const GHOST_DisplaySetting &setting)
{
/*
* Mode switching code ported from Quake 2 version 3.21 and bzflag version
* 2.4.0:
* Mode switching code ported from Quake 2 version 3.21 and BZFLAG version 2.4.0:
* ftp://ftp.idsoftware.com/idstuff/source/q2source-3.21.zip
* See linux/gl_glx.c:GLimp_SetMode
* http://wiki.bzflag.org/BZFlag_Source

View File

@@ -1528,13 +1528,13 @@ void GHOST_SystemX11::processEvent(XEvent *xe)
window->GetTabletData().Pressure = axis_value / ((float)xtablet.PressureLevels);
}
/* the (short) cast and the & 0xffff is bizarre and unexplained anywhere,
* but I got garbage data without it. Found it in the xidump.c source --matt
/* NOTE(@broken): the (short) cast and the & 0xffff is bizarre and unexplained anywhere,
* but I got garbage data without it. Found it in the `xidump.c` source.
*
* The '& 0xffff' just truncates the value to its two lowest bytes, this probably means
* some drivers do not properly set the whole int value? Since we convert to float
* afterward, I don't think we need to cast to short here, but do not have a device to
* check this. --mont29
* NOTE(@mont29): The '& 0xffff' just truncates the value to its two lowest bytes,
* this probably means some drivers do not properly set the whole int value?
* Since we convert to float afterward,
* I don't think we need to cast to short here, but do not have a device to check this.
*/
if (AXIS_VALUE_GET(3, axis_value)) {
window->GetTabletData().Xtilt = (short)(axis_value & 0xffff) /

View File

@@ -1092,9 +1092,9 @@ GHOST_TSuccess GHOST_WindowX11::setOrder(GHOST_TWindowOrder order)
XWindowAttributes attr;
Atom atom;
/* We use both XRaiseWindow and _NET_ACTIVE_WINDOW, since some
* window managers ignore the former (e.g. kwin from kde) and others
* don't implement the latter (e.g. fluxbox pre 0.9.9) */
/* We use both #XRaiseWindow and #_NET_ACTIVE_WINDOW, since some
* window managers ignore the former (e.g. KWIN from KDE) and others
* don't implement the latter (e.g. FLUXBOX before 0.9.9). */
XRaiseWindow(m_display, m_window);

View File

@@ -22,7 +22,6 @@
#include <cstring>
#include "GHOST_Types.h"
#include "GHOST_XrException.h"
#include "GHOST_Xr_intern.h"

View File

@@ -26,6 +26,7 @@
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "GHOST_Util.h"

View File

@@ -412,11 +412,14 @@ void GHOST_XrContext::getExtensionsToEnable(
try_ext.push_back(XR_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
/* Try enabling interaction profile extensions. */
/* Interaction profile extensions. */
try_ext.push_back(XR_EXT_HP_MIXED_REALITY_CONTROLLER_EXTENSION_NAME);
try_ext.push_back(XR_HTC_VIVE_COSMOS_CONTROLLER_INTERACTION_EXTENSION_NAME);
try_ext.push_back(XR_HUAWEI_CONTROLLER_INTERACTION_EXTENSION_NAME);
/* Controller model extension. */
try_ext.push_back(XR_MSFT_CONTROLLER_MODEL_EXTENSION_NAME);
/* Varjo quad view extension. */
try_ext.push_back(XR_VARJO_QUAD_VIEWS_EXTENSION_NAME);

View File

@@ -0,0 +1,629 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup GHOST
*/
#include <cassert>
#include <Eigen/Core>
#include <Eigen/Geometry>
#include "GHOST_Types.h"
#include "GHOST_XrException.h"
#include "GHOST_Xr_intern.h"
#include "GHOST_XrControllerModel.h"
#define TINYGLTF_IMPLEMENTATION
#define TINYGLTF_NO_STB_IMAGE
#define TINYGLTF_NO_STB_IMAGE_WRITE
#define STBIWDEF static inline
#include "tiny_gltf.h"
struct GHOST_XrControllerModelNode {
int32_t parent_idx = -1;
int32_t component_idx = -1;
float local_transform[4][4];
};
/* -------------------------------------------------------------------- */
/** \name glTF Utilities
*
* Adapted from Microsoft OpenXR-Mixed Reality Samples (MIT License):
* https://github.com/microsoft/OpenXR-MixedReality
* \{ */
struct GHOST_XrPrimitive {
std::vector<GHOST_XrControllerModelVertex> vertices;
std::vector<uint32_t> indices;
};
/**
* Validate that an accessor does not go out of bounds of the buffer view that it references and
* that the buffer view does not exceed the bounds of the buffer that it references
*/
static void validate_accessor(const tinygltf::Accessor &accessor,
const tinygltf::BufferView &buffer_view,
const tinygltf::Buffer &buffer,
size_t byte_stride,
size_t element_size)
{
/* Make sure the accessor does not go out of range of the buffer view. */
if (accessor.byteOffset + (accessor.count - 1) * byte_stride + element_size >
buffer_view.byteLength) {
throw GHOST_XrException("glTF: Accessor goes out of range of bufferview.");
}
/* Make sure the buffer view does not go out of range of the buffer. */
if (buffer_view.byteOffset + buffer_view.byteLength > buffer.data.size()) {
throw GHOST_XrException("glTF: BufferView goes out of range of buffer.");
}
}
template<float (GHOST_XrControllerModelVertex::*field)[3]>
static void read_vertices(const tinygltf::Accessor &accessor,
const tinygltf::BufferView &buffer_view,
const tinygltf::Buffer &buffer,
GHOST_XrPrimitive &primitive)
{
if (accessor.type != TINYGLTF_TYPE_VEC3) {
throw GHOST_XrException(
"glTF: Accessor for primitive attribute has incorrect type (VEC3 expected).");
}
if (accessor.componentType != TINYGLTF_COMPONENT_TYPE_FLOAT) {
throw GHOST_XrException(
"glTF: Accessor for primitive attribute has incorrect component type (FLOAT expected).");
}
/* If stride is not specified, it is tightly packed. */
constexpr size_t packed_size = sizeof(float) * 3;
const size_t stride = buffer_view.byteStride == 0 ? packed_size : buffer_view.byteStride;
validate_accessor(accessor, buffer_view, buffer, stride, packed_size);
/* Resize the vertices vector, if necessary, to include room for the attribute data.
If there are multiple attributes for a primitive, the first one will resize, and the
subsequent will not need to. */
primitive.vertices.resize(accessor.count);
/* Copy the attribute value over from the glTF buffer into the appropriate vertex field. */
const uint8_t *buffer_ptr = buffer.data.data() + buffer_view.byteOffset + accessor.byteOffset;
for (size_t i = 0; i < accessor.count; i++, buffer_ptr += stride) {
memcpy(primitive.vertices[i].*field, buffer_ptr, stride);
}
}
static void load_attribute_accessor(const tinygltf::Model &gltf_model,
const std::string &attribute_name,
int accessor_id,
GHOST_XrPrimitive &primitive)
{
const auto &accessor = gltf_model.accessors.at(accessor_id);
if (accessor.bufferView == -1) {
throw GHOST_XrException("glTF: Accessor for primitive attribute specifies no bufferview.");
}
const tinygltf::BufferView &buffer_view = gltf_model.bufferViews.at(accessor.bufferView);
if (buffer_view.target != TINYGLTF_TARGET_ARRAY_BUFFER && buffer_view.target != 0) {
throw GHOST_XrException(
"glTF: Accessor for primitive attribute uses bufferview with invalid 'target' type.");
}
const tinygltf::Buffer &buffer = gltf_model.buffers.at(buffer_view.buffer);
if (attribute_name.compare("POSITION") == 0) {
read_vertices<&GHOST_XrControllerModelVertex::position>(
accessor, buffer_view, buffer, primitive);
}
else if (attribute_name.compare("NORMAL") == 0) {
read_vertices<&GHOST_XrControllerModelVertex::normal>(
accessor, buffer_view, buffer, primitive);
}
}
/**
* Reads index data from a glTF primitive into a GHOST_XrPrimitive. glTF indices may be 8bit, 16bit
* or 32bit integers. This will coalesce indices from the source type(s) into a 32bit integer.
*/
template<typename TSrcIndex>
static void read_indices(const tinygltf::Accessor &accessor,
const tinygltf::BufferView &buffer_view,
const tinygltf::Buffer &buffer,
GHOST_XrPrimitive &primitive)
{
if (buffer_view.target != TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER &&
buffer_view.target != 0) { /* Allow 0 (not specified) even though spec doesn't seem to allow
this (BoomBox GLB fails). */
throw GHOST_XrException(
"glTF: Accessor for indices uses bufferview with invalid 'target' type.");
}
constexpr size_t component_size_bytes = sizeof(TSrcIndex);
if (buffer_view.byteStride != 0 &&
buffer_view.byteStride !=
component_size_bytes) { /* Index buffer must be packed per glTF spec. */
throw GHOST_XrException(
"glTF: Accessor for indices uses bufferview with invalid 'byteStride'.");
}
validate_accessor(accessor, buffer_view, buffer, component_size_bytes, component_size_bytes);
if ((accessor.count % 3) != 0) { /* Since only triangles are supported, enforce that the number
of indices is divisible by 3. */
throw GHOST_XrException("glTF: Unexpected number of indices for triangle primitive");
}
const TSrcIndex *index_buffer = reinterpret_cast<const TSrcIndex *>(
buffer.data.data() + buffer_view.byteOffset + accessor.byteOffset);
for (uint32_t i = 0; i < accessor.count; i++) {
primitive.indices.push_back(*(index_buffer + i));
}
}
/**
* Reads index data from a glTF primitive into a GHOST_XrPrimitive.
*/
static void load_index_accessor(const tinygltf::Model &gltf_model,
const tinygltf::Accessor &accessor,
GHOST_XrPrimitive &primitive)
{
if (accessor.type != TINYGLTF_TYPE_SCALAR) {
throw GHOST_XrException("glTF: Accessor for indices specifies invalid 'type'.");
}
if (accessor.bufferView == -1) {
throw GHOST_XrException("glTF: Index accessor without bufferView is currently not supported.");
}
const tinygltf::BufferView &buffer_view = gltf_model.bufferViews.at(accessor.bufferView);
const tinygltf::Buffer &buffer = gltf_model.buffers.at(buffer_view.buffer);
if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) {
read_indices<uint8_t>(accessor, buffer_view, buffer, primitive);
}
else if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) {
read_indices<uint16_t>(accessor, buffer_view, buffer, primitive);
}
else if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) {
read_indices<uint32_t>(accessor, buffer_view, buffer, primitive);
}
else {
throw GHOST_XrException("glTF: Accessor for indices specifies invalid 'componentType'.");
}
}
static GHOST_XrPrimitive read_primitive(const tinygltf::Model &gltf_model,
const tinygltf::Primitive &gltf_primitive)
{
if (gltf_primitive.mode != TINYGLTF_MODE_TRIANGLES) {
throw GHOST_XrException(
"glTF: Unsupported primitive mode. Only TINYGLTF_MODE_TRIANGLES is supported.");
}
GHOST_XrPrimitive primitive;
/* glTF vertex data is stored in an attribute dictionary.Loop through each attribute and insert
* it into the GHOST_XrPrimitive. */
for (const auto &[attr_name, accessor_idx] : gltf_primitive.attributes) {
load_attribute_accessor(gltf_model, attr_name, accessor_idx, primitive);
}
if (gltf_primitive.indices != -1) {
/* If indices are specified for the glTF primitive, read them into the GHOST_XrPrimitive. */
load_index_accessor(gltf_model, gltf_model.accessors.at(gltf_primitive.indices), primitive);
}
return primitive;
}
/**
* Calculate node local and world transforms.
*/
static void calc_node_transforms(const tinygltf::Node &gltf_node,
const float parent_transform[4][4],
float r_local_transform[4][4],
float r_world_transform[4][4])
{
/* A node may specify either a 4x4 matrix or TRS (Translation - Rotation - Scale) values, but not
* both. */
if (gltf_node.matrix.size() == 16) {
const std::vector<double> &dm = gltf_node.matrix;
float m[4][4] = {(float)dm[0],
(float)dm[1],
(float)dm[2],
(float)dm[3],
(float)dm[4],
(float)dm[5],
(float)dm[6],
(float)dm[7],
(float)dm[8],
(float)dm[9],
(float)dm[10],
(float)dm[11],
(float)dm[12],
(float)dm[13],
(float)dm[14],
(float)dm[15]};
memcpy(r_local_transform, m, sizeof(float) * 16);
}
else {
/* No matrix is present, so construct a matrix from the TRS values (each one is optional). */
std::vector<double> translation = gltf_node.translation;
std::vector<double> rotation = gltf_node.rotation;
std::vector<double> scale = gltf_node.scale;
Eigen::Matrix4f &m = *(Eigen::Matrix4f *)r_local_transform;
Eigen::Quaternionf q;
Eigen::Matrix3f scalemat;
if (translation.size() != 3) {
translation.resize(3);
translation[0] = translation[1] = translation[2] = 0.0;
}
if (rotation.size() != 4) {
rotation.resize(4);
rotation[0] = rotation[1] = rotation[2] = 0.0;
rotation[3] = 1.0;
}
if (scale.size() != 3) {
scale.resize(3);
scale[0] = scale[1] = scale[2] = 1.0;
}
q.w() = (float)rotation[3];
q.x() = (float)rotation[0];
q.y() = (float)rotation[1];
q.z() = (float)rotation[2];
q.normalize();
scalemat.setIdentity();
scalemat(0, 0) = (float)scale[0];
scalemat(1, 1) = (float)scale[1];
scalemat(2, 2) = (float)scale[2];
m.setIdentity();
m.block<3, 3>(0, 0) = q.toRotationMatrix() * scalemat;
m.block<3, 1>(0, 3) = Eigen::Vector3f(
(float)translation[0], (float)translation[1], (float)translation[2]);
}
*(Eigen::Matrix4f *)r_world_transform = *(Eigen::Matrix4f *)parent_transform *
*(Eigen::Matrix4f *)r_local_transform;
}
static void load_node(const tinygltf::Model &gltf_model,
int gltf_node_id,
int32_t parent_idx,
const float parent_transform[4][4],
const std::string &parent_name,
const std::vector<XrControllerModelNodePropertiesMSFT> &node_properties,
std::vector<GHOST_XrControllerModelVertex> &vertices,
std::vector<uint32_t> &indices,
std::vector<GHOST_XrControllerModelComponent> &components,
std::vector<GHOST_XrControllerModelNode> &nodes,
std::vector<int32_t> &node_state_indices)
{
const tinygltf::Node &gltf_node = gltf_model.nodes.at(gltf_node_id);
float world_transform[4][4];
GHOST_XrControllerModelNode &node = nodes.emplace_back();
const int32_t node_idx = (int32_t)(nodes.size() - 1);
node.parent_idx = parent_idx;
calc_node_transforms(gltf_node, parent_transform, node.local_transform, world_transform);
for (size_t i = 0; i < node_properties.size(); ++i) {
if ((node_state_indices[i] < 0) && (parent_name == node_properties[i].parentNodeName) &&
(gltf_node.name == node_properties[i].nodeName)) {
node_state_indices[i] = node_idx;
break;
}
}
if (gltf_node.mesh != -1) {
const tinygltf::Mesh &gltf_mesh = gltf_model.meshes.at(gltf_node.mesh);
GHOST_XrControllerModelComponent &component = components.emplace_back();
node.component_idx = components.size() - 1;
memcpy(component.transform, world_transform, sizeof(component.transform));
component.vertex_offset = vertices.size();
component.index_offset = indices.size();
for (const tinygltf::Primitive &gltf_primitive : gltf_mesh.primitives) {
/* Read the primitive data from the glTF buffers. */
const GHOST_XrPrimitive primitive = read_primitive(gltf_model, gltf_primitive);
const size_t start_vertex = vertices.size();
size_t offset = start_vertex;
size_t count = primitive.vertices.size();
vertices.resize(offset + count);
memcpy(vertices.data() + offset,
primitive.vertices.data(),
count * sizeof(decltype(primitive.vertices)::value_type));
offset = indices.size();
count = primitive.indices.size();
indices.resize(offset + count);
for (size_t i = 0; i < count; i += 3) {
indices[offset + i + 0] = start_vertex + primitive.indices[i + 0];
indices[offset + i + 1] = start_vertex + primitive.indices[i + 2];
indices[offset + i + 2] = start_vertex + primitive.indices[i + 1];
}
}
component.vertex_count = vertices.size() - component.vertex_offset;
component.index_count = indices.size() - component.index_offset;
}
/* Recursively load all children. */
for (const int child_node_id : gltf_node.children) {
load_node(gltf_model,
child_node_id,
node_idx,
world_transform,
gltf_node.name,
node_properties,
vertices,
indices,
components,
nodes,
node_state_indices);
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name OpenXR Extension Functions
*
* \{ */
static PFN_xrGetControllerModelKeyMSFT g_xrGetControllerModelKeyMSFT = nullptr;
static PFN_xrLoadControllerModelMSFT g_xrLoadControllerModelMSFT = nullptr;
static PFN_xrGetControllerModelPropertiesMSFT g_xrGetControllerModelPropertiesMSFT = nullptr;
static PFN_xrGetControllerModelStateMSFT g_xrGetControllerModelStateMSFT = nullptr;
static XrInstance g_instance = XR_NULL_HANDLE;
#define INIT_EXTENSION_FUNCTION(name) \
CHECK_XR( \
xrGetInstanceProcAddr(instance, #name, reinterpret_cast<PFN_xrVoidFunction *>(&g_##name)), \
"Failed to get pointer to extension function: " #name);
static void init_controller_model_extension_functions(XrInstance instance)
{
if (instance != g_instance) {
g_instance = instance;
g_xrGetControllerModelKeyMSFT = nullptr;
g_xrLoadControllerModelMSFT = nullptr;
g_xrGetControllerModelPropertiesMSFT = nullptr;
g_xrGetControllerModelStateMSFT = nullptr;
}
if (g_xrGetControllerModelKeyMSFT == nullptr) {
INIT_EXTENSION_FUNCTION(xrGetControllerModelKeyMSFT);
}
if (g_xrLoadControllerModelMSFT == nullptr) {
INIT_EXTENSION_FUNCTION(xrLoadControllerModelMSFT);
}
if (g_xrGetControllerModelPropertiesMSFT == nullptr) {
INIT_EXTENSION_FUNCTION(xrGetControllerModelPropertiesMSFT);
}
if (g_xrGetControllerModelStateMSFT == nullptr) {
INIT_EXTENSION_FUNCTION(xrGetControllerModelStateMSFT);
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name GHOST_XrControllerModel
*
* \{ */
GHOST_XrControllerModel::GHOST_XrControllerModel(XrInstance instance,
const char *subaction_path_str)
{
init_controller_model_extension_functions(instance);
CHECK_XR(xrStringToPath(instance, subaction_path_str, &m_subaction_path),
(std::string("Failed to get user path \"") + subaction_path_str + "\".").data());
}
GHOST_XrControllerModel::~GHOST_XrControllerModel()
{
if (m_load_task.valid()) {
m_load_task.wait();
}
}
void GHOST_XrControllerModel::load(XrSession session)
{
if (m_data_loaded || m_load_task.valid()) {
return;
}
/* Get model key. */
XrControllerModelKeyStateMSFT key_state{XR_TYPE_CONTROLLER_MODEL_KEY_STATE_MSFT};
CHECK_XR(g_xrGetControllerModelKeyMSFT(session, m_subaction_path, &key_state),
"Failed to get controller model key state.");
if (key_state.modelKey != XR_NULL_CONTROLLER_MODEL_KEY_MSFT) {
m_model_key = key_state.modelKey;
/* Load asynchronously. */
m_load_task = std::async(std::launch::async,
[&, session = session]() { return loadControllerModel(session); });
}
}
void GHOST_XrControllerModel::loadControllerModel(XrSession session)
{
/* Load binary buffers. */
uint32_t buf_size = 0;
CHECK_XR(g_xrLoadControllerModelMSFT(session, m_model_key, 0, &buf_size, nullptr),
"Failed to get controller model buffer size.");
std::vector<uint8_t> buf((size_t)buf_size);
CHECK_XR(g_xrLoadControllerModelMSFT(session, m_model_key, buf_size, &buf_size, buf.data()),
"Failed to load controller model binary buffers.");
/* Convert to glTF model. */
tinygltf::TinyGLTF gltf_loader;
tinygltf::Model gltf_model;
std::string err_msg;
{
/* Workaround for TINYGLTF_NO_STB_IMAGE define. Set custom image loader to prevent failure when
* parsing image data. */
auto load_img_func = [](tinygltf::Image *img,
const int p0,
std::string *p1,
std::string *p2,
int p3,
int p4,
const unsigned char *p5,
int p6,
void *user_pointer) -> bool {
(void)img;
(void)p0;
(void)p1;
(void)p2;
(void)p3;
(void)p4;
(void)p5;
(void)p6;
(void)user_pointer;
return true;
};
gltf_loader.SetImageLoader(load_img_func, nullptr);
}
if (!gltf_loader.LoadBinaryFromMemory(&gltf_model, &err_msg, nullptr, buf.data(), buf_size)) {
throw GHOST_XrException(("Failed to load glTF controller model: " + err_msg).c_str());
}
/* Get node properties. */
XrControllerModelPropertiesMSFT model_properties{XR_TYPE_CONTROLLER_MODEL_PROPERTIES_MSFT};
model_properties.nodeCapacityInput = 0;
CHECK_XR(g_xrGetControllerModelPropertiesMSFT(session, m_model_key, &model_properties),
"Failed to get controller model node properties count.");
std::vector<XrControllerModelNodePropertiesMSFT> node_properties(
model_properties.nodeCountOutput, {XR_TYPE_CONTROLLER_MODEL_NODE_PROPERTIES_MSFT});
model_properties.nodeCapacityInput = (uint32_t)node_properties.size();
model_properties.nodeProperties = node_properties.data();
CHECK_XR(g_xrGetControllerModelPropertiesMSFT(session, m_model_key, &model_properties),
"Failed to get controller model node properties.");
m_node_state_indices.resize(node_properties.size(), -1);
/* Get mesh vertex data. */
const tinygltf::Scene &default_scene = gltf_model.scenes.at(
(gltf_model.defaultScene == -1) ? 0 : gltf_model.defaultScene);
const int32_t root_idx = -1;
const std::string root_name = "";
float root_transform[4][4] = {0};
root_transform[0][0] = root_transform[1][1] = root_transform[2][2] = root_transform[3][3] = 1.0f;
for (const int node_id : default_scene.nodes) {
load_node(gltf_model,
node_id,
root_idx,
root_transform,
root_name,
node_properties,
m_vertices,
m_indices,
m_components,
m_nodes,
m_node_state_indices);
}
m_data_loaded = true;
}
void GHOST_XrControllerModel::updateComponents(XrSession session)
{
if (!m_data_loaded) {
return;
}
/* Get node states. */
XrControllerModelStateMSFT model_state{XR_TYPE_CONTROLLER_MODEL_STATE_MSFT};
model_state.nodeCapacityInput = 0;
CHECK_XR(g_xrGetControllerModelStateMSFT(session, m_model_key, &model_state),
"Failed to get controller model node state count.");
const uint32_t count = model_state.nodeCountOutput;
std::vector<XrControllerModelNodeStateMSFT> node_states(
count, {XR_TYPE_CONTROLLER_MODEL_NODE_STATE_MSFT});
model_state.nodeCapacityInput = count;
model_state.nodeStates = node_states.data();
CHECK_XR(g_xrGetControllerModelStateMSFT(session, m_model_key, &model_state),
"Failed to get controller model node states.");
/* Update node local transforms. */
assert(m_node_state_indices.size() == count);
for (uint32_t state_idx = 0; state_idx < count; ++state_idx) {
const int32_t &node_idx = m_node_state_indices[state_idx];
if (node_idx >= 0) {
const XrPosef &pose = node_states[state_idx].nodePose;
Eigen::Matrix4f &m = *(Eigen::Matrix4f *)m_nodes[node_idx].local_transform;
Eigen::Quaternionf q(
pose.orientation.w, pose.orientation.x, pose.orientation.y, pose.orientation.z);
m.setIdentity();
m.block<3, 3>(0, 0) = q.toRotationMatrix();
m.block<3, 1>(0, 3) = Eigen::Vector3f(pose.position.x, pose.position.y, pose.position.z);
}
}
/* Calculate component transforms (in world space). */
std::vector<Eigen::Matrix4f> world_transforms(m_nodes.size());
uint32_t i = 0;
for (const GHOST_XrControllerModelNode &node : m_nodes) {
world_transforms[i] = (node.parent_idx >= 0) ? world_transforms[node.parent_idx] *
*(Eigen::Matrix4f *)node.local_transform :
*(Eigen::Matrix4f *)node.local_transform;
if (node.component_idx >= 0) {
memcpy(m_components[node.component_idx].transform,
world_transforms[i].data(),
sizeof(m_components[node.component_idx].transform));
}
++i;
}
}
void GHOST_XrControllerModel::getData(GHOST_XrControllerModelData &r_data)
{
if (m_data_loaded) {
r_data.count_vertices = (uint32_t)m_vertices.size();
r_data.vertices = m_vertices.data();
r_data.count_indices = (uint32_t)m_indices.size();
r_data.indices = m_indices.data();
r_data.count_components = (uint32_t)m_components.size();
r_data.components = m_components.data();
}
else {
r_data.count_vertices = 0;
r_data.vertices = nullptr;
r_data.count_indices = 0;
r_data.indices = nullptr;
r_data.count_components = 0;
r_data.components = nullptr;
}
}
/** \} */

View File

@@ -0,0 +1,59 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup GHOST
*/
/* Note: Requires OpenXR headers to be included before this one for OpenXR types (XrInstance,
* XrSession, etc.). */
#pragma once
#include <atomic>
#include <future>
#include <vector>
struct GHOST_XrControllerModelNode;
/**
* OpenXR glTF controller model.
*/
class GHOST_XrControllerModel {
public:
GHOST_XrControllerModel(XrInstance instance, const char *subaction_path);
~GHOST_XrControllerModel();
void load(XrSession session);
void updateComponents(XrSession session);
void getData(GHOST_XrControllerModelData &r_data);
private:
XrPath m_subaction_path = XR_NULL_PATH;
XrControllerModelKeyMSFT m_model_key = XR_NULL_CONTROLLER_MODEL_KEY_MSFT;
std::future<void> m_load_task;
std::atomic<bool> m_data_loaded = false;
std::vector<GHOST_XrControllerModelVertex> m_vertices;
std::vector<uint32_t> m_indices;
std::vector<GHOST_XrControllerModelComponent> m_components;
std::vector<GHOST_XrControllerModelNode> m_nodes;
/** Maps node states to nodes. */
std::vector<int32_t> m_node_state_indices;
void loadControllerModel(XrSession session);
};

View File

@@ -30,6 +30,7 @@
#include "GHOST_IXrGraphicsBinding.h"
#include "GHOST_XrAction.h"
#include "GHOST_XrContext.h"
#include "GHOST_XrControllerModel.h"
#include "GHOST_XrException.h"
#include "GHOST_XrSwapchain.h"
#include "GHOST_Xr_intern.h"
@@ -52,6 +53,8 @@ struct OpenXRSessionData {
std::vector<GHOST_XrSwapchain> swapchains;
std::map<std::string, GHOST_XrActionSet> action_sets;
/* Controller models identified by subaction path. */
std::map<std::string, GHOST_XrControllerModel> controller_models;
};
struct GHOST_XrDrawInfo {
@@ -916,3 +919,71 @@ void GHOST_XrSession::getActionCustomdataArray(const char *action_set_name,
}
/** \} */ /* Actions */
/* -------------------------------------------------------------------- */
/** \name Controller Model
*
* \{ */
bool GHOST_XrSession::loadControllerModel(const char *subaction_path)
{
if (!m_context->isExtensionEnabled(XR_MSFT_CONTROLLER_MODEL_EXTENSION_NAME)) {
return false;
}
XrSession session = m_oxr->session;
std::map<std::string, GHOST_XrControllerModel> &controller_models = m_oxr->controller_models;
std::map<std::string, GHOST_XrControllerModel>::iterator it = controller_models.find(
subaction_path);
if (it == controller_models.end()) {
XrInstance instance = m_context->getInstance();
it = controller_models
.emplace(std::piecewise_construct,
std::make_tuple(subaction_path),
std::make_tuple(instance, subaction_path))
.first;
}
it->second.load(session);
return true;
}
void GHOST_XrSession::unloadControllerModel(const char *subaction_path)
{
std::map<std::string, GHOST_XrControllerModel> &controller_models = m_oxr->controller_models;
if (controller_models.find(subaction_path) != controller_models.end()) {
controller_models.erase(subaction_path);
}
}
bool GHOST_XrSession::updateControllerModelComponents(const char *subaction_path)
{
XrSession session = m_oxr->session;
std::map<std::string, GHOST_XrControllerModel>::iterator it = m_oxr->controller_models.find(
subaction_path);
if (it == m_oxr->controller_models.end()) {
return false;
}
it->second.updateComponents(session);
return true;
}
bool GHOST_XrSession::getControllerModelData(const char *subaction_path,
GHOST_XrControllerModelData &r_data)
{
std::map<std::string, GHOST_XrControllerModel>::iterator it = m_oxr->controller_models.find(
subaction_path);
if (it == m_oxr->controller_models.end()) {
return false;
}
it->second.getData(r_data);
return true;
}
/** \} */ /* Controller Model */

View File

@@ -69,10 +69,8 @@ class GHOST_XrSession {
const char *const *profile_paths);
bool attachActionSets();
/**
* Action functions to be called post-session start.
* \param action_set_name: When `nullptr`, all attached action sets will be synced.
*/
/** Action functions to be called post-session start. */
/** param action_set_name : When `nullptr`, all attached action sets will be synced. */
bool syncActions(const char *action_set_name = nullptr);
bool applyHapticAction(const char *action_set_name,
const char *action_name,
@@ -84,12 +82,18 @@ class GHOST_XrSession {
const char *action_name,
const char *subaction_path);
/* Custom data (owned by Blender, not GHOST) accessors. */
/** Custom data (owned by Blender, not GHOST) accessors. */
void *getActionSetCustomdata(const char *action_set_name);
void *getActionCustomdata(const char *action_set_name, const char *action_name);
uint32_t getActionCount(const char *action_set_name);
void getActionCustomdataArray(const char *action_set_name, void **r_customdata_array);
/** Controller model functions. */
bool loadControllerModel(const char *subaction_path);
void unloadControllerModel(const char *subaction_path);
bool updateControllerModelComponents(const char *subaction_path);
bool getControllerModelData(const char *subaction_path, GHOST_XrControllerModelData &r_data);
private:
/** Pointer back to context managing this session. Would be nice to avoid, but needed to access
* custom callbacks set before session start. */

View File

@@ -21,6 +21,7 @@
#pragma once
#include <memory>
#include <string.h>
#include <vector>
#include "GHOST_Xr_openxr_includes.h"

View File

@@ -89,7 +89,7 @@ typedef struct localListBase {
void *first, *last;
} localListBase;
/* note: keep this struct aligned (e.g., irix/gcc) - Hos */
/* NOTE(@hos): keep this struct aligned (e.g., IRIX/GCC). */
typedef struct MemHead {
int tag1;
size_t len;
@@ -98,9 +98,8 @@ typedef struct MemHead {
const char *nextname;
int tag2;
short pad1;
short alignment; /* if non-zero aligned alloc was used
* and alignment is stored here.
*/
/* if non-zero aligned allocation was used and alignment is stored here. */
short alignment;
#ifdef DEBUG_MEMCOUNTER
int _count;
#endif

View File

@@ -219,7 +219,7 @@ def enable_addons(addons=None, support=None, disable=False, check_only=False):
try:
import bpy
except ModuleNotFoundError:
print("Could not import bpy, enable_addons must be run from whithin Blender.")
print("Could not import bpy, enable_addons must be run from within Blender.")
return
if addons is None:

View File

@@ -54,11 +54,26 @@ def update_factory_startup_grease_pencils():
gpd.onion_keyframe_type = 'ALL'
def update_factory_startup_theme():
# To prevent saving over the current theme Preferences,
# store the current state of use_preferences_save to use later.
preferences = bpy.context.preferences
save_preferences_state = preferences.use_preferences_save
# Turn use_preferences_save off and set header background alpha.
preferences.use_preferences_save = False
preferences.themes['Default'].view_3d.space.header[3] = 0.8
# Restore the original use_preferences_save status.
preferences.use_preferences_save = save_preferences_state
@persistent
def load_handler(_):
update_factory_startup_screens()
update_factory_startup_scenes()
update_factory_startup_grease_pencils()
update_factory_startup_theme()
def register():

View File

@@ -46,44 +46,85 @@ def selected_sequences_len(context):
def draw_color_balance(layout, color_balance):
layout.prop(color_balance, "correction_method")
layout.use_property_split = False
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
col = flow.column()
box = col.box()
split = box.split(factor=0.35)
col = split.column(align=True)
col.label(text="Lift:")
col.separator()
col.separator()
col.prop(color_balance, "lift", text="")
col.prop(color_balance, "invert_lift", text="Invert", icon='ARROW_LEFTRIGHT')
split.template_color_picker(color_balance, "lift", value_slider=True, cubic=True)
if color_balance.correction_method == 'LIFT_GAMMA_GAIN':
col = flow.column()
col = flow.column()
box = col.box()
split = box.split(factor=0.35)
col = split.column(align=True)
col.label(text="Lift:")
col.separator()
col.separator()
col.prop(color_balance, "lift", text="")
col.prop(color_balance, "invert_lift", text="Invert", icon='ARROW_LEFTRIGHT')
split.template_color_picker(color_balance, "lift", value_slider=True, cubic=True)
box = col.box()
split = box.split(factor=0.35)
col = split.column(align=True)
col.label(text="Gamma:")
col.separator()
col.separator()
col.prop(color_balance, "gamma", text="")
col.prop(color_balance, "invert_gamma", text="Invert", icon='ARROW_LEFTRIGHT')
split.template_color_picker(color_balance, "gamma", value_slider=True, lock_luminosity=True, cubic=True)
col = flow.column()
col = flow.column()
box = col.box()
split = box.split(factor=0.35)
col = split.column(align=True)
col.label(text="Gamma:")
col.separator()
col.separator()
col.prop(color_balance, "gamma", text="")
col.prop(color_balance, "invert_gamma", text="Invert", icon='ARROW_LEFTRIGHT')
split.template_color_picker(color_balance, "gamma", value_slider=True, lock_luminosity=True, cubic=True)
box = col.box()
split = box.split(factor=0.35)
col = split.column(align=True)
col.label(text="Gain:")
col.separator()
col.separator()
col.prop(color_balance, "gain", text="")
col.prop(color_balance, "invert_gain", text="Invert", icon='ARROW_LEFTRIGHT')
split.template_color_picker(color_balance, "gain", value_slider=True, lock_luminosity=True, cubic=True)
col = flow.column()
box = col.box()
split = box.split(factor=0.35)
col = split.column(align=True)
col.label(text="Gain:")
col.separator()
col.separator()
col.prop(color_balance, "gain", text="")
col.prop(color_balance, "invert_gain", text="Invert", icon='ARROW_LEFTRIGHT')
split.template_color_picker(color_balance, "gain", value_slider=True, lock_luminosity=True, cubic=True)
elif color_balance.correction_method == 'OFFSET_POWER_SLOPE':
col = flow.column()
box = col.box()
split = box.split(factor=0.35)
col = split.column(align=True)
col.label(text="Offset:")
col.separator()
col.separator()
col.prop(color_balance, "offset", text="")
col.prop(color_balance, "invert_offset", text="Invert", icon='ARROW_LEFTRIGHT')
split.template_color_picker(color_balance, "offset", value_slider=True, cubic=True)
col = flow.column()
box = col.box()
split = box.split(factor=0.35)
col = split.column(align=True)
col.label(text="Power:")
col.separator()
col.separator()
col.prop(color_balance, "power", text="")
col.prop(color_balance, "invert_power", text="Invert", icon='ARROW_LEFTRIGHT')
split.template_color_picker(color_balance, "power", value_slider=True, cubic=True)
col = flow.column()
box = col.box()
split = box.split(factor=0.35)
col = split.column(align=True)
col.label(text="Slope:")
col.separator()
col.separator()
col.prop(color_balance, "slope", text="")
col.prop(color_balance, "invert_slope", text="Invert", icon='ARROW_LEFTRIGHT')
split.template_color_picker(color_balance, "slope", value_slider=True, cubic=True)
class SEQUENCER_PT_active_tool(ToolActivePanelHelper, Panel):
@@ -148,8 +189,12 @@ class SEQUENCER_HT_header(Header):
if st.view_type in {'SEQUENCER', 'SEQUENCER_PREVIEW'}:
row = layout.row(align=True)
row.prop(sequencer_tool_settings, "overlap_mode", text="")
if st.view_type == 'SEQUENCER_PREVIEW':
row = layout.row(align=True)
row.prop(sequencer_tool_settings, "pivot_point", text="", icon_only=True)
if st.view_type in {'SEQUENCER', 'SEQUENCER_PREVIEW'}:
row = layout.row(align=True)
row.prop(tool_settings, "use_snap_sequencer", text="")
sub = row.row(align=True)

View File

@@ -279,6 +279,7 @@ shader_node_categories = [
]),
ShaderNodeCategory("SH_NEW_CONVERTOR", "Converter", items=[
NodeItem("ShaderNodeMapRange"),
NodeItem("ShaderNodeFloatCurve"),
NodeItem("ShaderNodeClamp"),
NodeItem("ShaderNodeMath"),
NodeItem("ShaderNodeValToRGB"),
@@ -524,7 +525,11 @@ geometry_node_categories = [
NodeItem("GeometryNodeCurveFill"),
NodeItem("GeometryNodeCurveTrim"),
NodeItem("GeometryNodeCurveLength"),
NodeItem("GeometryNodeCurveSplineType"),
NodeItem("GeometryNodeSplineLength"),
NodeItem("GeometryNodeCurveSubdivide"),
NodeItem("GeometryNodeCurveParameter"),
NodeItem("GeometryNodeCurveSetHandles"),
NodeItem("GeometryNodeInputTangent"),
NodeItem("GeometryNodeCurveSample"),
NodeItem("GeometryNodeCurveFillet"),
@@ -615,9 +620,11 @@ geometry_node_categories = [
]),
GeometryNodeCategory("GEO_UTILITIES", "Utilities", items=[
NodeItem("ShaderNodeMapRange"),
NodeItem("ShaderNodeFloatCurve"),
NodeItem("ShaderNodeClamp"),
NodeItem("ShaderNodeMath"),
NodeItem("FunctionNodeBooleanMath"),
NodeItem("FunctionNodeRotateEuler"),
NodeItem("FunctionNodeFloatCompare"),
NodeItem("FunctionNodeFloatToInt"),
NodeItem("GeometryNodeSwitch"),

View File

@@ -14,11 +14,17 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup blendthumb
*
* Thumbnail from Blend file extraction for MS-Windows (DLL).
*/
#include <new>
#include <objbase.h>
#include <shlobj.h> // For SHChangeNotify
#include <shlobj.h> /* For #SHChangeNotify */
#include <shlwapi.h>
#include <thumbcache.h> // For IThumbnailProvider.
#include <thumbcache.h> /* For IThumbnailProvider */
extern HRESULT CBlendThumb_CreateInstance(REFIID riid, void **ppv);
@@ -33,16 +39,16 @@ struct CLASS_OBJECT_INIT {
PFNCREATEINSTANCE pfnCreate;
};
// add classes supported by this module here
/* Add classes supported by this module here. */
const CLASS_OBJECT_INIT c_rgClassObjectInit[] = {
{&CLSID_BlendThumbHandler, CBlendThumb_CreateInstance}};
long g_cRefModule = 0;
// Handle the DLL's module
HINSTANCE g_hInst = NULL;
/** Handle the DLL's module */
HINSTANCE g_hInst = nullptr;
// Standard DLL functions
/** Standard DLL functions. */
STDAPI_(BOOL) DllMain(HINSTANCE hInstance, DWORD dwReason, void *)
{
if (dwReason == DLL_PROCESS_ATTACH) {
@@ -54,7 +60,7 @@ STDAPI_(BOOL) DllMain(HINSTANCE hInstance, DWORD dwReason, void *)
STDAPI DllCanUnloadNow()
{
// Only allow the DLL to be unloaded after all outstanding references have been released
/* Only allow the DLL to be unloaded after all outstanding references have been released. */
return (g_cRefModule == 0) ? S_OK : S_FALSE;
}
@@ -76,7 +82,7 @@ class CClassFactory : public IClassFactory {
REFIID riid,
void **ppv)
{
*ppv = NULL;
*ppv = nullptr;
HRESULT hr = CLASS_E_CLASSNOTAVAILABLE;
for (size_t i = 0; i < cClassObjectInits; i++) {
if (clsid == *pClassObjectInits[i].pClsid) {
@@ -87,7 +93,8 @@ class CClassFactory : public IClassFactory {
hr = pClassFactory->QueryInterface(riid, ppv);
pClassFactory->Release();
}
break; // match found
/* Match found. */
break;
}
}
return hr;
@@ -98,7 +105,7 @@ class CClassFactory : public IClassFactory {
DllAddRef();
}
// IUnknown
/** #IUnknown */
IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] = {QITABENT(CClassFactory, IClassFactory), {0}};
@@ -119,7 +126,7 @@ class CClassFactory : public IClassFactory {
return cRef;
}
// IClassFactory
/** #IClassFactory */
IFACEMETHODIMP CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv)
{
return punkOuter ? CLASS_E_NOAGGREGATION : _pfnCreate(riid, ppv);
@@ -152,33 +159,37 @@ STDAPI DllGetClassObject(REFCLSID clsid, REFIID riid, void **ppv)
clsid, c_rgClassObjectInit, ARRAYSIZE(c_rgClassObjectInit), riid, ppv);
}
// A struct to hold the information required for a registry entry
/**
* A struct to hold the information required for a registry entry.
*/
struct REGISTRY_ENTRY {
HKEY hkeyRoot;
PCWSTR pszKeyName;
PCWSTR pszValueName;
DWORD dwValueType;
PCWSTR pszData; // These two fields could/should have been a union, but C++
DWORD dwData; // only lets you initialize the first field in a union.
/** These two fields could/should have been a union, but C++ */
PCWSTR pszData;
/** Only lets you initialize the first field in a union. */
DWORD dwData;
};
// Creates a registry key (if needed) and sets the default value of the key
/**
* Creates a registry key (if needed) and sets the default value of the key.
*/
HRESULT CreateRegKeyAndSetValue(const REGISTRY_ENTRY *pRegistryEntry)
{
HKEY hKey;
HRESULT hr = HRESULT_FROM_WIN32(RegCreateKeyExW(pRegistryEntry->hkeyRoot,
pRegistryEntry->pszKeyName,
0,
NULL,
nullptr,
REG_OPTION_NON_VOLATILE,
KEY_SET_VALUE,
NULL,
nullptr,
&hKey,
NULL));
nullptr));
if (SUCCEEDED(hr)) {
// All this just to support REG_DWORD...
/* All this just to support #REG_DWORD. */
DWORD size;
DWORD data;
BYTE *lpData = (LPBYTE)pRegistryEntry->pszData;
@@ -202,9 +213,9 @@ HRESULT CreateRegKeyAndSetValue(const REGISTRY_ENTRY *pRegistryEntry)
return hr;
}
//
// Registers this COM server
//
/**
* Registers this COM server.
*/
STDAPI DllRegisterServer()
{
HRESULT hr;
@@ -216,15 +227,15 @@ STDAPI DllRegisterServer()
}
else {
const REGISTRY_ENTRY rgRegistryEntries[] = {
// RootKey KeyName ValueName ValueType Data
/* `RootKey KeyName ValueName ValueType Data` */
{HKEY_CURRENT_USER,
L"Software\\Classes\\CLSID\\" SZ_CLSID_BLENDTHUMBHANDLER,
NULL,
nullptr,
REG_SZ,
SZ_BLENDTHUMBHANDLER},
{HKEY_CURRENT_USER,
L"Software\\Classes\\CLSID\\" SZ_CLSID_BLENDTHUMBHANDLER L"\\InProcServer32",
NULL,
nullptr,
REG_SZ,
szModuleName},
{HKEY_CURRENT_USER,
@@ -237,10 +248,10 @@ STDAPI DllRegisterServer()
L"Treatment",
REG_DWORD,
0,
0}, // doesn't appear to do anything...
0}, /* This doesn't appear to do anything. */
{HKEY_CURRENT_USER,
L"Software\\Classes\\.blend\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}",
NULL,
nullptr,
REG_SZ,
SZ_CLSID_BLENDTHUMBHANDLER},
};
@@ -251,17 +262,17 @@ STDAPI DllRegisterServer()
}
}
if (SUCCEEDED(hr)) {
// This tells the shell to invalidate the thumbnail cache. This is important because any
// .blend files viewed before registering this handler would otherwise show cached blank
// thumbnails.
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
/* This tells the shell to invalidate the thumbnail cache.
* This is important because any `.blend` files viewed before registering this handler
* would otherwise show cached blank thumbnails. */
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
}
return hr;
}
//
// Unregisters this COM server
//
/**
* Unregisters this COM server
*/
STDAPI DllUnregisterServer()
{
HRESULT hr = S_OK;
@@ -270,11 +281,11 @@ STDAPI DllUnregisterServer()
L"Software\\Classes\\CLSID\\" SZ_CLSID_BLENDTHUMBHANDLER,
L"Software\\Classes\\.blend\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}"};
// Delete the registry entries
/* Delete the registry entries. */
for (int i = 0; i < ARRAYSIZE(rgpszKeys) && SUCCEEDED(hr); i++) {
hr = HRESULT_FROM_WIN32(RegDeleteTreeW(HKEY_CURRENT_USER, rgpszKeys[i]));
if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) {
// If the registry entry has already been deleted, say S_OK.
/* If the registry entry has already been deleted, say S_OK. */
hr = S_OK;
}
}

View File

@@ -26,10 +26,13 @@
#include "BLI_function_ref.hh"
#include "BLI_map.hh"
#include "BLI_set.hh"
#include "BLI_string_ref.hh"
#include "BLI_uuid.h"
#include "BLI_vector.hh"
#include "BKE_asset_catalog_path.hh"
#include <map>
#include <memory>
#include <set>
@@ -38,7 +41,6 @@
namespace blender::bke {
using CatalogID = bUUID;
using CatalogPath = std::string;
using CatalogPathComponent = std::string;
/* Would be nice to be able to use `std::filesystem::path` for this, but it's currently not
* available on the minimum macOS target version. */
@@ -47,12 +49,12 @@ using CatalogFilePath = std::string;
class AssetCatalog;
class AssetCatalogDefinitionFile;
class AssetCatalogTree;
class AssetCatalogFilter;
/* Manages the asset catalogs of a single asset library (i.e. of catalogs defined in a single
* directory hierarchy). */
class AssetCatalogService {
public:
static const char PATH_SEPARATOR;
static const CatalogFilePath DEFAULT_CATALOG_FILENAME;
public:
@@ -94,15 +96,23 @@ class AssetCatalogService {
void merge_from_disk_before_writing();
/** Return catalog with the given ID. Return nullptr if not found. */
AssetCatalog *find_catalog(CatalogID catalog_id);
AssetCatalog *find_catalog(CatalogID catalog_id) const;
/** Return first catalog with the given path. Return nullptr if not found. This is not an
* efficient call as it's just a linear search over the catalogs. */
AssetCatalog *find_catalog_by_path(const CatalogPath &path) const;
AssetCatalog *find_catalog_by_path(const AssetCatalogPath &path) const;
/**
* Create a filter object that can be used to determine whether an asset belongs to the given
* catalog, or any of the catalogs in the sub-tree rooted at the given catalog.
*
* \see #AssetCatalogFilter
*/
AssetCatalogFilter create_catalog_filter(CatalogID active_catalog_id) const;
/** Create a catalog with some sensible auto-generated catalog ID.
* The catalog will be saved to the default catalog file.*/
AssetCatalog *create_catalog(const CatalogPath &catalog_path);
AssetCatalog *create_catalog(const AssetCatalogPath &catalog_path);
/**
* Soft-delete the catalog, ensuring it actually gets deleted when the catalog definition file is
@@ -112,7 +122,7 @@ class AssetCatalogService {
/**
* Update the catalog path, also updating the catalog path of all sub-catalogs.
*/
void update_catalog_path(CatalogID catalog_id, const CatalogPath &new_catalog_path);
void update_catalog_path(CatalogID catalog_id, const AssetCatalogPath &new_catalog_path);
AssetCatalogTree *get_catalog_tree();
@@ -124,7 +134,7 @@ class AssetCatalogService {
Map<CatalogID, std::unique_ptr<AssetCatalog>> catalogs_;
Map<CatalogID, std::unique_ptr<AssetCatalog>> deleted_catalogs_;
std::unique_ptr<AssetCatalogDefinitionFile> catalog_definition_file_;
std::unique_ptr<AssetCatalogTree> catalog_tree_;
std::unique_ptr<AssetCatalogTree> catalog_tree_ = std::make_unique<AssetCatalogTree>();
CatalogFilePath asset_library_root_;
void load_directory_recursive(const CatalogFilePath &directory_path);
@@ -150,6 +160,11 @@ class AssetCatalogService {
std::unique_ptr<AssetCatalogTree> read_into_tree();
void rebuild_tree();
/**
* For every catalog, ensure that its parent path also has a known catalog.
*/
void create_missing_catalogs();
};
/**
@@ -172,7 +187,7 @@ class AssetCatalogTreeItem {
StringRef get_name() const;
/** Return the full catalog path, defined as the name of this catalog prefixed by the full
* catalog path of its parent and a separator. */
CatalogPath catalog_path() const;
AssetCatalogPath catalog_path() const;
int count_parents() const;
bool has_children() const;
@@ -280,10 +295,10 @@ class AssetCatalogDefinitionFile {
class AssetCatalog {
public:
AssetCatalog() = default;
AssetCatalog(CatalogID catalog_id, const CatalogPath &path, const std::string &simple_name);
AssetCatalog(CatalogID catalog_id, const AssetCatalogPath &path, const std::string &simple_name);
CatalogID catalog_id;
CatalogPath path;
AssetCatalogPath path;
/**
* Simple, human-readable name for the asset catalog. This is stored on assets alongside the
* catalog ID; the catalog ID is a UUID that is not human-readable,
@@ -297,27 +312,17 @@ class AssetCatalog {
bool is_deleted = false;
} flags;
/**
* \return true only if this catalog's path is contained within the given path.
* When this catalog's path is equal to the given path, return true as well.
*
* Note that non-normalized paths (so for example starting or ending with a slash) are not
* supported, and result in undefined behavior.
*/
bool is_contained_in(const CatalogPath &other_path) const;
/**
* Create a new Catalog with the given path, auto-generating a sensible catalog simple-name.
*
* NOTE: the given path will be cleaned up (trailing spaces removed, etc.), so the returned
* `AssetCatalog`'s path differ from the given one.
*/
static std::unique_ptr<AssetCatalog> from_path(const CatalogPath &path);
static CatalogPath cleanup_path(const CatalogPath &path);
static std::unique_ptr<AssetCatalog> from_path(const AssetCatalogPath &path);
protected:
/** Generate a sensible catalog ID for the given path. */
static std::string sensible_simple_name_for_path(const CatalogPath &path);
static std::string sensible_simple_name_for_path(const AssetCatalogPath &path);
};
/** Comparator for asset catalogs, ordering by (path, UUID). */
@@ -336,4 +341,20 @@ struct AssetCatalogPathCmp {
* Being a set, duplicates are removed. The catalog's simple name is ignored in this. */
using AssetCatalogOrderedSet = std::set<const AssetCatalog *, AssetCatalogPathCmp>;
/**
* Filter that can determine whether an asset should be visible or not, based on its catalog ID.
*
* \see AssetCatalogService::create_filter()
*/
class AssetCatalogFilter {
public:
bool contains(CatalogID asset_catalog_id) const;
protected:
friend AssetCatalogService;
const Set<CatalogID> matching_catalog_ids;
explicit AssetCatalogFilter(Set<CatalogID> &&matching_catalog_ids);
};
} // namespace blender::bke

View File

@@ -0,0 +1,143 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup bke
*/
#pragma once
#ifndef __cplusplus
# error This is a C++ header.
#endif
#include "BLI_function_ref.hh"
#include "BLI_string_ref.hh"
#include "BLI_sys_types.h"
#include <string>
namespace blender::bke {
/**
* Location of an Asset Catalog in the catalog tree, denoted by slash-separated path components.
*
* Each path component is a string that is not allowed to have slashes or colons. The latter is to
* make things easy to save in the colon-delimited Catalog Definition File format.
*
* The path of a catalog determines where in the catalog hierarchy the catalog is shown. Examples
* are "Characters/Ellie/Poses/Hand" or "Kit_bash/City/Skyscrapers". The path looks like a
* file-system path, with a few differences:
*
* - Only slashes are used as path component separators.
* - All paths are absolute, so there is no need for a leading slash.
*
* See https://wiki.blender.org/wiki/Source/Architecture/Asset_System/Catalogs
*
* Paths are stored as byte sequences, and assumed to be UTF-8.
*/
class AssetCatalogPath {
friend std::ostream &operator<<(std::ostream &stream, const AssetCatalogPath &path_to_append);
private:
/**
* The path itself, such as "Agents/Secret/327".
*/
std::string path_;
public:
static const char SEPARATOR;
AssetCatalogPath() = delete;
AssetCatalogPath(StringRef path);
AssetCatalogPath(const std::string &path);
AssetCatalogPath(const char *path);
AssetCatalogPath(const AssetCatalogPath &other_path) = default;
AssetCatalogPath(AssetCatalogPath &&other_path) noexcept;
~AssetCatalogPath() = default;
uint64_t hash() const;
uint64_t length() const; /* Length of the path in bytes. */
/** C-string representation of the path. */
const char *c_str() const;
const std::string &str() const;
/* In-class operators, because of the implicit `AssetCatalogPath(StringRef)` constructor.
* Otherwise `string == string` could cast both sides to `AssetCatalogPath`. */
bool operator==(const AssetCatalogPath &other_path) const;
bool operator!=(const AssetCatalogPath &other_path) const;
bool operator<(const AssetCatalogPath &other_path) const;
AssetCatalogPath &operator=(const AssetCatalogPath &other_path) = default;
AssetCatalogPath &operator=(AssetCatalogPath &&other_path) = default;
/** Concatenate two paths, returning the new path. */
AssetCatalogPath operator/(const AssetCatalogPath &path_to_append) const;
/* False when the path is empty, true otherwise. */
operator bool() const;
/**
* Clean up the path. This ensures:
* - Every path component is stripped of its leading/trailing spaces.
* - Empty components (caused by double slashes or leading/trailing slashes) are removed.
* - Invalid characters are replaced with valid ones.
*/
[[nodiscard]] AssetCatalogPath cleanup() const;
/**
* \return true only if the given path is a parent of this catalog's path.
* When this catalog's path is equal to the given path, return true as well.
* In other words, this defines a weak subset.
*
* True: "some/path/there" is contained in "some/path" and "some".
* False: "path/there" is not contained in "some/path/there".
*
* Note that non-cleaned-up paths (so for example starting or ending with a
* slash) are not supported, and result in undefined behavior.
*/
bool is_contained_in(const AssetCatalogPath &other_path) const;
/**
* \return the parent path, or an empty path if there is no parent.
*/
AssetCatalogPath parent() const;
/**
* Change the initial part of the path from `from_path` to `to_path`.
* If this path does not start with `from_path`, return an empty path as result.
*
* Example:
*
* AssetCatalogPath path("some/path/to/some/catalog");
* path.rebase("some/path", "new/base") -> "new/base/to/some/catalog"
*/
AssetCatalogPath rebase(const AssetCatalogPath &from_path,
const AssetCatalogPath &to_path) const;
/** Call the callback function for each path component, in left-to-right order. */
using ComponentIteratorFn = FunctionRef<void(StringRef component_name, bool is_last_component)>;
void iterate_components(ComponentIteratorFn callback) const;
protected:
/** Strip leading/trailing spaces and replace disallowed characters. */
static std::string cleanup_component(StringRef component_name);
};
/** Output the path as string. */
std::ostream &operator<<(std::ostream &stream, const AssetCatalogPath &path_to_append);
} // namespace blender::bke

View File

@@ -21,6 +21,7 @@
#pragma once
struct Main;
//
#ifdef __cplusplus
extern "C" {

View File

@@ -39,7 +39,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 31
#define BLENDER_FILE_SUBVERSION 32
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and show a warning if the file

View File

@@ -97,6 +97,7 @@ void BKE_curvemapping_evaluate_premulRGBF(const struct CurveMapping *cumap,
float vecout[3],
const float vecin[3]);
bool BKE_curvemapping_RGBA_does_something(const struct CurveMapping *cumap);
void BKE_curvemapping_table_F(const struct CurveMapping *cumap, float **array, int *size);
void BKE_curvemapping_table_RGBA(const struct CurveMapping *cumap, float **array, int *size);
/* non-const, these modify the curve */

View File

@@ -133,6 +133,9 @@ enum {
LIB_ID_COPY_SHAPEKEY = 1 << 26,
/** EXCEPTION! Specific deep-copy of node trees used e.g. for rendering purposes. */
LIB_ID_COPY_NODETREE_LOCALIZE = 1 << 27,
/** EXCEPTION! Specific handling of RB objects regarding collections differs depending whether we
duplicate scene/collections, or objects. */
LIB_ID_COPY_RIGID_BODY_NO_COLLECTION_HANDLING = 1 << 28,
/* *** Helper 'defines' gathering most common flag sets. *** */
/** Shapekeys are not real ID's, more like local data to geometry IDs... */
@@ -261,7 +264,8 @@ struct ID *BKE_id_copy_ex(struct Main *bmain,
const int flag);
struct ID *BKE_id_copy_for_duplicate(struct Main *bmain,
struct ID *id,
const uint duplicate_flags);
const uint duplicate_flags,
const int copy_flags);
void BKE_lib_id_swap(struct Main *bmain, struct ID *id_a, struct ID *id_b);
void BKE_lib_id_swap_full(struct Main *bmain, struct ID *id_a, struct ID *id_b);

View File

@@ -651,9 +651,8 @@ extern void (*BKE_mesh_batch_cache_free_cb)(struct Mesh *me);
/* Inlines */
/* Instead of -1 that function uses ORIGINDEX_NONE as defined in BKE_customdata.h,
* but I don't want to force every user of BKE_mesh.h to also include that file.
* ~~ Sybren */
/* NOTE(@sybren): Instead of -1 that function uses ORIGINDEX_NONE as defined in BKE_customdata.h,
* but I don't want to force every user of BKE_mesh.h to also include that file. */
BLI_INLINE int BKE_mesh_origindex_mface_mpoly(const int *index_mf_to_mpoly,
const int *index_mp_to_orig,
const int i)

View File

@@ -417,7 +417,7 @@ typedef struct bNodeTreeType {
void (*local_sync)(struct bNodeTree *localtree, struct bNodeTree *ntree);
void (*local_merge)(struct Main *bmain, struct bNodeTree *localtree, struct bNodeTree *ntree);
/* Tree update. Overrides nodetype->updatetreefunc! */
/* Tree update. Overrides `nodetype->updatetreefunc` ! */
void (*update)(struct bNodeTree *ntree);
bool (*validate_link)(struct bNodeTree *ntree, struct bNodeLink *link);
@@ -443,7 +443,7 @@ void ntreeTypeFreeLink(const struct bNodeTreeType *nt);
bool ntreeIsRegistered(struct bNodeTree *ntree);
struct GHashIterator *ntreeTypeGetIterator(void);
/* helper macros for iterating over tree types */
/* Helper macros for iterating over tree types. */
#define NODE_TREE_TYPES_BEGIN(ntype) \
{ \
GHashIterator *__node_tree_type_iter__ = ntreeTypeGetIterator(); \
@@ -548,7 +548,7 @@ void nodeUnregisterType(struct bNodeType *ntype);
bool nodeTypeUndefined(struct bNode *node);
struct GHashIterator *nodeTypeGetIterator(void);
/* helper macros for iterating over node types */
/* Helper macros for iterating over node types. */
#define NODE_TYPES_BEGIN(ntype) \
{ \
GHashIterator *__node_type_iter__ = nodeTypeGetIterator(); \
@@ -574,7 +574,7 @@ const char *nodeStaticSocketType(int type, int subtype);
const char *nodeStaticSocketInterfaceType(int type, int subtype);
const char *nodeStaticSocketLabel(int type, int subtype);
/* helper macros for iterating over node types */
/* Helper macros for iterating over node types. */
#define NODE_SOCKET_TYPES_BEGIN(stype) \
{ \
GHashIterator *__node_socket_type_iter__ = nodeSocketTypeGetIterator(); \
@@ -746,7 +746,8 @@ int BKE_node_clipboard_get_type(void);
/* Node Instance Hash */
typedef struct bNodeInstanceHash {
GHash *ghash; /* XXX should be made a direct member, GHash allocation needs to support it */
/** XXX should be made a direct member, #GHash allocation needs to support it */
GHash *ghash;
} bNodeInstanceHash;
typedef void (*bNodeInstanceValueFP)(void *value);
@@ -1102,6 +1103,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree,
#define SH_NODE_VERTEX_COLOR 706
#define SH_NODE_OUTPUT_AOV 707
#define SH_NODE_VECTOR_ROTATE 708
#define SH_NODE_CURVE_FLOAT 709
/* custom defines options for Material node */
// #define SH_NODE_MAT_DIFF 1
@@ -1346,7 +1348,7 @@ void ntreeCompositCryptomatteLayerPrefix(const Scene *scene,
const bNode *node,
char *r_prefix,
size_t prefix_len);
/* Update the runtime layer names with the cryptomatte layer names of the references
/* Update the runtime layer names with the crypto-matte layer names of the references
* render layer or image. */
void ntreeCompositCryptomatteUpdateLayerNames(const Scene *scene, bNode *node);
struct CryptomatteSession *ntreeCompositCryptomatteSession(const Scene *scene, bNode *node);
@@ -1507,7 +1509,10 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define GEO_NODE_POINTS_TO_VERTICES 1094
#define GEO_NODE_CURVE_REVERSE 1095
#define GEO_NODE_PROXIMITY 1096
#define GEO_NODE_CURVE_SUBDIVIDE 1097
#define GEO_NODE_INPUT_SPLINE_LENGTH 1098
#define GEO_NODE_CURVE_SPLINE_TYPE 1099
#define GEO_NODE_CURVE_SET_HANDLES 1100
/** \} */
/* -------------------------------------------------------------------- */
@@ -1525,6 +1530,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define FN_NODE_STRING_SUBSTRING 1212
#define FN_NODE_INPUT_SPECIAL_CHARACTERS 1213
#define FN_NODE_RANDOM_VALUE 1214
#define FN_NODE_ROTATE_EULER 1215
/** \} */

View File

@@ -85,6 +85,7 @@ set(SRC
intern/armature_update.c
intern/asset.cc
intern/asset_catalog.cc
intern/asset_catalog_path.cc
intern/asset_library.cc
intern/attribute.c
intern/attribute_access.cc
@@ -306,6 +307,7 @@ set(SRC
BKE_armature.hh
BKE_asset.h
BKE_asset_catalog.hh
BKE_asset_catalog_path.hh
BKE_asset_library.h
BKE_asset_library.hh
BKE_attribute.h
@@ -789,6 +791,7 @@ if(WITH_GTESTS)
intern/action_test.cc
intern/armature_test.cc
intern/asset_catalog_test.cc
intern/asset_catalog_path_test.cc
intern/asset_library_test.cc
intern/asset_test.cc
intern/cryptomatte_test.cc

View File

@@ -38,7 +38,6 @@
namespace blender::bke {
const char AssetCatalogService::PATH_SEPARATOR = '/';
const CatalogFilePath AssetCatalogService::DEFAULT_CATALOG_FILENAME = "blender_assets.cats.txt";
/* For now this is the only version of the catalog definition files that is supported.
@@ -66,16 +65,16 @@ bool AssetCatalogService::is_empty() const
return catalogs_.is_empty();
}
AssetCatalog *AssetCatalogService::find_catalog(CatalogID catalog_id)
AssetCatalog *AssetCatalogService::find_catalog(CatalogID catalog_id) const
{
std::unique_ptr<AssetCatalog> *catalog_uptr_ptr = this->catalogs_.lookup_ptr(catalog_id);
const std::unique_ptr<AssetCatalog> *catalog_uptr_ptr = this->catalogs_.lookup_ptr(catalog_id);
if (catalog_uptr_ptr == nullptr) {
return nullptr;
}
return catalog_uptr_ptr->get();
}
AssetCatalog *AssetCatalogService::find_catalog_by_path(const CatalogPath &path) const
AssetCatalog *AssetCatalogService::find_catalog_by_path(const AssetCatalogPath &path) const
{
for (const auto &catalog : catalogs_.values()) {
if (catalog->path == path) {
@@ -86,6 +85,33 @@ AssetCatalog *AssetCatalogService::find_catalog_by_path(const CatalogPath &path)
return nullptr;
}
AssetCatalogFilter AssetCatalogService::create_catalog_filter(
const CatalogID active_catalog_id) const
{
Set<CatalogID> matching_catalog_ids;
matching_catalog_ids.add(active_catalog_id);
const AssetCatalog *active_catalog = find_catalog(active_catalog_id);
if (!active_catalog) {
/* If the UUID is unknown (i.e. not mapped to an actual Catalog), it is impossible to determine
* its children. The filter can still work on the given UUID. */
return AssetCatalogFilter(std::move(matching_catalog_ids));
}
/* This cannot just iterate over tree items to get all the required data, because tree items only
* represent single UUIDs. It could be used to get the main UUIDs of the children, though, and
* then only do an exact match on the path (instead of the more complex `is_contained_in()`
* call). Without an extra indexed-by-path acceleration structure, this is still going to require
* a linear search, though. */
for (const auto &catalog_uptr : this->catalogs_.values()) {
if (catalog_uptr->path.is_contained_in(active_catalog->path)) {
matching_catalog_ids.add(catalog_uptr->catalog_id);
}
}
return AssetCatalogFilter(std::move(matching_catalog_ids));
}
void AssetCatalogService::delete_catalog(CatalogID catalog_id)
{
std::unique_ptr<AssetCatalog> *catalog_uptr_ptr = this->catalogs_.lookup_ptr(catalog_id);
@@ -108,25 +134,25 @@ void AssetCatalogService::delete_catalog(CatalogID catalog_id)
}
void AssetCatalogService::update_catalog_path(CatalogID catalog_id,
const CatalogPath &new_catalog_path)
const AssetCatalogPath &new_catalog_path)
{
AssetCatalog *renamed_cat = this->find_catalog(catalog_id);
const CatalogPath old_cat_path = renamed_cat->path;
const AssetCatalogPath old_cat_path = renamed_cat->path;
for (auto &catalog_uptr : catalogs_.values()) {
AssetCatalog *cat = catalog_uptr.get();
if (!cat->is_contained_in(old_cat_path)) {
const AssetCatalogPath new_path = cat->path.rebase(old_cat_path, new_catalog_path);
if (!new_path) {
continue;
}
const CatalogPath path_suffix = cat->path.substr(old_cat_path.length());
cat->path = new_catalog_path + path_suffix;
cat->path = new_path;
}
this->rebuild_tree();
}
AssetCatalog *AssetCatalogService::create_catalog(const CatalogPath &catalog_path)
AssetCatalog *AssetCatalogService::create_catalog(const AssetCatalogPath &catalog_path)
{
std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path(catalog_path);
@@ -145,11 +171,8 @@ AssetCatalog *AssetCatalogService::create_catalog(const CatalogPath &catalog_pat
catalog_definition_file_->add_new(catalog_ptr);
}
/* The tree may not exist; this happens when no catalog definition file has been loaded yet. When
* the tree is created any in-memory catalogs will be added, so it doesn't need to happen now. */
if (catalog_tree_) {
catalog_tree_->insert_item(*catalog_ptr);
}
BLI_assert_msg(catalog_tree_, "An Asset Catalog tree should always exist.");
catalog_tree_->insert_item(*catalog_ptr);
return catalog_ptr;
}
@@ -189,7 +212,7 @@ void AssetCatalogService::load_from_disk(const CatalogFilePath &file_or_director
/* TODO: Should there be a sanitize step? E.g. to remove catalogs with identical paths? */
catalog_tree_ = read_into_tree();
rebuild_tree();
}
void AssetCatalogService::load_directory_recursive(const CatalogFilePath &directory_path)
@@ -319,8 +342,7 @@ CatalogFilePath AssetCatalogService::find_suitable_cdf_path_for_writing(
/* - There's no definition file next to the .blend file.
* -> Ask the asset library API for an appropriate location. */
char suitable_root_path[PATH_MAX];
BKE_asset_library_find_suitable_root_path_from_path(blend_file_path.c_str(),
suitable_root_path);
BKE_asset_library_find_suitable_root_path_from_path(blend_file_path.c_str(), suitable_root_path);
char asset_lib_cdf_path[PATH_MAX];
BLI_path_join(asset_lib_cdf_path,
sizeof(asset_lib_cdf_path),
@@ -358,9 +380,48 @@ std::unique_ptr<AssetCatalogTree> AssetCatalogService::read_into_tree()
void AssetCatalogService::rebuild_tree()
{
create_missing_catalogs();
this->catalog_tree_ = read_into_tree();
}
void AssetCatalogService::create_missing_catalogs()
{
/* Construct an ordered set of paths to check, so that parents are ordered before children. */
std::set<AssetCatalogPath> paths_to_check;
for (auto &catalog : catalogs_.values()) {
paths_to_check.insert(catalog->path);
}
std::set<AssetCatalogPath> seen_paths;
/* The empty parent should never be created, so always be considered "seen". */
seen_paths.insert(AssetCatalogPath(""));
/* Find and create missing direct parents (so ignoring parents-of-parents). */
while (!paths_to_check.empty()) {
/* Pop the first path of the queue. */
const AssetCatalogPath path = *paths_to_check.begin();
paths_to_check.erase(paths_to_check.begin());
if (seen_paths.find(path) != seen_paths.end()) {
/* This path has been seen already, so it can be ignored. */
continue;
}
seen_paths.insert(path);
const AssetCatalogPath parent_path = path.parent();
if (seen_paths.find(parent_path) != seen_paths.end()) {
/* The parent exists, continue to the next path. */
continue;
}
/* The parent doesn't exist, so create it and queue it up for checking its parent. */
create_catalog(parent_path);
paths_to_check.insert(parent_path);
}
/* TODO(Sybren): bind the newly created catalogs to a CDF, if we know about it. */
}
/* ---------------------------------------------------------------------- */
AssetCatalogTreeItem::AssetCatalogTreeItem(StringRef name,
@@ -380,11 +441,11 @@ StringRef AssetCatalogTreeItem::get_name() const
return name_;
}
CatalogPath AssetCatalogTreeItem::catalog_path() const
AssetCatalogPath AssetCatalogTreeItem::catalog_path() const
{
std::string current_path = name_;
AssetCatalogPath current_path = name_;
for (const AssetCatalogTreeItem *parent = parent_; parent; parent = parent->parent_) {
current_path = parent->name_ + AssetCatalogService::PATH_SEPARATOR + current_path;
current_path = AssetCatalogPath(parent->name_) / current_path;
}
return current_path;
}
@@ -405,32 +466,6 @@ bool AssetCatalogTreeItem::has_children() const
/* ---------------------------------------------------------------------- */
/**
* Iterate over path components, calling \a callback for each component. E.g. "just/some/path"
* iterates over "just", then "some" then "path".
*/
static void iterate_over_catalog_path_components(
const CatalogPath &path,
FunctionRef<void(StringRef component_name, bool is_last_component)> callback)
{
const char *next_slash_ptr;
for (const char *path_component = path.data(); path_component && path_component[0];
/* Jump to one after the next slash if there is any. */
path_component = next_slash_ptr ? next_slash_ptr + 1 : nullptr) {
next_slash_ptr = BLI_path_slash_find(path_component);
const bool is_last_component = next_slash_ptr == nullptr;
/* Note that this won't be null terminated. */
const StringRef component_name = is_last_component ?
path_component :
StringRef(path_component,
next_slash_ptr - path_component);
callback(component_name, is_last_component);
}
}
void AssetCatalogTree::insert_item(const AssetCatalog &catalog)
{
const AssetCatalogTreeItem *parent = nullptr;
@@ -438,30 +473,29 @@ void AssetCatalogTree::insert_item(const AssetCatalog &catalog)
* added to (if not there yet). */
AssetCatalogTreeItem::ChildMap *current_item_children = &root_items_;
BLI_assert_msg(!ELEM(catalog.path[0], '/', '\\'),
BLI_assert_msg(!ELEM(catalog.path.str()[0], '/', '\\'),
"Malformed catalog path; should not start with a separator");
const CatalogID nil_id{};
iterate_over_catalog_path_components(
catalog.path, [&](StringRef component_name, const bool is_last_component) {
/* Insert new tree element - if no matching one is there yet! */
auto [key_and_item, was_inserted] = current_item_children->emplace(
component_name,
AssetCatalogTreeItem(
component_name, is_last_component ? catalog.catalog_id : nil_id, parent));
AssetCatalogTreeItem &item = key_and_item->second;
catalog.path.iterate_components([&](StringRef component_name, const bool is_last_component) {
/* Insert new tree element - if no matching one is there yet! */
auto [key_and_item, was_inserted] = current_item_children->emplace(
component_name,
AssetCatalogTreeItem(
component_name, is_last_component ? catalog.catalog_id : nil_id, parent));
AssetCatalogTreeItem &item = key_and_item->second;
/* If full path of this catalog already exists as parent path of a previously read catalog,
* we can ensure this tree item's UUID is set here. */
if (is_last_component && BLI_uuid_is_nil(item.catalog_id_)) {
item.catalog_id_ = catalog.catalog_id;
}
/* If full path of this catalog already exists as parent path of a previously read catalog,
* we can ensure this tree item's UUID is set here. */
if (is_last_component && BLI_uuid_is_nil(item.catalog_id_)) {
item.catalog_id_ = catalog.catalog_id;
}
/* Walk further into the path (no matter if a new item was created or not). */
parent = &item;
current_item_children = &item.children_;
});
/* Walk further into the path (no matter if a new item was created or not). */
parent = &item;
current_item_children = &item.children_;
});
}
void AssetCatalogTree::foreach_item(AssetCatalogTreeItem::ItemIterFn callback)
@@ -592,7 +626,7 @@ std::unique_ptr<AssetCatalog> AssetCatalogDefinitionFile::parse_catalog_line(con
const StringRef path_and_simple_name = line.substr(first_delim + 1);
const int64_t second_delim = path_and_simple_name.find_first_of(delim);
CatalogPath catalog_path;
std::string path_in_file;
std::string simple_name;
if (second_delim == 0) {
/* Delimiter as first character means there is no path. These lines are to be ignored. */
@@ -601,16 +635,16 @@ std::unique_ptr<AssetCatalog> AssetCatalogDefinitionFile::parse_catalog_line(con
if (second_delim == StringRef::not_found) {
/* No delimiter means no simple name, just treat it as all "path". */
catalog_path = path_and_simple_name;
path_in_file = path_and_simple_name;
simple_name = "";
}
else {
catalog_path = path_and_simple_name.substr(0, second_delim);
path_in_file = path_and_simple_name.substr(0, second_delim);
simple_name = path_and_simple_name.substr(second_delim + 1).trim();
}
catalog_path = AssetCatalog::cleanup_path(catalog_path);
return std::make_unique<AssetCatalog>(catalog_id, catalog_path, simple_name);
AssetCatalogPath catalog_path = path_in_file;
return std::make_unique<AssetCatalog>(catalog_id, catalog_path.cleanup(), simple_name);
}
bool AssetCatalogDefinitionFile::write_to_disk() const
@@ -716,25 +750,25 @@ bool AssetCatalogDefinitionFile::ensure_directory_exists(
}
AssetCatalog::AssetCatalog(const CatalogID catalog_id,
const CatalogPath &path,
const AssetCatalogPath &path,
const std::string &simple_name)
: catalog_id(catalog_id), path(path), simple_name(simple_name)
{
}
std::unique_ptr<AssetCatalog> AssetCatalog::from_path(const CatalogPath &path)
std::unique_ptr<AssetCatalog> AssetCatalog::from_path(const AssetCatalogPath &path)
{
const CatalogPath clean_path = cleanup_path(path);
const AssetCatalogPath clean_path = path.cleanup();
const CatalogID cat_id = BLI_uuid_generate_random();
const std::string simple_name = sensible_simple_name_for_path(clean_path);
auto catalog = std::make_unique<AssetCatalog>(cat_id, clean_path, simple_name);
return catalog;
}
std::string AssetCatalog::sensible_simple_name_for_path(const CatalogPath &path)
std::string AssetCatalog::sensible_simple_name_for_path(const AssetCatalogPath &path)
{
std::string name = path;
std::replace(name.begin(), name.end(), AssetCatalogService::PATH_SEPARATOR, '-');
std::string name = path.str();
std::replace(name.begin(), name.end(), AssetCatalogPath::SEPARATOR, '-');
if (name.length() < MAX_NAME - 1) {
return name;
}
@@ -744,33 +778,14 @@ std::string AssetCatalog::sensible_simple_name_for_path(const CatalogPath &path)
return "..." + name.substr(name.length() - 60);
}
CatalogPath AssetCatalog::cleanup_path(const CatalogPath &path)
AssetCatalogFilter::AssetCatalogFilter(Set<CatalogID> &&matching_catalog_ids)
: matching_catalog_ids(std::move(matching_catalog_ids))
{
/* TODO(@sybren): maybe go over each element of the path, and trim those? */
CatalogPath clean_path = StringRef(path).trim().trim(AssetCatalogService::PATH_SEPARATOR).trim();
return clean_path;
}
bool AssetCatalog::is_contained_in(const CatalogPath &other_path) const
bool AssetCatalogFilter::contains(const CatalogID asset_catalog_id) const
{
if (other_path.empty()) {
return true;
}
if (this->path == other_path) {
return true;
}
/* To be a child path of 'other_path', our path must be at least a separator and another
* character longer. */
if (this->path.length() < other_path.length() + 2) {
return false;
}
const StringRef this_path(this->path);
const bool prefix_ok = this_path.startswith(other_path);
const char next_char = this_path[other_path.length()];
return prefix_ok && next_char == AssetCatalogService::PATH_SEPARATOR;
return matching_catalog_ids.contains(asset_catalog_id);
}
} // namespace blender::bke

View File

@@ -0,0 +1,228 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup bke
*/
#include "BKE_asset_catalog_path.hh"
#include "BLI_path_util.h"
namespace blender::bke {
const char AssetCatalogPath::SEPARATOR = '/';
AssetCatalogPath::AssetCatalogPath(const std::string &path) : path_(path)
{
}
AssetCatalogPath::AssetCatalogPath(StringRef path) : path_(path)
{
}
AssetCatalogPath::AssetCatalogPath(const char *path) : path_(path)
{
}
AssetCatalogPath::AssetCatalogPath(AssetCatalogPath &&other_path) noexcept
: path_(std::move(other_path.path_))
{
}
uint64_t AssetCatalogPath::hash() const
{
std::hash<std::string> hasher{};
return hasher(this->path_);
}
uint64_t AssetCatalogPath::length() const
{
return this->path_.length();
}
const char *AssetCatalogPath::c_str() const
{
return this->path_.c_str();
}
const std::string &AssetCatalogPath::str() const
{
return this->path_;
}
/* In-class operators, because of the implicit `AssetCatalogPath(StringRef)` constructor.
* Otherwise `string == string` could cast both sides to `AssetCatalogPath`. */
bool AssetCatalogPath::operator==(const AssetCatalogPath &other_path) const
{
return this->path_ == other_path.path_;
}
bool AssetCatalogPath::operator!=(const AssetCatalogPath &other_path) const
{
return !(*this == other_path);
}
bool AssetCatalogPath::operator<(const AssetCatalogPath &other_path) const
{
return this->path_ < other_path.path_;
}
AssetCatalogPath AssetCatalogPath::operator/(const AssetCatalogPath &path_to_append) const
{
/* `"" / "path"` or `"path" / ""` should just result in `"path"` */
if (!*this) {
return path_to_append;
}
if (!path_to_append) {
return *this;
}
std::stringstream new_path;
new_path << this->path_ << SEPARATOR << path_to_append.path_;
return AssetCatalogPath(new_path.str());
}
AssetCatalogPath::operator bool() const
{
return !this->path_.empty();
}
std::ostream &operator<<(std::ostream &stream, const AssetCatalogPath &path_to_append)
{
stream << path_to_append.path_;
return stream;
}
AssetCatalogPath AssetCatalogPath::cleanup() const
{
std::stringstream clean_components;
bool first_component_seen = false;
this->iterate_components([&clean_components, &first_component_seen](StringRef component_name,
bool /*is_last_component*/) {
const std::string clean_component = cleanup_component(component_name);
if (clean_component.empty()) {
/* These are caused by leading, trailing, or double slashes. */
return;
}
/* If a previous path component has been streamed already, we need a path separator. This
* cannot use the `is_last_component` boolean, because the last component might be skipped due
* to the condition above. */
if (first_component_seen) {
clean_components << SEPARATOR;
}
first_component_seen = true;
clean_components << clean_component;
});
return AssetCatalogPath(clean_components.str());
}
std::string AssetCatalogPath::cleanup_component(StringRef component)
{
std::string cleaned = component.trim();
/* Replace colons with something else, as those are used in the CDF file as delimiter. */
std::replace(cleaned.begin(), cleaned.end(), ':', '-');
return cleaned;
}
bool AssetCatalogPath::is_contained_in(const AssetCatalogPath &other_path) const
{
if (!other_path) {
/* The empty path contains all other paths. */
return true;
}
if (this->path_ == other_path.path_) {
/* Weak is-in relation: equal paths contain each other. */
return true;
}
/* To be a child path of 'other_path', our path must be at least a separator and another
* character longer. */
if (this->length() < other_path.length() + 2) {
return false;
}
/* Create StringRef to be able to use .startswith(). */
const StringRef this_path(this->path_);
const bool prefix_ok = this_path.startswith(other_path.path_);
const char next_char = this_path[other_path.length()];
return prefix_ok && next_char == SEPARATOR;
}
AssetCatalogPath AssetCatalogPath::parent() const
{
if (!*this) {
return AssetCatalogPath("");
}
std::string::size_type last_sep_index = this->path_.rfind(SEPARATOR);
if (last_sep_index == std::string::npos) {
return AssetCatalogPath("");
}
return AssetCatalogPath(this->path_.substr(0, last_sep_index));
}
void AssetCatalogPath::iterate_components(ComponentIteratorFn callback) const
{
const char *next_slash_ptr;
for (const char *path_component = this->path_.data(); path_component && path_component[0];
/* Jump to one after the next slash if there is any. */
path_component = next_slash_ptr ? next_slash_ptr + 1 : nullptr) {
next_slash_ptr = BLI_path_slash_find(path_component);
const bool is_last_component = next_slash_ptr == nullptr;
/* Note that this won't be null terminated. */
const StringRef component_name = is_last_component ?
path_component :
StringRef(path_component,
next_slash_ptr - path_component);
callback(component_name, is_last_component);
}
}
AssetCatalogPath AssetCatalogPath::rebase(const AssetCatalogPath &from_path,
const AssetCatalogPath &to_path) const
{
if (!from_path) {
if (!to_path) {
return AssetCatalogPath("");
}
return to_path / *this;
}
if (!this->is_contained_in(from_path)) {
return AssetCatalogPath("");
}
if (*this == from_path) {
/* Early return, because otherwise the length+1 below is going to cause problems. */
return to_path;
}
/* When from_path = "test", we need to skip "test/" to get the rest of the path, hence the +1. */
const StringRef suffix = StringRef(this->path_).substr(from_path.length() + 1);
const AssetCatalogPath path_suffix(suffix);
return to_path / path_suffix;
}
} // namespace blender::bke

View File

@@ -0,0 +1,251 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation
* All rights reserved.
*/
#include "BKE_asset_catalog_path.hh"
#include "BLI_set.hh"
#include "BLI_vector.hh"
#include <set>
#include <sstream>
#include "testing/testing.h"
namespace blender::bke::tests {
TEST(AssetCatalogPathTest, construction)
{
AssetCatalogPath from_char_literal("the/path");
const std::string str_const = "the/path";
AssetCatalogPath from_string_constant(str_const);
std::string str_variable = "the/path";
AssetCatalogPath from_string_variable(str_variable);
std::string long_string = "this is a long/string/with/a/path in the middle";
StringRef long_string_ref(long_string);
StringRef middle_bit = long_string_ref.substr(10, 23);
AssetCatalogPath from_string_ref(middle_bit);
EXPECT_EQ(from_string_ref, "long/string/with/a/path");
}
TEST(AssetCatalogPathTest, length)
{
const AssetCatalogPath one("1");
EXPECT_EQ(1, one.length());
const AssetCatalogPath empty("");
EXPECT_EQ(0, empty.length());
const AssetCatalogPath utf8("some/родитель");
EXPECT_EQ(21, utf8.length()) << "13 characters should be 21 bytes.";
}
TEST(AssetCatalogPathTest, comparison_operators)
{
const AssetCatalogPath empty("");
const AssetCatalogPath the_path("the/path");
const AssetCatalogPath the_path_child("the/path/child");
const AssetCatalogPath unrelated_path("unrelated/path");
const AssetCatalogPath other_instance_same_path("the/path");
EXPECT_LT(empty, the_path);
EXPECT_LT(the_path, the_path_child);
EXPECT_LT(the_path, unrelated_path);
EXPECT_EQ(empty, empty) << "Identical empty instances should compare equal.";
EXPECT_EQ(empty, "") << "Comparison to empty string should be possible.";
EXPECT_EQ(the_path, the_path) << "Identical non-empty instances should compare equal.";
EXPECT_EQ(the_path, "the/path") << "Comparison to string should be possible.";
EXPECT_EQ(the_path, other_instance_same_path)
<< "Different instances with equal path should compare equal.";
EXPECT_NE(the_path, the_path_child);
EXPECT_NE(the_path, unrelated_path);
EXPECT_NE(the_path, empty);
EXPECT_FALSE(empty);
EXPECT_TRUE(the_path);
}
TEST(AssetCatalogPathTest, move_semantics)
{
AssetCatalogPath source_path("source/path");
EXPECT_TRUE(source_path);
AssetCatalogPath dest_path = std::move(source_path);
EXPECT_FALSE(source_path);
EXPECT_TRUE(dest_path);
}
TEST(AssetCatalogPathTest, concatenation)
{
AssetCatalogPath some_parent("some/родитель");
AssetCatalogPath child = some_parent / "ребенок";
EXPECT_EQ(some_parent, "some/родитель")
<< "Appending a child path should not modify the parent.";
EXPECT_EQ(child, "some/родитель/ребенок");
AssetCatalogPath appended_compound_path = some_parent / "ребенок/внук";
EXPECT_EQ(appended_compound_path, "some/родитель/ребенок/внук");
AssetCatalogPath empty("");
AssetCatalogPath child_of_the_void = empty / "child";
EXPECT_EQ(child_of_the_void, "child")
<< "Appending to an empty path should not create an initial slash.";
AssetCatalogPath parent_of_the_void = some_parent / empty;
EXPECT_EQ(parent_of_the_void, "some/родитель")
<< "Prepending to an empty path should not create a trailing slash.";
std::string subpath = "child";
AssetCatalogPath concatenated_with_string = some_parent / subpath;
EXPECT_EQ(concatenated_with_string, "some/родитель/child");
}
TEST(AssetCatalogPathTest, hashable)
{
AssetCatalogPath path("heyyyyy");
std::set<AssetCatalogPath> path_std_set;
path_std_set.insert(path);
blender::Set<AssetCatalogPath> path_blender_set;
path_blender_set.add(path);
}
TEST(AssetCatalogPathTest, stream_operator)
{
AssetCatalogPath path("путь/в/Пермь");
std::stringstream sstream;
sstream << path;
EXPECT_EQ("путь/в/Пермь", sstream.str());
}
TEST(AssetCatalogPathTest, is_contained_in)
{
const AssetCatalogPath catpath("simple/path/child");
EXPECT_FALSE(catpath.is_contained_in("unrelated"));
EXPECT_FALSE(catpath.is_contained_in("sim"));
EXPECT_FALSE(catpath.is_contained_in("simple/pathx"));
EXPECT_FALSE(catpath.is_contained_in("simple/path/c"));
EXPECT_FALSE(catpath.is_contained_in("simple/path/child/grandchild"));
EXPECT_FALSE(catpath.is_contained_in("simple/path/"))
<< "Non-normalized paths are not expected to work.";
EXPECT_TRUE(catpath.is_contained_in(""));
EXPECT_TRUE(catpath.is_contained_in("simple"));
EXPECT_TRUE(catpath.is_contained_in("simple/path"));
/* Test with some UTF8 non-ASCII characters. */
AssetCatalogPath some_parent("some/родитель");
AssetCatalogPath child = some_parent / "ребенок";
EXPECT_TRUE(child.is_contained_in(some_parent));
EXPECT_TRUE(child.is_contained_in("some"));
AssetCatalogPath appended_compound_path = some_parent / "ребенок/внук";
EXPECT_TRUE(appended_compound_path.is_contained_in(some_parent));
EXPECT_TRUE(appended_compound_path.is_contained_in(child));
/* Test "going up" directory-style. */
AssetCatalogPath child_with_dotdot = some_parent / "../../other/hierarchy/part";
EXPECT_TRUE(child_with_dotdot.is_contained_in(some_parent))
<< "dotdot path components should have no meaning";
}
TEST(AssetCatalogPathTest, cleanup)
{
AssetCatalogPath ugly_path("/ some / родитель / ");
AssetCatalogPath clean_path = ugly_path.cleanup();
EXPECT_EQ(AssetCatalogPath("/ some / родитель / "), ugly_path)
<< "cleanup should not modify the path instance itself";
EXPECT_EQ(AssetCatalogPath("some/родитель"), clean_path);
AssetCatalogPath double_slashed("some//родитель");
EXPECT_EQ(AssetCatalogPath("some/родитель"), double_slashed.cleanup());
AssetCatalogPath with_colons("some/key:subkey=value/path");
EXPECT_EQ(AssetCatalogPath("some/key-subkey=value/path"), with_colons.cleanup());
}
TEST(AssetCatalogPathTest, iterate_components)
{
AssetCatalogPath path("путь/в/Пермь");
Vector<std::pair<std::string, bool>> seen_components;
path.iterate_components([&seen_components](StringRef component_name, bool is_last_component) {
std::pair<std::string, bool> parameter_pair = std::make_pair<std::string, bool>(
component_name, bool(is_last_component));
seen_components.append(parameter_pair);
});
ASSERT_EQ(3, seen_components.size());
EXPECT_EQ("путь", seen_components[0].first);
EXPECT_EQ("в", seen_components[1].first);
EXPECT_EQ("Пермь", seen_components[2].first);
EXPECT_FALSE(seen_components[0].second);
EXPECT_FALSE(seen_components[1].second);
EXPECT_TRUE(seen_components[2].second);
}
TEST(AssetCatalogPathTest, rebase)
{
AssetCatalogPath path("some/path/to/some/catalog");
EXPECT_EQ(path.rebase("some/path", "new/base"), "new/base/to/some/catalog");
EXPECT_EQ(path.rebase("", "new/base"), "new/base/some/path/to/some/catalog");
EXPECT_EQ(path.rebase("some/path/to/some/catalog", "some/path/to/some/catalog"),
"some/path/to/some/catalog")
<< "Rebasing to itself should not change the path.";
EXPECT_EQ(path.rebase("path/to", "new/base"), "")
<< "Non-matching base path should return empty string to indicate 'NO'.";
/* Empty strings should be handled without crashing or other nasty side-effects. */
AssetCatalogPath empty("");
EXPECT_EQ(empty.rebase("path/to", "new/base"), "");
EXPECT_EQ(empty.rebase("", "new/base"), "new/base");
EXPECT_EQ(empty.rebase("", ""), "");
}
TEST(AssetCatalogPathTest, parent)
{
const AssetCatalogPath ascii_path("path/with/missing/parents");
EXPECT_EQ(ascii_path.parent(), "path/with/missing");
const AssetCatalogPath path("путь/в/Пермь/долог/и/далек");
EXPECT_EQ(path.parent(), "путь/в/Пермь/долог/и");
EXPECT_EQ(path.parent().parent(), "путь/в/Пермь/долог");
EXPECT_EQ(path.parent().parent().parent(), "путь/в/Пермь");
const AssetCatalogPath one_level("one");
EXPECT_EQ(one_level.parent(), "");
const AssetCatalogPath empty("");
EXPECT_EQ(empty.parent(), "");
}
} // namespace blender::bke::tests

View File

@@ -57,6 +57,22 @@ class TestableAssetCatalogService : public AssetCatalogService {
{
return catalog_definition_file_.get();
}
void create_missing_catalogs()
{
AssetCatalogService::create_missing_catalogs();
}
int64_t count_catalogs_with_path(const CatalogFilePath &path)
{
int64_t count = 0;
for (auto &catalog_uptr : catalogs_.values()) {
if (catalog_uptr->path == path) {
count++;
}
}
return count;
}
};
class AssetCatalogTest : public testing::Test {
@@ -106,7 +122,7 @@ class AssetCatalogTest : public testing::Test {
EXPECT_EQ(expected_filename, actual_item.get_name());
/* Does the computed number of parents match? */
EXPECT_EQ(expected_path.parent_count, actual_item.count_parents());
EXPECT_EQ(expected_path.name, actual_item.catalog_path());
EXPECT_EQ(expected_path.name, actual_item.catalog_path().str());
}
/**
@@ -186,21 +202,21 @@ TEST_F(AssetCatalogTest, load_single_file)
AssetCatalog *poses_ellie = service.find_catalog(UUID_POSES_ELLIE);
ASSERT_NE(nullptr, poses_ellie);
EXPECT_EQ(UUID_POSES_ELLIE, poses_ellie->catalog_id);
EXPECT_EQ("character/Ellie/poselib", poses_ellie->path);
EXPECT_EQ("character/Ellie/poselib", poses_ellie->path.str());
EXPECT_EQ("POSES_ELLIE", poses_ellie->simple_name);
/* Test white-space stripping and support in the path. */
AssetCatalog *poses_whitespace = service.find_catalog(UUID_POSES_ELLIE_WHITESPACE);
ASSERT_NE(nullptr, poses_whitespace);
EXPECT_EQ(UUID_POSES_ELLIE_WHITESPACE, poses_whitespace->catalog_id);
EXPECT_EQ("character/Ellie/poselib/white space", poses_whitespace->path);
EXPECT_EQ("character/Ellie/poselib/white space", poses_whitespace->path.str());
EXPECT_EQ("POSES_ELLIE WHITESPACE", poses_whitespace->simple_name);
/* Test getting a UTF-8 catalog ID. */
AssetCatalog *poses_ruzena = service.find_catalog(UUID_POSES_RUZENA);
ASSERT_NE(nullptr, poses_ruzena);
EXPECT_EQ(UUID_POSES_RUZENA, poses_ruzena->catalog_id);
EXPECT_EQ("character/Ružena/poselib", poses_ruzena->path);
EXPECT_EQ("character/Ružena/poselib", poses_ruzena->path.str());
EXPECT_EQ("POSES_RUŽENA", poses_ruzena->simple_name);
}
@@ -429,7 +445,7 @@ TEST_F(AssetCatalogTest, on_blendfile_save__with_existing_cdf)
const AssetCatalog *cat = service.create_catalog("some/catalog/path");
const CatalogFilePath blendfilename = top_level_dir + "subdir/some_file.blend";
ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename.c_str()));
ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename));
EXPECT_EQ(cdf_filename, service.get_catalog_definition_file()->file_path);
/* Test that the CDF was created in the expected location. */
@@ -456,7 +472,7 @@ TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_empty_directory)
const AssetCatalog *cat = service.create_catalog("some/catalog/path");
const CatalogFilePath blendfilename = target_dir + "some_file.blend";
ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename.c_str()));
ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename));
/* Test that the CDF was created in the expected location. */
const CatalogFilePath expected_cdf_path = target_dir +
@@ -489,7 +505,7 @@ TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_existing_cdf_and_me
/* Mock that the blend file is written to a subdirectory of the asset library. */
const CatalogFilePath blendfilename = target_dir + "some_file.blend";
ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename.c_str()));
ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename));
/* Test that the CDF still exists in the expected location. */
const CatalogFilePath backup_filename = writable_cdf_file + "~";
@@ -534,7 +550,7 @@ TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_existing_asset_lib)
const AssetCatalog *cat = service.create_catalog("some/catalog/path");
/* Mock that the blend file is written to the directory already containing a CDF. */
ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename.c_str()));
ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename));
/* Test that the CDF still exists in the expected location. */
EXPECT_TRUE(BLI_exists(writable_cdf_file.c_str()));
@@ -588,7 +604,7 @@ TEST_F(AssetCatalogTest, create_first_catalog_from_scratch)
AssetCatalog *written_cat = loaded_service.find_catalog(cat->catalog_id);
ASSERT_NE(nullptr, written_cat);
EXPECT_EQ(written_cat->catalog_id, cat->catalog_id);
EXPECT_EQ(written_cat->path, cat->path);
EXPECT_EQ(written_cat->path, cat->path.str());
}
TEST_F(AssetCatalogTest, create_catalog_after_loading_file)
@@ -640,7 +656,7 @@ TEST_F(AssetCatalogTest, create_catalog_path_cleanup)
AssetCatalog *cat = service.create_catalog(" /some/path / ");
EXPECT_FALSE(BLI_uuid_is_nil(cat->catalog_id));
EXPECT_EQ("some/path", cat->path);
EXPECT_EQ("some/path", cat->path.str());
EXPECT_EQ("some-path", cat->simple_name);
}
@@ -652,7 +668,7 @@ TEST_F(AssetCatalogTest, create_catalog_simple_name)
EXPECT_FALSE(BLI_uuid_is_nil(cat->catalog_id));
EXPECT_EQ("production/Spite Fright/Characters/Victora/Pose Library/Approved/Body Parts/Hands",
cat->path);
cat->path.str());
EXPECT_EQ("...ht-Characters-Victora-Pose Library-Approved-Body Parts-Hands", cat->simple_name);
}
@@ -718,7 +734,7 @@ TEST_F(AssetCatalogTest, update_catalog_path)
AssetCatalogService::DEFAULT_CATALOG_FILENAME);
const AssetCatalog *orig_cat = service.find_catalog(UUID_POSES_RUZENA);
const CatalogPath orig_path = orig_cat->path;
const AssetCatalogPath orig_path = orig_cat->path;
service.update_catalog_path(UUID_POSES_RUZENA, "charlib/Ružena");
@@ -733,12 +749,12 @@ TEST_F(AssetCatalogTest, update_catalog_path)
EXPECT_EQ(orig_cat->catalog_id, renamed_cat->catalog_id)
<< "Changing the path should not change the catalog ID.";
EXPECT_EQ("charlib/Ružena", renamed_cat->path)
EXPECT_EQ("charlib/Ružena", renamed_cat->path.str())
<< "Changing the path should change the path. Surprise.";
EXPECT_EQ("charlib/Ružena/hand", service.find_catalog(UUID_POSES_RUZENA_HAND)->path)
EXPECT_EQ("charlib/Ružena/hand", service.find_catalog(UUID_POSES_RUZENA_HAND)->path.str())
<< "Changing the path should update children.";
EXPECT_EQ("charlib/Ružena/face", service.find_catalog(UUID_POSES_RUZENA_FACE)->path)
EXPECT_EQ("charlib/Ružena/face", service.find_catalog(UUID_POSES_RUZENA_FACE)->path.str())
<< "Changing the path should update children.";
}
@@ -775,7 +791,7 @@ TEST_F(AssetCatalogTest, merge_catalog_files)
/* When there are overlaps, the in-memory (i.e. last-saved) paths should win. */
const AssetCatalog *ruzena_face = loaded_service.find_catalog(UUID_POSES_RUZENA_FACE);
EXPECT_EQ("character/Ružena/poselib/face", ruzena_face->path);
EXPECT_EQ("character/Ružena/poselib/face", ruzena_face->path.str());
}
TEST_F(AssetCatalogTest, backups)
@@ -846,21 +862,104 @@ TEST_F(AssetCatalogTest, order_by_path)
}
}
TEST_F(AssetCatalogTest, is_contained_in)
TEST_F(AssetCatalogTest, create_missing_catalogs)
{
const AssetCatalog cat(BLI_uuid_generate_random(), "simple/path/child", "");
TestableAssetCatalogService new_service;
new_service.create_catalog("path/with/missing/parents");
EXPECT_FALSE(cat.is_contained_in("unrelated"));
EXPECT_FALSE(cat.is_contained_in("sim"));
EXPECT_FALSE(cat.is_contained_in("simple/pathx"));
EXPECT_FALSE(cat.is_contained_in("simple/path/c"));
EXPECT_FALSE(cat.is_contained_in("simple/path/child/grandchild"));
EXPECT_FALSE(cat.is_contained_in("simple/path/"))
<< "Non-normalized paths are not expected to work.";
EXPECT_EQ(nullptr, new_service.find_catalog_by_path("path/with/missing"))
<< "Missing parents should not be immediately created.";
EXPECT_EQ(nullptr, new_service.find_catalog_by_path("")) << "Empty path should never be valid";
EXPECT_TRUE(cat.is_contained_in(""));
EXPECT_TRUE(cat.is_contained_in("simple"));
EXPECT_TRUE(cat.is_contained_in("simple/path"));
new_service.create_missing_catalogs();
EXPECT_NE(nullptr, new_service.find_catalog_by_path("path/with/missing"));
EXPECT_NE(nullptr, new_service.find_catalog_by_path("path/with"));
EXPECT_NE(nullptr, new_service.find_catalog_by_path("path"));
EXPECT_EQ(nullptr, new_service.find_catalog_by_path(""))
<< "Empty path should never be valid, even when after missing catalogs";
}
TEST_F(AssetCatalogTest, create_missing_catalogs_after_loading)
{
TestableAssetCatalogService loaded_service(asset_library_root_);
loaded_service.load_from_disk();
const AssetCatalog *cat_char = loaded_service.find_catalog_by_path("character");
const AssetCatalog *cat_ellie = loaded_service.find_catalog_by_path("character/Ellie");
const AssetCatalog *cat_ruzena = loaded_service.find_catalog_by_path("character/Ružena");
ASSERT_NE(nullptr, cat_char) << "Missing parents should be created immediately after loading.";
ASSERT_NE(nullptr, cat_ellie) << "Missing parents should be created immediately after loading.";
ASSERT_NE(nullptr, cat_ruzena) << "Missing parents should be created immediately after loading.";
AssetCatalogDefinitionFile *cdf = loaded_service.get_catalog_definition_file();
ASSERT_NE(nullptr, cdf);
EXPECT_TRUE(cdf->contains(cat_char->catalog_id)) << "Missing parents should be saved to a CDF.";
EXPECT_TRUE(cdf->contains(cat_ellie->catalog_id)) << "Missing parents should be saved to a CDF.";
EXPECT_TRUE(cdf->contains(cat_ruzena->catalog_id))
<< "Missing parents should be saved to a CDF.";
/* Check that each missing parent is only created once. The CDF contains multiple paths that
* could trigger the creation of missing parents, so this test makes sense. */
EXPECT_EQ(1, loaded_service.count_catalogs_with_path("character"));
EXPECT_EQ(1, loaded_service.count_catalogs_with_path("character/Ellie"));
EXPECT_EQ(1, loaded_service.count_catalogs_with_path("character/Ružena"));
}
TEST_F(AssetCatalogTest, create_catalog_filter)
{
AssetCatalogService service(asset_library_root_);
service.load_from_disk();
/* Alias for the same catalog as the main one. */
AssetCatalog *alias_ruzena = service.create_catalog("character/Ružena/poselib");
/* Alias for a sub-catalog. */
AssetCatalog *alias_ruzena_hand = service.create_catalog("character/Ružena/poselib/hand");
AssetCatalogFilter filter = service.create_catalog_filter(UUID_POSES_RUZENA);
/* Positive test for loaded-from-disk catalogs. */
EXPECT_TRUE(filter.contains(UUID_POSES_RUZENA))
<< "Main catalog should be included in the filter.";
EXPECT_TRUE(filter.contains(UUID_POSES_RUZENA_HAND))
<< "Sub-catalog should be included in the filter.";
EXPECT_TRUE(filter.contains(UUID_POSES_RUZENA_FACE))
<< "Sub-catalog should be included in the filter.";
/* Positive test for newly-created catalogs. */
EXPECT_TRUE(filter.contains(alias_ruzena->catalog_id))
<< "Alias of main catalog should be included in the filter.";
EXPECT_TRUE(filter.contains(alias_ruzena_hand->catalog_id))
<< "Alias of sub-catalog should be included in the filter.";
/* Negative test for unrelated catalogs. */
EXPECT_FALSE(filter.contains(BLI_uuid_nil())) << "Nil catalog should not be included.";
EXPECT_FALSE(filter.contains(UUID_ID_WITHOUT_PATH));
EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE));
EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE_WHITESPACE));
EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE_TRAILING_SLASH));
EXPECT_FALSE(filter.contains(UUID_WITHOUT_SIMPLENAME));
}
TEST_F(AssetCatalogTest, create_catalog_filter_for_unknown_uuid)
{
AssetCatalogService service;
const bUUID unknown_uuid = BLI_uuid_generate_random();
AssetCatalogFilter filter = service.create_catalog_filter(unknown_uuid);
EXPECT_TRUE(filter.contains(unknown_uuid));
EXPECT_FALSE(filter.contains(BLI_uuid_nil())) << "Nil catalog should not be included.";
EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE));
}
TEST_F(AssetCatalogTest, create_catalog_filter_for_unassigned_assets)
{
AssetCatalogService service;
AssetCatalogFilter filter = service.create_catalog_filter(BLI_uuid_nil());
EXPECT_TRUE(filter.contains(BLI_uuid_nil()));
EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE));
}
} // namespace blender::bke::tests

View File

@@ -49,7 +49,7 @@ TEST(AssetLibraryTest, load_and_free_c_functions)
const bUUID uuid_poses_ellie("df60e1f6-2259-475b-93d9-69a1b4a8db78");
AssetCatalog *poses_ellie = service->find_catalog(uuid_poses_ellie);
ASSERT_NE(nullptr, poses_ellie) << "unable to find POSES_ELLIE catalog";
EXPECT_EQ("character/Ellie/poselib", poses_ellie->path);
EXPECT_EQ("character/Ellie/poselib", poses_ellie->path.str());
BKE_asset_library_free(library_c_ptr);
}

View File

@@ -597,7 +597,7 @@ static Collection *collection_duplicate_recursive(Main *bmain,
}
else if (collection_old->id.newid == NULL) {
collection_new = (Collection *)BKE_id_copy_for_duplicate(
bmain, (ID *)collection_old, duplicate_flags);
bmain, (ID *)collection_old, duplicate_flags, LIB_ID_COPY_DEFAULT);
if (collection_new == collection_old) {
return collection_new;

View File

@@ -1212,6 +1212,20 @@ void BKE_curvemapping_init(CurveMapping *cumap)
}
}
void BKE_curvemapping_table_F(const CurveMapping *cumap, float **array, int *size)
{
int a;
*size = CM_TABLE + 1;
*array = MEM_callocN(sizeof(float) * (*size) * 4, "CurveMapping");
for (a = 0; a < *size; a++) {
if (cumap->cm[0].table) {
(*array)[a * 4 + 0] = cumap->cm[0].table[a].y;
}
}
}
void BKE_curvemapping_table_RGBA(const CurveMapping *cumap, float **array, int *size)
{
int a;

View File

@@ -1904,7 +1904,7 @@ KeyBlock *BKE_keyblock_add_ctime(Key *key, const char *name, const bool do_force
return kb;
}
/* only the active keyblock */
/* Only the active key-block. */
KeyBlock *BKE_keyblock_from_object(Object *ob)
{
Key *key = BKE_key_from_object(ob);
@@ -2247,7 +2247,7 @@ void BKE_keyblock_convert_to_mesh(KeyBlock *kb, Mesh *me)
* Computes normals (vertices, polygons and/or loops ones) of given mesh for given shape key.
*
* \param kb: the KeyBlock to use to compute normals.
* \param mesh: the Mesh to apply keyblock to.
* \param mesh: the Mesh to apply key-block to.
* \param r_vertnors: if non-NULL, an array of vectors, same length as number of vertices.
* \param r_polynors: if non-NULL, an array of vectors, same length as number of polygons.
* \param r_loopnors: if non-NULL, an array of vectors, same length as number of loops.
@@ -2345,7 +2345,7 @@ void BKE_keyblock_update_from_vertcos(Object *ob, KeyBlock *kb, const float (*ve
return;
}
/* Copy coords to keyblock */
/* Copy coords to key-block. */
if (ELEM(ob->type, OB_MESH, OB_LATTICE)) {
for (a = 0; a < tot; a++, fp += 3, co++) {
copy_v3_v3(fp, *co);
@@ -2405,7 +2405,7 @@ void BKE_keyblock_convert_from_vertcos(Object *ob, KeyBlock *kb, const float (*v
kb->data = MEM_mallocN(tot * elemsize, __func__);
/* Copy coords to keyblock */
/* Copy coords to key-block. */
BKE_keyblock_update_from_vertcos(ob, kb, vertCos);
}
@@ -2594,7 +2594,7 @@ bool BKE_keyblock_move(Object *ob, int org_index, int new_index)
}
/**
* Check if given keyblock (as index) is used as basis by others in given key.
* Check if given key-block (as index) is used as basis by others in given key.
*/
bool BKE_keyblock_is_basis(Key *key, const int index)
{

View File

@@ -674,7 +674,10 @@ ID *BKE_id_copy(Main *bmain, const ID *id)
* Invokes the appropriate copy method for the block and returns the result in
* newid, unless test. Returns true if the block can be copied.
*/
ID *BKE_id_copy_for_duplicate(Main *bmain, ID *id, const eDupli_ID_Flags duplicate_flags)
ID *BKE_id_copy_for_duplicate(Main *bmain,
ID *id,
const eDupli_ID_Flags duplicate_flags,
const int copy_flags)
{
if (id == NULL) {
return id;
@@ -685,7 +688,7 @@ ID *BKE_id_copy_for_duplicate(Main *bmain, ID *id, const eDupli_ID_Flags duplica
return id;
}
ID *id_new = BKE_id_copy(bmain, id);
ID *id_new = BKE_id_copy_ex(bmain, id, NULL, copy_flags);
/* Copying add one user by default, need to get rid of that one. */
id_us_min(id_new);
ID_NEW_SET(id, id_new);

View File

@@ -538,7 +538,7 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree)
if (node->storage) {
/* could be handlerized at some point, now only 1 exception still */
if (ELEM(ntree->type, NTREE_SHADER, NTREE_GEOMETRY) &&
ELEM(node->type, SH_NODE_CURVE_VEC, SH_NODE_CURVE_RGB)) {
ELEM(node->type, SH_NODE_CURVE_VEC, SH_NODE_CURVE_RGB, SH_NODE_CURVE_FLOAT)) {
BKE_curvemapping_blend_write(writer, (const CurveMapping *)node->storage);
}
else if ((ntree->type == NTREE_GEOMETRY) &&
@@ -714,6 +714,7 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree)
switch (node->type) {
case SH_NODE_CURVE_VEC:
case SH_NODE_CURVE_RGB:
case SH_NODE_CURVE_FLOAT:
case CMP_NODE_TIME:
case CMP_NODE_CURVE_VEC:
case CMP_NODE_CURVE_RGB:
@@ -5574,6 +5575,7 @@ static void registerShaderNodes()
register_node_type_sh_shadertorgb();
register_node_type_sh_normal();
register_node_type_sh_mapping();
register_node_type_sh_curve_float();
register_node_type_sh_curve_vec();
register_node_type_sh_curve_rgb();
register_node_type_sh_map_range();
@@ -5707,11 +5709,14 @@ static void registerGeometryNodes()
{
register_node_type_geo_group();
register_node_type_geo_legacy_curve_set_handles();
register_node_type_geo_legacy_attribute_proximity();
register_node_type_geo_legacy_attribute_randomize();
register_node_type_geo_legacy_material_assign();
register_node_type_geo_legacy_select_by_material();
register_node_type_geo_legacy_curve_spline_type();
register_node_type_geo_legacy_curve_reverse();
register_node_type_geo_legacy_curve_subdivide();
register_node_type_geo_align_rotation_to_vector();
register_node_type_geo_attribute_capture();
@@ -5764,6 +5769,7 @@ static void registerGeometryNodes()
register_node_type_geo_input_normal();
register_node_type_geo_input_position();
register_node_type_geo_input_tangent();
register_node_type_geo_input_spline_length();
register_node_type_geo_instance_on_points();
register_node_type_geo_is_viewport();
register_node_type_geo_join_geometry();
@@ -5818,6 +5824,7 @@ static void registerFunctionNodes()
register_node_type_fn_input_string();
register_node_type_fn_input_vector();
register_node_type_fn_random_value();
register_node_type_fn_rotate_euler();
register_node_type_fn_string_length();
register_node_type_fn_string_substring();
register_node_type_fn_value_to_string();

View File

@@ -2634,10 +2634,16 @@ Object *BKE_object_duplicate(Main *bmain,
{
const bool is_subprocess = (duplicate_options & LIB_ID_DUPLICATE_IS_SUBPROCESS) != 0;
const bool is_root_id = (duplicate_options & LIB_ID_DUPLICATE_IS_ROOT_ID) != 0;
int copy_flags = LIB_ID_COPY_DEFAULT;
if (!is_subprocess) {
BKE_main_id_newptr_and_tag_clear(bmain);
}
else {
/* In case copying object is a sub-process of collection (or scene) copying, do not try to
* re-assign RB objects to existing RBW collections. */
copy_flags |= LIB_ID_COPY_RIGID_BODY_NO_COLLECTION_HANDLING;
}
if (is_root_id) {
/* In case root duplicated ID is linked, assume we want to get a local copy of it and duplicate
* all expected linked data. */
@@ -2649,7 +2655,7 @@ Object *BKE_object_duplicate(Main *bmain,
Material ***matarar;
Object *obn = (Object *)BKE_id_copy_for_duplicate(bmain, &ob->id, dupflag);
Object *obn = (Object *)BKE_id_copy_for_duplicate(bmain, &ob->id, dupflag, copy_flags);
/* 0 == full linked. */
if (dupflag == 0) {
@@ -2658,13 +2664,13 @@ Object *BKE_object_duplicate(Main *bmain,
if (dupflag & USER_DUP_MAT) {
for (int i = 0; i < obn->totcol; i++) {
BKE_id_copy_for_duplicate(bmain, (ID *)obn->mat[i], dupflag);
BKE_id_copy_for_duplicate(bmain, (ID *)obn->mat[i], dupflag, copy_flags);
}
}
if (dupflag & USER_DUP_PSYS) {
ParticleSystem *psys;
for (psys = obn->particlesystem.first; psys; psys = psys->next) {
BKE_id_copy_for_duplicate(bmain, (ID *)psys->part, dupflag);
BKE_id_copy_for_duplicate(bmain, (ID *)psys->part, dupflag, copy_flags);
}
}
@@ -2675,77 +2681,77 @@ Object *BKE_object_duplicate(Main *bmain,
switch (obn->type) {
case OB_MESH:
if (dupflag & USER_DUP_MESH) {
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_CURVE:
if (dupflag & USER_DUP_CURVE) {
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_SURF:
if (dupflag & USER_DUP_SURF) {
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_FONT:
if (dupflag & USER_DUP_FONT) {
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_MBALL:
if (dupflag & USER_DUP_MBALL) {
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_LAMP:
if (dupflag & USER_DUP_LAMP) {
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_ARMATURE:
if (dupflag & USER_DUP_ARM) {
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_LATTICE:
if (dupflag != 0) {
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_CAMERA:
if (dupflag != 0) {
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_LIGHTPROBE:
if (dupflag & USER_DUP_LIGHTPROBE) {
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_SPEAKER:
if (dupflag != 0) {
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_GPENCIL:
if (dupflag & USER_DUP_GPENCIL) {
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_HAIR:
if (dupflag & USER_DUP_HAIR) {
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_POINTCLOUD:
if (dupflag & USER_DUP_POINTCLOUD) {
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
case OB_VOLUME:
if (dupflag & USER_DUP_VOLUME) {
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag);
id_new = BKE_id_copy_for_duplicate(bmain, id_old, dupflag, copy_flags);
}
break;
}
@@ -2756,7 +2762,7 @@ Object *BKE_object_duplicate(Main *bmain,
matarar = BKE_object_material_array_p(obn);
if (matarar) {
for (int i = 0; i < obn->totcol; i++) {
BKE_id_copy_for_duplicate(bmain, (ID *)(*matarar)[i], dupflag);
BKE_id_copy_for_duplicate(bmain, (ID *)(*matarar)[i], dupflag, copy_flags);
}
}
}

View File

@@ -4619,11 +4619,11 @@ void psys_get_particle_on_path(ParticleSimulationData *sim,
pind.cache = cached ? psys->pointcache : NULL;
pind.epoint = NULL;
pind.bspline = (psys->part->flag & PART_HAIR_BSPLINE);
/* pind.dm disabled in editmode means we don't get effectors taken into
* account when subdividing for instance */
/* `pind.dm` disabled in edit-mode means we don't get effectors taken into
* account when subdividing for instance. */
pind.mesh = psys_in_edit_mode(sim->depsgraph, psys) ?
NULL :
psys->hair_out_mesh; /* XXX Sybren EEK */
psys->hair_out_mesh; /* XXX(@sybren) EEK. */
init_particle_interpolation(sim->ob, psys, pa, &pind);
do_particle_interpolation(psys, p, pa, t, &pind, state);

View File

@@ -302,7 +302,7 @@ void BKE_rigidbody_object_copy(Main *bmain, Object *ob_dst, const Object *ob_src
ob_dst->rigidbody_object = rigidbody_copy_object(ob_src, flag);
ob_dst->rigidbody_constraint = rigidbody_copy_constraint(ob_src, flag);
if (flag & LIB_ID_CREATE_NO_MAIN) {
if ((flag & (LIB_ID_CREATE_NO_MAIN | LIB_ID_COPY_RIGID_BODY_NO_COLLECTION_HANDLING)) != 0) {
return;
}
@@ -1211,8 +1211,8 @@ RigidBodyWorld *BKE_rigidbody_world_copy(RigidBodyWorld *rbw, const int flag)
id_us_plus((ID *)rbw_copy->constraints);
}
if ((flag & LIB_ID_CREATE_NO_MAIN) == 0) {
/* This is a regular copy, and not a CoW copy for depsgraph evaluation */
if ((flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) == 0) {
/* This is a regular copy, and not a CoW copy for depsgraph evaluation. */
rbw_copy->shared = MEM_callocN(sizeof(*rbw_copy->shared), "RigidBodyWorld_Shared");
BKE_ptcache_copy_list(&rbw_copy->shared->ptcaches, &rbw->shared->ptcaches, LIB_ID_COPY_CACHES);
rbw_copy->shared->pointcache = rbw_copy->shared->ptcaches.first;

View File

@@ -1801,6 +1801,7 @@ Scene *BKE_scene_duplicate(Main *bmain, Scene *sce, eSceneCopyMethod type)
/* Scene duplication is always root of duplication currently. */
const bool is_subprocess = false;
const bool is_root_id = true;
const int copy_flags = LIB_ID_COPY_DEFAULT;
if (!is_subprocess) {
BKE_main_id_newptr_and_tag_clear(bmain);
@@ -1816,21 +1817,40 @@ Scene *BKE_scene_duplicate(Main *bmain, Scene *sce, eSceneCopyMethod type)
/* Copy Freestyle LineStyle datablocks. */
LISTBASE_FOREACH (ViewLayer *, view_layer_dst, &sce_copy->view_layers) {
LISTBASE_FOREACH (FreestyleLineSet *, lineset, &view_layer_dst->freestyle_config.linesets) {
BKE_id_copy_for_duplicate(bmain, (ID *)lineset->linestyle, duplicate_flags);
BKE_id_copy_for_duplicate(bmain, (ID *)lineset->linestyle, duplicate_flags, copy_flags);
}
}
/* Full copy of world (included animations) */
BKE_id_copy_for_duplicate(bmain, (ID *)sce->world, duplicate_flags);
BKE_id_copy_for_duplicate(bmain, (ID *)sce->world, duplicate_flags, copy_flags);
/* Full copy of GreasePencil. */
BKE_id_copy_for_duplicate(bmain, (ID *)sce->gpd, duplicate_flags);
BKE_id_copy_for_duplicate(bmain, (ID *)sce->gpd, duplicate_flags, copy_flags);
/* Deep-duplicate collections and objects (using preferences' settings for which sub-data to
* duplicate along the object itself). */
BKE_collection_duplicate(
bmain, NULL, sce_copy->master_collection, duplicate_flags, LIB_ID_DUPLICATE_IS_SUBPROCESS);
/* Rigid body world collections may not be instantiated as scene's collections, ensure they
* also get properly duplicated. */
if (sce_copy->rigidbody_world != NULL) {
if (sce_copy->rigidbody_world->group != NULL) {
BKE_collection_duplicate(bmain,
NULL,
sce_copy->rigidbody_world->group,
duplicate_flags,
LIB_ID_DUPLICATE_IS_SUBPROCESS);
}
if (sce_copy->rigidbody_world->constraints != NULL) {
BKE_collection_duplicate(bmain,
NULL,
sce_copy->rigidbody_world->constraints,
duplicate_flags,
LIB_ID_DUPLICATE_IS_SUBPROCESS);
}
}
if (!is_subprocess) {
/* This code will follow into all ID links using an ID tagged with LIB_TAG_NEW. */
BKE_libblock_relink_to_newid(&sce_copy->id);

View File

@@ -2295,7 +2295,7 @@ static void softbody_calc_forces(
sb_sfesf_threads_run(depsgraph, scene, ob, timenow, sb->totspring, NULL);
}
/* after spring scan because it uses Effoctors too */
/* After spring scan because it uses effectors too. */
ListBase *effectors = BKE_effectors_create(depsgraph, ob, NULL, sb->effector_weights, false);
if (do_deflector) {

View File

@@ -3014,6 +3014,61 @@ static int channels_average_error_sort(const void *a, const void *b)
return 0;
}
static int compare_firstlast_putting_undefined_first(
bool inverse, bool a_markerless, int a_value, bool b_markerless, int b_value)
{
if (a_markerless && b_markerless) {
/* Neither channel has not-disabled markers, return whatever. */
return 0;
}
if (a_markerless) {
/* Put the markerless channel first. */
return 0;
}
if (b_markerless) {
/* Put the markerless channel first. */
return 1;
}
/* Both channels have markers. */
if (inverse) {
if (a_value < b_value) {
return 1;
}
return 0;
}
if (a_value > b_value) {
return 1;
}
return 0;
}
static int channels_start_sort(const void *a, const void *b)
{
const MovieTrackingDopesheetChannel *channel_a = a;
const MovieTrackingDopesheetChannel *channel_b = b;
return compare_firstlast_putting_undefined_first(false,
channel_a->tot_segment == 0,
channel_a->first_not_disabled_marker_framenr,
channel_b->tot_segment == 0,
channel_b->first_not_disabled_marker_framenr);
}
static int channels_end_sort(const void *a, const void *b)
{
const MovieTrackingDopesheetChannel *channel_a = a;
const MovieTrackingDopesheetChannel *channel_b = b;
return compare_firstlast_putting_undefined_first(false,
channel_a->tot_segment == 0,
channel_a->last_not_disabled_marker_framenr,
channel_b->tot_segment == 0,
channel_b->last_not_disabled_marker_framenr);
}
static int channels_alpha_inverse_sort(const void *a, const void *b)
{
if (channels_alpha_sort(a, b)) {
@@ -3053,22 +3108,51 @@ static int channels_average_error_inverse_sort(const void *a, const void *b)
return 0;
}
static int channels_start_inverse_sort(const void *a, const void *b)
{
const MovieTrackingDopesheetChannel *channel_a = a;
const MovieTrackingDopesheetChannel *channel_b = b;
return compare_firstlast_putting_undefined_first(true,
channel_a->tot_segment == 0,
channel_a->first_not_disabled_marker_framenr,
channel_b->tot_segment == 0,
channel_b->first_not_disabled_marker_framenr);
}
static int channels_end_inverse_sort(const void *a, const void *b)
{
const MovieTrackingDopesheetChannel *channel_a = a;
const MovieTrackingDopesheetChannel *channel_b = b;
return compare_firstlast_putting_undefined_first(true,
channel_a->tot_segment == 0,
channel_a->last_not_disabled_marker_framenr,
channel_b->tot_segment == 0,
channel_b->last_not_disabled_marker_framenr);
}
/* Calculate frames segments at which track is tracked continuously. */
static void tracking_dopesheet_channels_segments_calc(MovieTrackingDopesheetChannel *channel)
{
MovieTrackingTrack *track = channel->track;
int i, segment;
bool first_not_disabled_marker_framenr_set;
channel->tot_segment = 0;
channel->max_segment = 0;
channel->total_frames = 0;
channel->first_not_disabled_marker_framenr = 0;
channel->last_not_disabled_marker_framenr = 0;
/* TODO(sergey): looks a bit code-duplicated, need to look into
* logic de-duplication here.
*/
/* count */
i = 0;
first_not_disabled_marker_framenr_set = false;
while (i < track->markersnr) {
MovieTrackingMarker *marker = &track->markers[i];
@@ -3086,6 +3170,12 @@ static void tracking_dopesheet_channels_segments_calc(MovieTrackingDopesheetChan
break;
}
if (!first_not_disabled_marker_framenr_set) {
channel->first_not_disabled_marker_framenr = marker->framenr;
first_not_disabled_marker_framenr_set = true;
}
channel->last_not_disabled_marker_framenr = marker->framenr;
prev_fra = marker->framenr;
len++;
i++;
@@ -3203,6 +3293,12 @@ static void tracking_dopesheet_channels_sort(MovieTracking *tracking,
else if (sort_method == TRACKING_DOPE_SORT_AVERAGE_ERROR) {
BLI_listbase_sort(&dopesheet->channels, channels_average_error_inverse_sort);
}
else if (sort_method == TRACKING_DOPE_SORT_START) {
BLI_listbase_sort(&dopesheet->channels, channels_start_inverse_sort);
}
else if (sort_method == TRACKING_DOPE_SORT_END) {
BLI_listbase_sort(&dopesheet->channels, channels_end_inverse_sort);
}
}
else {
if (sort_method == TRACKING_DOPE_SORT_NAME) {
@@ -3217,6 +3313,12 @@ static void tracking_dopesheet_channels_sort(MovieTracking *tracking,
else if (sort_method == TRACKING_DOPE_SORT_AVERAGE_ERROR) {
BLI_listbase_sort(&dopesheet->channels, channels_average_error_sort);
}
else if (sort_method == TRACKING_DOPE_SORT_START) {
BLI_listbase_sort(&dopesheet->channels, channels_start_sort);
}
else if (sort_method == TRACKING_DOPE_SORT_END) {
BLI_listbase_sort(&dopesheet->channels, channels_end_sort);
}
}
}

Some files were not shown because too many files have changed in this diff Show More