Compare commits

..

415 Commits

Author SHA1 Message Date
c81d830ba0 Merge branch 'master' into temp-image-engine 2022-12-07 15:04:39 +01:00
3255ddc2f8 Rename some methods to represent their internal flag. 2022-12-07 15:03:36 +01:00
8db9d6bd6b Add back UDIM support. 2022-12-07 15:01:17 +01:00
2dc51fccb8 Fix T101787, T102786. Cycles: Improved out-of-memory messaging on Metal
This patch adds a new `max_working_set_exceeded()` check on Metal so that we can display a "System is out of GPU memory" message to the user. Without this, we get obtuse "CommandBuffer failed" errors at render time due to exceeding the size limit of resident resources.

Likely fix for T101787 & T102786.

Reviewed By: brecht

Differential Revision: https://developer.blender.org/D16713
2022-12-07 13:56:21 +00:00
4d05a000cb Fix light tree header file included while feature disabled 2022-12-07 14:47:11 +01:00
f8ddf16e5a Cleanup: Typo in comment 2022-12-07 14:44:45 +01:00
6df6e537a3 Recalculate bounds when textures go offscreen. 2022-12-07 14:28:39 +01:00
cc1ba74ce2 Fix T102966: Curves editmode selection drawing not stable / flickering
Issue was the lifetime of GPUVertFormat & GPUVertAttr.
Both need to be static in the function to be persistent here (and
handled appropriately).

Was an error in rB319ee296fd0c.

Maniphest Tasks: T102966

Differential Revision: https://developer.blender.org/D16704
2022-12-07 09:40:54 +01:00
38f793d349 Remove visible flag. all textures are visible at all moments. 2022-12-07 08:53:26 +01:00
0ba5e6a8dd Rename dirty to need_full_update. 2022-12-07 08:45:48 +01:00
ff280aba1b Cleanup: renamed ensure_float_buffer to cached_float_buffer and remove wrapper function. 2022-12-07 08:41:43 +01:00
7ccb299fb7 Make sure textures are drawn in the right place. 2022-12-07 08:28:26 +01:00
0b85d6a030 3D View: support canceling viewport operations
Support Esc / RMB to cancel dolly, move, rotate & zoom.
Previously only roll could be canceled.

This can be useful to temporary orbit away from the camera or an
orthographic view without having to manually set it back.
2022-12-07 17:45:24 +11:00
dcd4eb4c25 Cleanup: minor refactor to view operator event handling
- Add VIEW_CANCEL event_code.
- De-duplicate operator freeing logic for the roll operator.
- Structure checks so adding cancel is is simplified.
- Split event checks into two blocks, one for model events, another
  for all other events.
2022-12-07 17:45:24 +11:00
0e90896cba Cleanup: simplify udim parameters when uv packing
Migrate (some) of the UDIM offset calculation from inside one
of the packing engines (where it's consumed) to the packing
operator (where it's produced).

This change (and others) will help simplify the future migration
of the packing engine inside editors/uvedit/uvedit_islands.cc
to the Geometry module, so it can eventually replace the other
packing engine in geometry/intern/uv_parametrizer.cc
2022-12-07 15:30:13 +13:00
a5f9f7e2fc OBJ: Avoid retrieving mesh arrays, improve const correctness
Store the potentially owned mesh separately from the original/evaluated
mesh which is now stored with a const pointer. Also store mesh spans
separately in the class so they don't have to be retrieved for every
index.
2022-12-06 15:26:42 -06:00
a459018a99 Cleanup: Simplify naming in UV sphere primitive
It's obvious that these are indices, no need for it to be part of names.
2022-12-06 14:25:43 -06:00
c1d4105005 Cleanup: Remove unnecessary indentation in cone primitive
The loop is skipped if there are zero iterations anyway.
2022-12-06 14:25:43 -06:00
ff324ab716 Cleanup: Make mesh primitive topology building more parallel
Avoid using an incremented "loop index" variable which makes the whole
data-filling necessarily sequential. No functional changes expected,
this just simplifies some refactors to face corner storage.
2022-12-06 14:25:43 -06:00
fd9b197226 GPU: Fix using FLOAT_2D_ARRAY and FLOAT_3D textures via Python.
Translation from python enum values were incorrect and textures created
in python using those types would result in faulty textures. In
renderdoc those textures would not bind.
2022-12-06 20:16:39 +01:00
3124241256 Fix Cycles SSE4 define for fast math rint function.
Differential Revision: https://developer.blender.org/D16708
2022-12-06 19:06:43 +01:00
48b5dcdbe8 Animation: Removal of most of the old pose library
Remove most of the old (pre-3.0) pose library:

- Remove The entire `editors/armature/pose_lib.c` file
- Deprecate `Object::poselib` in DNA
- Remove Operators marked as deprecated in T93405
- Remove RNA property `Object.pose_library`
- Add comment to clarify that the call `BLO_read_id_address(reader,
  ob->id.lib, &ob->poselib);` handles deprecated data.

Note that this functionality has been documented as deprecated since
Blender 3.2.

What remains of the old pose library: The DNA for action markers
(`bAction::markers`) and the corresponding Python API. This will allow
future versions of Blender to still convert old pose libraries to new
ones (via the Pose Library panel in the Action editor).

Manifest task: T93406
2022-12-06 18:37:10 +01:00
de9f32a666 Merge branch 'blender-v3.4-release' 2022-12-06 18:27:44 +01:00
38e1b92b7a Fix T102964: Key-map event type values changed for NDOF buttons
Regression in [0] changed NDOF key-map values.

Restore the previous values & add missing view buttons to
rna_enum_event_type_items.

[0]: ecda118be4

Differential Revision: https://developer.blender.org/D16701
2022-12-06 18:26:24 +01:00
7d99c51e17 Cycles: enable light tree again
Bugs that caused wrong renders should be fixed now, and tests that showed minor
floating point differences on platforms were tweaked to sidestep the problem.

Ref T77889
2022-12-06 18:18:53 +01:00
bc9548da80 Merge branch 'blender-v3.4-release' 2022-12-06 18:05:08 +01:00
Joseph Eagar
31ccf79011 Fix T102336: dynamic topology crash
Ensure there is always an active vertex when a face is hit.

Differential Revision: https://developer.blender.org/D16705
2022-12-06 18:03:26 +01:00
658220e815 Fix T101245: Allow Thumbnails of > 256:1 Images
Ensure that thumbnails of images with aspect greater than 256:1 have
dimensions of at least one pixel.

See D16707 for more details

Differential Revision: https://developer.blender.org/D16707

Reviewed by Brecht Van Lommel
2022-12-06 08:17:50 -08:00
64541b242a Fix T102940: "Mask by Color" sculpt tool crash
We need to ensure the mask layer exists before running the operator.
I made the order of function calls here the same as in newer code later
on in this file for consistency.

Differential Revision: https://developer.blender.org/D16696
2022-12-06 08:17:50 -08:00
87582d95df Fix T101245: Allow Thumbnails of > 256:1 Images
Ensure that thumbnails of images with aspect greater than 256:1 have
dimensions of at least one pixel.

See D16707 for more details

Differential Revision: https://developer.blender.org/D16707

Reviewed by Brecht Van Lommel
2022-12-06 08:15:05 -08:00
995f757049 Fix T102940: "Mask by Color" sculpt tool crash
We need to ensure the mask layer exists before running the operator.
I made the order of function calls here the same as in newer code later
on in this file for consistency.

Differential Revision: https://developer.blender.org/D16696
2022-12-06 10:00:12 -06:00
16b6116b9d Fix Cycles light tree render errors on Windows
Due to mistake in popcount implementation. Thanks to Weizhen for help
figuring this out.
2022-12-06 16:52:15 +01:00
8f213e7436 Merge branch 'blender-v3.4-release' 2022-12-06 16:39:51 +01:00
37b02b0432 Fix T102965: Crash when compiling shader tree with undefined mix nodes
node_set_typeinfo leaves the bNode::type value as-is for nodes with undefined types.
2022-12-06 16:39:21 +01:00
adea6681c0 Merge branch 'blender-v3.4-release' 2022-12-06 16:35:44 +01:00
212b054bb3 Update freedesktop file with Blender 3.4 features. 2022-12-06 16:34:53 +01:00
7a6630e5ce Enable other textures as well. 2022-12-06 16:23:16 +01:00
44748e6fe3 Initial transform from tile to texture. 2022-12-06 15:40:53 +01:00
6428c847fd Cycles oneAPI: clarify Linux Driver requirements in GUI
"Linux Driver" wasn't precise enough for users, the actual driver
requirement is on "Intel® Graphics Compute Runtime for oneAPI Level Zero
and OpenCL™ Driver", ie. https://github.com/intel/compute-runtime /
intel-level-zero-gpu package.

This follows-up the discussion on
https://developer.blender.org/rBff89c1793d8c75615ed43248def25812ec13e6e3
2022-12-06 13:21:19 +01:00
67dd652557 Tests: anim keylist test, avoid interaction between checks
Simplify checks so that one check doesn't influence the following one.

Checks no longer pass the last-visited frame number into the "start frame"
parameter of the next check. This way all test values are hard-coded and
easy to read, without having to understand how all the checks fit together.

No functional changes.
2022-12-06 12:27:16 +01:00
979930f8b6 Test: animation, avoid segfault in keylist unit tests
Replace `EXPECT_NE(column, nullptr)` with `ASSERT_NE(column, nullptr)` to
abort the test on failure. With `EXPECT_NE`, the test would continue onto
the next like, which accesses `column->cfra` and would segfault.

No functional changes to the tests. Just better reporting of failures.
2022-12-06 12:27:16 +01:00
997ff54d30 Fix: UI: broken texpaintslot/color attributes/attributes name filtering
rB8b7cd1ed2a17 broke this for the paint slots
rB4669178fc378 broke this for regular attributes

Name filtering in UI Lists works when:
- [one] the items to be filtered have a name property
-- see how `uilist_filter_items_default` gets the `namebuf`
- [two] custom python filter functions (`filter_items`) implement it
themselves
-- if you use `filter_items` and dont do name filtering there, the default
name filtering wont be used

So, two problems with rB8b7cd1ed2a17:
- [1] items to be listed changed from `texture_paint_images` to
`texture_paint_slots`
-- the former has name_property defined, the later lacks this
- [2] the new `ColorAttributesListBase` defined a `filter_items` function,
but did not implement name filtering

And the problem with rB4669178fc378:
- it added `filter_items` functions, but did not implement name filtering.

These are all corrected now.

Fixes T102878

Maniphest Tasks: T102878

Differential Revision: https://developer.blender.org/D16676
2022-12-06 11:09:28 +01:00
31943d1313 Fix T102937: "view3d.view_roll" operator conflicts with RMB invocation
When RMB is used to start the operator, don't use it for canceling.
2022-12-06 20:50:51 +11:00
ea14c48c09 Fix T102276: Hotkey conflict Alt D in Node Editor with Duplicate Linked and Detach
This unassign the Alt+D shortcut from the detach operator. Right now the
operator has to be accessed via the menu.

Alt+D is left for duplicate link, following the other editors.
2022-12-06 09:45:20 +01:00
979b295154 Fix incorrect cursor motion coordinates for WIN32
Cursor motion events on windows read the position from GetCursorPos()
which wasn't always the same location stored in `lParam`.

In situations where events were handled immediately this wasn't often a
problem, for heavier scenes or when updates between event handling was
slow - many in-between cursor events would be incorrect.

This behavior dates back to the initial commit, there doesn't seem to be
a good reason not to use the cursor coordinates from the event.

Noticed when investigating T102346.
2022-12-06 17:18:01 +11:00
d486f33d63 GHOST: OpenGL errors now use "file:line: " contention for errors
Make OpenGL errors match formatting used by GCC & clang
(as well as Blender's logging), so utilities that recognize this
convention can be used to quickly access this location.
2022-12-06 14:48:38 +11:00
f68e50a263 WM: operators that add their own undo pushes now clears the redo panel
Detect when the operator adds its own undo step and clear the panel.

An alternative fix for [0] which caused T101743.

Needed to prevent changing values in the last operator panel from
destructively undoing brush steps.

[0]: 11bdc321a2.

Reviewed By: mont29, joeedh

Ref D16523
2022-12-06 14:01:36 +11:00
7465aa8965 Merge branch 'blender-v3.4-release' 2022-12-06 13:47:40 +11:00
26f66e1423 Revert "Sculpt: Register sculpt brush op so it clears last operator panel"
This reverts commit 11bdc321a2.

This change caused T101743, in general OPTYPE_UNDO should not be used
to control the UI.

This also caused 2x undo pushes to be performed when sculpting,
although sculpt mode doesn't add a step for the second undo push
so it wasn't visible to the user.

An alternative fix will be applied separately as it's too risky this
close to a release.
2022-12-06 13:45:20 +11:00
db54b99ee1 BLI_path_util: support both forward and back slashes for WIN32
The following functions only supported back slashes on WIN32,
which can use both forward and back slashes.

- BLI_path_append
- BLI_path_append_dir
- BLI_path_slash_ensure
- BLI_path_slash_rstrip

Follow up to [0] which is a more limited bug-fix.

[0]: a16ef95ff6
2022-12-06 13:28:39 +11:00
ed0125afe5 Merge branch 'blender-v3.4-release' 2022-12-06 13:19:04 +11:00
a16ef95ff6 Fix T102914: bpy.ops.wm.append no longer supports / in paths for WIN32
Support both forward and back slashes on WIN32.

Forward slashes for paths in WIN32 was removed in [0] (for BLI_path_join)
& [1] (for BLI_path_name_at_index), this is correct on UNIX as back
slashes can be used in paths but not on WIN32 which can use both.

Note that other path functions such as BLI_path_append &
BLI_path_slash_ensure should be updated too, but this is out of scope
for a bug-fix.

Documenting and ensuring Windows path handling functions all handle
forward slashes can be done separately.

[0]: 8f7ab1bf46
[1]: 511ae22264

Reviewed By: harley

Ref D16700
2022-12-06 13:09:47 +11:00
f450d39ada Fix T84078: improve UV unwrapping for quads with an internal reflex angle
When triangulating meshes, the UV unwrapper was previously using a
heuristic to split quads into triangles. If one of the internal angles
is greater than 180degrees, a so-called "reflex angle", the heuristic
was giving a poor choice of split.

Instead of using a special case for quads, this change routes everything
through the generic n-gon `BLI_polyfill_beautify` method instead.

Reviewed By: Brecht Van Lommel

Differential Revision: https://developer.blender.org/D16505
2022-12-06 13:56:02 +13:00
644afda7eb Fix T102543: improve uv unwrapping with n-gons and shared vertices
When n-gons share vertices, their triangulation can be non-manifold,
even if the original mesh is manifold.

The UV Unwrapper does not currently work with non-manifold meshes.

This workaround attempts to modify the triangulation of n-gons in
the UV unwrapper to preserve the manifold property.

This change replaces the previous fix for quads, and extends it
to all n-gons.

See T84078 as motivation for this change.

Differential Revision: https://developer.blender.org/D16521
2022-12-06 13:42:24 +13:00
7a12934f1e Merge branch 'blender-v3.4-release' 2022-12-06 11:12:53 +11:00
e29068aaed Cleanup: remove unused argument 2022-12-06 10:37:15 +11:00
Ramil Roosileht
587a1b16ae Attributes: Autofill for attribute conversion operators
Make "Convert Attribute" and "Convert Color Attribute" operators
auto-fill their initial settings with active attribute's domain
and data type if it wasn't already set explicitly.

Differential Revision: https://developer.blender.org/D16550
2022-12-05 16:30:50 -06:00
294e41477b Fix T102961: mirrored vertices sometimes get locked in transform
Two vertices within the threshold can mirror each other causing neither
to be transformed.
2022-12-05 19:11:11 -03:00
0808eaf44e Cycles: temporarily disable light tree again due to platform differences
Regression tests are failing with some platform/compiler combinations, and
fixing this is taking some time.

Ref T77889
2022-12-05 21:57:43 +01:00
5270610b29 Fix Cycles uninitialized variables in mesh light sampling
Causing wrong renders and differences between platforms.
2022-12-05 20:20:51 +01:00
1af8ddf69f Merge branch 'blender-v3.4-release' 2022-12-05 12:47:25 -06:00
c82749c0c0 Fix: memory leak in curve circle primitive node 2022-12-05 12:33:43 -06:00
2ce6ac462b Cleanup: Const correctness for node find functions
You shouldn't be able to retrieve a mutable node from a const node tree
or a mutable socket from a const node. Use const_cast in one place in
order to correct this without duplicating the function, which is still
awkward in the C-API.
2022-12-05 11:37:55 -06:00
ca2ca0ce5d Geometry Nodes: add instance test category 2022-12-05 17:56:47 +01:00
f646a4f22c Cleanup: renaming tan_spread to cot_half_spread to avoid ambiguity
Differential Revision: https://developer.blender.org/D16695
2022-12-05 17:04:04 +01:00
ee89f213de Cycles: improve many lights sampling using light tree
Uses a light tree to more effectively sample scenes with many lights. This can
significantly reduce noise, at the cost of a somewhat longer render time per
sample.

Light tree sampling is enabled by default. It can be disabled in the Sampling >
Lights panel. Scenes using light clamping or ray visibility tricks may render
different as these are biased techniques that depend on the sampling strategy.

The implementation is currently disabled on AMD HIP. This is planned to be fixed
before the release.

Implementation by Jeffrey Liu, Weizhen Huang, Alaska and Brecht Van Lommel.

Ref T77889
2022-12-05 16:09:03 +01:00
0731d78d00 Cycles: remove shadow pass
This was not working well in non-trivial scenes before the light tree, and now
it is even harder to make it work well with the light tree. It would average the
with equal weight for every light object regardless of intensity or distance, and
be quite noisy due to not working with multiple importance sampling.

We may restore this if were enough good use cases for the previous implementation,
but let's wait and see what the feedback is.

Some uses cases for this have been replaced by the shadow catcher passes, which
did not exist when this was added.

Ref T77889
2022-12-05 15:52:10 +01:00
ccae00c9e2 Fix: memory leak in curve circle primitive node 2022-12-05 15:34:54 +01:00
83077d3683 Fix: wrong pivot point output in String to Curves node
The issue was that using `curves.bounds_min_max` included the radius
which does not make sense in this context.
2022-12-05 13:20:30 +01:00
44ab02fc5c Geometry Nodes: add texture regression test category 2022-12-05 12:06:18 +01:00
42b51bf6a9 Fix T102571: Can't stop audio playback when using multiple windows
Traverse all scene dependency graphs and stop audio playback for each
scene.

Also fixes T71233

Reviewed By: sergey, mont29

Differential Revision: https://developer.blender.org/D16646
2022-12-05 04:45:38 +01:00
9cb061f4f0 Cleanup: spelling in comments 2022-12-05 12:58:18 +11:00
0dee238c8c Cleanup: remove duplicate doc-strings
Duplicating doc-strings in both header & implementation
should be avoided as they often diverge & maintaining them is more work.
2022-12-05 12:54:04 +11:00
2b914a2ecb Cleanup: correct misspelling of occurrence 2022-12-05 12:54:02 +11:00
cc6bdac921 Cleanup: format 2022-12-05 12:54:00 +11:00
997e143a50 Cleanup: quiet compiler warnings 2022-12-05 12:53:56 +11:00
9719fd6964 Cleanup: format 2022-12-03 10:53:44 +13:00
Iliya Katueshenock
18e386613c Attributes: Remove asserts for DefaultMixer negative weight
The attribute smoothing node asks for the ability to have a factor
outside the range of 0 and 1. The problem with this is that there is a
negative weight assertion for some of the mixers. If mixing between 0
and 1, then at a factor of 2, one of the elements will be negative.

Differential Revision: https://developer.blender.org/D16351
2022-12-02 14:44:54 -06:00
ce16fa0f4c Fix: Node Editor: Hide compoitor-specific menu items
Previews and the "Read Viewlayers" operator are specific to the
compositor and shouldn't show in other node editor types.
2022-12-02 14:31:44 -06:00
2155bdd500 Cleanup: Remove "done" variable from node runtime
The runtime storage is meant for more persistent things. These local
states for an algorithm are much better handled by an array now.
2022-12-02 14:14:14 -06:00
1c26341464 Cleanup: Gammar in BMesh mesh conversion comment 2022-12-02 13:28:30 -06:00
ab4926bcff Fix: Various mishandling of node identifiers and vector
In a few places, nodes were added without updating the Identifiers and
vector. In other places nodes we removed without removing from and
rebuilding the vector. This is solved in a few ways. First I exposed
a function to rebuild the vector from scratch, and added unique ID
finding to a few places.

The changes to node group building and separating are more involved,
mostly because it was hard to see the correct behavior without some
refactoring. Now `VectorSet` is used to store nodes involved in the
operation. Some things are handled more simply with the topology
cache and by passing a span of nodes.
2022-12-02 13:28:30 -06:00
948f13a8e7 Cleanup: compiler warning 2022-12-02 19:13:38 +01:00
71071a25a0 Fix crash on File > Link or Append
Would attempt to destruct memory of a null pointer. Use `MEM_delete()`
instead of manual destruction, which allows this case (NOP then).
2022-12-02 19:09:52 +01:00
2a33875065 Fix link error after recent changes to use span for iterating over nodes 2022-12-02 18:51:38 +01:00
0302ab4e02 Fix link error on Linux + Clang due to missing atomic symbols
The new atomic disjoint set uses additional atomics which are not supported
as intrinsics on all architectures and require linking to libatomic.

Now always link to libatomic on Linux when it is available, instead of only
checking if atomic add for int64_t requires linking to this library.

Thanks to Sergey for the help fixing this.
2022-12-02 18:27:07 +01:00
6b7119f9ed Merge branch 'blender-v3.4-release' 2022-12-02 11:24:18 -06:00
99dc90accc Fix: Reversed attribute is_internal RNA property
`is_internal` is supposed to mean that the attribute shouldn't be
visible in lists or the spreadsheet by default, and that it can't be
accessed in geometry nodes. But the value was reversed, which
just happened to work because the list filtering was swapped.

Differential Revision: https://developer.blender.org/D16680
2022-12-02 11:21:54 -06:00
5b8e2ebd97 Cleanup: Use Span to iterate over nodes instead of ListBase
Since 90ea1b7643, there is always a span of nodes
available at runtime. This is easier to read and write.
2022-12-02 11:13:00 -06:00
c5e71cebaa Cycles: Remove OpenGL header
It is not really used from any of the sources, including the
standalone app. Since we are moving to a more backend-independent
drawing it makes sense to remove header which was specific to
how Blender integrates Cycles into viewport.

There is probably some cleanup in CMake files is possible, but
there is some inter-dependency with USD.

Differential Revision: https://developer.blender.org/D16681
2022-12-02 17:19:00 +01:00
ab8946f957 Merge branch 'blender-v3.4-release' 2022-12-02 16:48:09 +01:00
3d9f4012dc Cycles: Fixes for viewport render on Metal drawing backend
This change fixes issues with viewport rendering when Metal
GPU backend is used for drawing. This is not a default build
configuration and requires the following tweaks:

- Enable WITH_METAL_BACKEND CMake option (set it to on)
- Use `--gpu-backend metal` command line arguments

It also helps using the `--factory-startup` command line
argument to ensure Eevee is not used (it is not ported and
will crash).

The root of the problem was in the use of glViewport().
It is replaced with the GPU_viewport_size_get_i() which
is supposed to be portable equivalent form the GPU module.
Without this change the viewport size is detected to be 0
which backfired in few places.

The rest of the changes were to make the code more robust
in the extreme conditions instead of asserting or crashing.

Simplified and streamlined GPU resources creation in the
display driver. It was a bit convoluted mix of creation of
the GPU resources and resizing them to the proper size. It
even seemed to be done in the reverse order. Now it is as
simple as "just ensure GPU resources are there for the
given texture or buffer size".

Also avoid division by zero in the tile manager.

Differential Revision: https://developer.blender.org/D16679
2022-12-02 16:46:43 +01:00
e2f6fb5d35 i18n: Enable Finish language processing. 2022-12-02 16:28:53 +01:00
2bce3c0ac4 Fix: don't allow node identifiers to be zero
Was missing in rB88c6d824e78ebe40b891.
2022-12-02 15:42:15 +01:00
e028662f78 Cycles: store axis and length of an area light instead of their product 2022-12-02 15:23:09 +01:00
6a7917162c Fix asset index only generating empty entries since 1efc94bb2f
Steps to reproduce were:
- Open a .blend file that is located inside of an asset library and
  contains assets.
- Save and close the file.
- Open a new file (Ctrl+N -> General).
- Open asset browser and load the asset library from above.
- If the assets from the file above still show up, press refresh button.
- -> Assets from the file above don't appear.

Likely fixes the underlying issue for T102610. A followup will be needed
to correct the empty asset index files written because of this bug.

We're in the process of moving responsibilities from the file/asset
browser backend to the asset system. 1efc94bb2f introduces a new
representation for asset, which would own the asset metadata now instead
of the file data.

Since the file-list code still does the loading of asset libraries,
ownership of the asset metadata has to be transferred to the asset
system. However, the asset indexing still requires it to be available,
so it can update the index with latest data. So transfer the ownership,
but still keep a non-owning pointer set.

Differential Revision: https://developer.blender.org/D16665

Reviewed by: Bastien Montagne
2022-12-02 14:48:51 +01:00
79498d4463 Cleanup: Silenced unused parameter in pbvh.c 2022-12-02 13:43:22 +01:00
ea86ec200a GPU: Added VkVertexBuffer alloc/release data.
This makes sure that the GPU_batch_init will not crash on an assert
where the data of vertex buffer needs to be allocated.
2022-12-02 13:41:23 +01:00
b8c7e93a65 Add experimental option to force all linked data as directly linked.
This is a workaround required to get BAT reliably working again after
recent rB133dde41bb5b, which fixed many indirectly linked IDs being
tagged as directly linked, and therefore having their reference written
in .blend file.

It seems that BAT is still missing proper handling of some ID pointers.

Required for the end of the Heist production here at Blender Studio.
2022-12-02 13:39:28 +01:00
d57f68616a Fix: bump minimum version
rB9fa4ceb340951 caused a forward compatibility issue.
Going forward, when changing socket names, only the name should be
changed and not the identifier if possible.
2022-12-02 13:18:54 +01:00
6d22aa2f84 Cleanup: simplify access to cached mesh normals 2022-12-02 13:12:06 +01:00
198460f6a4 Cleanup: fix compiler warning about using %u with int value
`but->type` is an `enum`, which maps to `int`, so `%d` should be used for
printing its value with `printf()`.
2022-12-02 12:54:11 +01:00
caac5686c5 GPU: Add vulkan to GPU_backend_get_type().
Vulkan backend detection wasn't added to GPU_backend_get_type.
This change will add support for vulkan to the function.
2022-12-02 12:51:11 +01:00
3d5a4fbcc2 Cleanup: move some files that use normals to C++
Doing this to help with T102858.
2022-12-02 12:34:26 +01:00
88c6d824e7 Nodes: ensure that node identifiers are larger than zero
Zero should not be a valid identifier to make it easier to detect when
the identifier has not been set after a node has been allocated.
2022-12-02 11:59:20 +01:00
39615cd3b7 BLI: add atomic disjoint set data structure
The existing `DisjointSet` data structure only supports single
threaded access, which limits performance severely in some cases.

This patch implements `AtomicDisjointSet` based on
"Wait-free Parallel Algorithms for the Union-Find Problem"
by Richard J. Anderson and Heather Woll.

The Mesh Island node also got updated to make use of the new data
structure. In my tests it got 2-5 times faster. More details are in 16653.

Differential Revision: https://developer.blender.org/D16653
2022-12-02 10:39:19 +01:00
5f0120cd35 Merge branch 'blender-v3.4-release' 2022-12-02 08:47:05 +01:00
0197b524e4 Update THIRD-PARTY-LICENSES.txt for Blender 3.4. 2022-12-02 00:38:23 -08:00
46f991dbae Sculpt: Fix broken pivots when entering paint modes
When entering paint modes the paint pivot was cleared,
which broken rotate around pivot.  Fixed for all paint modes.
PBVH modes set the pivot to the PBVH bounding box
while texture paint uses the evaluated mesh bounding box.
2022-12-02 00:37:50 -08:00
6b0e769d14 Nodes: Restrict viewer key tree updates to compositor
The active viewer key is only used by the compositor, so only tag the
node tree for update of it is a compositor node tree.
2022-12-02 10:32:50 +02:00
09ee781a67 GPU: Add placeholders for PixelBuffer to vulkan backend.
PixelBuffer was recently introduced. This change adds empty placeholders to the
vulkan backend and other related API tweaks.
2022-12-02 08:35:17 +01:00
587b213fe1 Fix: Node sorting broken after node identifier commit
90ea1b7643 broke the sorting that happens as nodes are selected.
The compare function for stable sort had different requirements than
the previous implementation.
2022-12-01 17:55:33 -06:00
4d5e8b7caa Cleanup: Use new node identifiers when copying tree
We can avoid creating a new map and use the node vector set that
must be built anyway when updating pointers in the new tree.
2022-12-01 15:40:46 -06:00
e78cd27565 Fix T102895: Grammar in apply scale operator
"Fonts" are referred to as "Text objects" now.
2022-12-01 15:40:46 -06:00
b768a2bf2f Cleanup: Remove unnecessary list clearing in node tree reading
The lists were cleared a few lines below already.
2022-12-01 15:40:46 -06:00
8842a8c4c3 Cleanup: format 2022-12-02 10:14:50 +13:00
90ea1b7643 Nodes: Use persistent integer to identify to nodes
This patch adds an integer identifier to nodes that doesn't change when
the node name changes. This identifier can be used by different systems
to reference a node. This may be important to store caches and simulation
states per node, because otherwise those would always be invalidated
when a node name changes.

Additionally, this kind of identifier could make some things more efficient,
because with it an integer is enough to identify a node and one does not
have to store the node name.

I observed a 10% improvement in evaluation time in a file with an extreme
number of simple math nodes, due to reduced logging overhead-- from
0.226s to 0.205s.

Differential Revision: https://developer.blender.org/D15775
2022-12-01 15:08:12 -06:00
fefe7ddf39 BLI: Add math::orthogonal and math::compare
Port of C BLI API.
2022-12-01 21:46:06 +01:00
2466b2e43c Cleanup: BLI: Rename arguments of math::atan2 2022-12-01 21:46:06 +01:00
730fd0a257 BLI: Add math::sqrt
Allows other number types to overload this function without poluting std
namespace.
2022-12-01 21:46:06 +01:00
4c1b250e17 Fix T102893: Assert Opening File Browser (Win32)
Fix debug assert opening File Browser on Windows platform.

See D16672 for more details.

Differential Revision: https://developer.blender.org/D16672

Reviewed by Julian Eisel
2022-12-01 12:25:49 -08:00
25501983bb Cleanup: Spelling mistake in comment 2022-12-01 16:29:38 +01:00
009f7de619 Cleanup: use better matching integer types for graphics interop handle
Ref D16042
2022-12-01 15:55:48 +01:00
Jason Fielder
b132e3b3ce Cycles: use GPU module for viewport display
To make GPU backends other than OpenGL work. Adds required pixel buffer and
fence objects to GPU module.

Authored by Apple: Michael Parkin-White

Ref T96261
Ref T92212

Reviewed By: fclem, brecht

Differential Revision: https://developer.blender.org/D16042
2022-12-01 15:55:48 +01:00
b5ebc9bb24 Fix T101996: merge fcurve keyframes on the same frame after snapping
Use recently introduced BKE_fcurve_merge_duplicate_keys (that was moved
from the transform system to BKE) to merge keyframes on the same frame
after snapping (same as what would happen with the transform system).

This makes behavior consistent and prevents a state after snapping that
cannot be reproduced in any other way.

NOTE: same probably has to be done for greasepencil, but that is for
another commit.
2022-12-01 15:41:55 +01:00
a179246e1f Move fcurve cleanup from transform system to BKE
This exposes the fcurve cleanup from transform system to other callers
in anticipation to use it in the snapping operators.

It has been renamed from `posttrans_fcurve_clean` to
`BKE_fcurve_merge_duplicate_keys` to better describe what it does.
No functional change expected.

Ref. T101996

NOTE: same probably has to be done for greasepencil, but that is for
another commit.

Maniphest Tasks: T101996

Differential Revision: https://developer.blender.org/D16663
2022-12-01 15:41:50 +01:00
f95768d1e0 Update THIRD-PARTY-LICENSES.txt for Blender 3.4. 2022-12-01 15:21:43 +01:00
5e4dcb8cf0 Cleanup: use OB_MODE_ALL_PAINT_GPENCIL in more places
This just replaces the combined usage of OB_MODE_PAINT_GPENCIL
OB_MODE_SCULPT_GPENCIL
OB_MODE_WEIGHT_GPENCIL
OB_MODE_VERTEX_GPENCIL.

Differential Revision: https://developer.blender.org/D16652
2022-12-01 12:15:36 +01:00
1a2e2dcddc Cleanup: Improve function name for asset identifier creation
I find this a bit more explanatory/clear.
2022-12-01 11:42:27 +01:00
5c580ff457 Fix asset-only loading optimizatoin not working as intended
Introduced in fc7beac8d6, but I think this never worked because the
`asset_library_ref` of the temporary file-list used for reading in a
background thread is nulled. Now there's a different pointer that we can
use that works properly.
2022-12-01 11:42:27 +01:00
9f3b0e41bb Fix T102887: crash deleting plane track
Two things here:
- fix ghash lookup from rB4d497721ecd1
-- this was looking in the wrong map (causing an assert on file load)
- set MovieTrackingObject active_plane_track to NULL upon deletion (same
as for regular tracks)
-- rBfe38715600c introduced a crash because `draw_tracking_tracks` would
still get an active plane track (logic for getting these changed)

Maniphest Tasks: T102887

Differential Revision: https://developer.blender.org/D16660
2022-12-01 10:59:59 +01:00
3cebc58936 Fix: Assert in subdivide curves node after span slicing change
a5e7657cee missed this call where clamped slicing is necessary.
The subdivision of a segment purposefully modifies the handle types of
the other side of the following control point, but that didn't work for
the final cyclic segment.
2022-11-30 21:21:58 -06:00
4aac5b92c1 Sculpt: Fix T102824: broken face primitive partitioning in pbvh nodes
The code I wrote to group triangles or multires quads that
belonging to single faces into single PBVH nodes had edge
cases that failed.  The code is now much simpler and simply
assigns groups of primitives to nodes.
2022-11-30 13:55:08 -08:00
65393add52 Sculpt: Fix broken pivots when entering paint modes
When entering paint modes the paint pivot was cleared,
which broken rotate around pivot.  Fixed for all paint modes.
PBVH modes set the pivot to the PBVH bounding box
while texture paint uses the evaluated mesh bounding box.
2022-11-30 13:54:56 -08:00
918282d391 Sculpt: fix crash when no brush
If no brush exists the stroke operator
falls through to the grab transform
op in the global view3d keymap.

This now works.  It would be nice if
we could get rid of that keymap entry
though and add it manually to the edit/paint
modes that need it.
2022-11-30 13:54:03 -08:00
7151c2dc3e Cleanup: Unused variable, RNA description warning 2022-11-30 15:34:08 -06:00
1017b493ed Sculpt: Fix T102824: broken face primitive partitioning in pbvh nodes
The code I wrote to group triangles or multires quads that
belonging to single faces into single PBVH nodes had edge
cases that failed.  The code is now much simpler and simply
assigns groups of primitives to nodes.
2022-11-30 13:26:36 -08:00
fde628ddb3 Sculpt: Fix broken pivots when entering paint modes
When entering paint modes the paint pivot was cleared,
which broken rotate around pivot.  Fixed for all paint modes.
PBVH modes set the pivot to the PBVH bounding box
while texture paint uses the evaluated mesh bounding box.
2022-11-30 13:22:24 -08:00
8fa5206ab0 Sculpt: fix crash when no brush
If no brush exists the stroke operator
falls through to the grab transform
op in the global view3d keymap.

This now works.  It would be nice if
we could get rid of that keymap entry
though and add it manually to the edit/paint
modes that need it.
2022-11-30 13:22:24 -08:00
222b64fcdc Fix Cycles CUDA crash when building kernels without optimizations (for debug)
In this case the blocksize may not the one we requested, which was assumed to be
the case. Instead get the effective block size from the compiler as was already
done for Metal and OneAPI.
2022-11-30 21:46:17 +01:00
b25c301c15 Build: make CUDA kernel compilation output not verbose
Unless using WITH_CYCLES_DEBUG.

This is convenient for investigating kernel performance, but too verbose to
always have in the buildbot logs especially now that we are also compiling HIP
and OneAPI kernels.
2022-11-30 21:19:51 +01:00
396b407c7d Cycles: new setting and heuristics for mesh light importance sampling
Materials now have an enum to set the emission sampling method, to be
either None, Auto, Front, Back or Front & Back. This replace the
previous "Multiple Importance Sample" option.

Auto is the new default, and uses a heuristic to estimate the emitted
light intensity to determine of the mesh should be considered as a light
for sampling. Shaders sometimes have a bit of emission but treating them
as a light source is not worth the memory/performance overhead.

The Front/Back settings are not important yet, but will help when a
light tree is added. In that case setting emission to Front only on
closed meshes can help ignore emission from inside the mesh interior that
does not contribute anything.

Includes contributions by Brecht Van Lommel and Alaska.

Ref T77889
2022-11-30 21:19:51 +01:00
ac51d331df Refactor: Cycles light sampling code reorganization
* Split light types into own files, move light type specific code from
  light tree and MNEE.
* Move flat light distribution code into own kernel file and host side
  building function, in preparation of light tree addition. Add light/sample.h
  as main entry point to kernel light sampling.
* Better separate calculation of pdf for selecting a light, and pdf for
  sampling a point on the light. The selection pdf is now also stored in
  LightSampling for MNEE to correctly recalculate the full pdf when the
  shading position changes but the point on the light remains fixed.
* Improvement to kernel light storage, using packed_float3, better variable
  names, etc.

Includes contributions by Brecht Van Lommel and Weizhen Huang.

Ref T77889
2022-11-30 21:19:51 +01:00
db1728096a Cleanup: Remove unused node socket cache handling
This cache was never written to, only "copied" between sockets in one
case, it dates back at least a decade. It doesn't make sense to store
caches on node trees directly anyway, since they can be used in
multiple places.
2022-11-30 13:25:06 -06:00
31b3b07ad7 Cleanup: Remove useless comments in node.cc
Also remove unnecessary `struct` keywords.
2022-11-30 13:11:12 -06:00
f37e8c2e96 Merge branch 'blender-v3.4-release' 2022-11-30 20:08:31 +01:00
692474ccf9 Release cycle: Bump BLENDER_VERSION_CYCLE to rc for 3.4. 2022-11-30 20:04:37 +01:00
b582028b12 Correct previously missed case of manual path building in file browser
Missed in 39c9164ea1. Also adds a comments to point at the function
that should be used instead.
2022-11-30 20:02:09 +01:00
39c9164ea1 File/Asset Browser: Get full asset path from asset representation
No user visible changes expected.

Add a function to query the full path for a file, so that asset files
can get the path via the asset representation and its new asset
identifier. This is designed to be a reliable way to locate an asset,
and using it is yet another step to rely less on the problematic file
browser code.
Also, previous code would build the full path manually in a few places,
which is good to deduplicate anyway.
2022-11-30 19:44:34 +01:00
ccc9eef1b9 Assets: Get asset path via new identifier (not via file browser hacks)
With the asset identifier introduced in the previous commit, we can now
locate an asset just from its `AssetRepresentation`, without requiring
information from the asset library and the file browser storage. With
this we can remove some hacks and function parameters. A RNA/BPY
function is also affected, but I didn't remove the paramter to keep
compatibility. It's simply ignored and not required anymore, noted this
in the parameter description (noted for T102877).
2022-11-30 19:44:34 +01:00
f68da703a5 Asset system: Initial asset identifier type
No user visible changes expected.

`AssetIdentifier` holds information to uniquely identify and locate an
asset. More information:
https://wiki.blender.org/wiki/Source/Architecture/Asset_System/Back_End#Asset_Identifier

For the start this is tied quite a bit to file paths, so that external
assets are assumed to be in the file system.

This is needed to support an "All" asset library (see T102879), which
would contain assets from different locations. Currently the location of
an asset is queried via the file browser backend, which however requires
a common root location. It also moves us further away from the file
browser towards the asset system (see T87235) and allows us to remove
some hacks (see following commit).
2022-11-30 19:44:34 +01:00
cfaca0d9ab Asset System: Store root path in asset library data
No user visible changes expected.

If an asset library is located on disk, store the path to it in the
asset library data. This is called the "root path" now.
With this we can construct an asset identifier, which is introduced in
the following commit.
2022-11-30 19:44:34 +01:00
2165136740 File/Asset Browser: Refactor how recursive paths are set
When reading directories recursively, the code would first only set the
file name as the relative path and then later iterate over the read files
and prepend the recursed into path, to get the complete path relative to
the recursed into directory. This isn't clear and confused me quite a
bit. And it is not compatible with what we need for creating asset
identifiers, which are introduced in the 2nd following commit.

Instead properly determine the complete relative path when initially
adding the file, and don't change it after. The asset identifier can the
be constructed properly at the time needed.
2022-11-30 19:44:34 +01:00
b78b6e3cd7 Cleanup: Correct comment in hash description
We use the blender namespace now rather than BLI.
2022-11-30 11:49:32 -06:00
7cdcb76815 Cleanup: Remove node tree runtime fields
`done` was only used in one place, and `is_updating` was never read.
Generally we should avoid adding this sort of temporary data to longer
lived structs.
2022-11-30 11:41:01 -06:00
507b724056 Cleanup: Remove unnecessary BMesh unique pointer in OBJ code
This is only used once, it's simpler to just free it in that case and
wait for further RAII improvements from elsewhere in the codebase.
2022-11-30 10:46:37 -06:00
0b13e7ce0f Cleanup: Remove unnecessary use of deprecated DNA define
This was solved by `dna::shallow_copy`
2022-11-30 10:27:33 -06:00
Christoph Lendenfeld
c17d7ddabe Merge branch 'blender-v3.4-release' 2022-11-30 17:26:01 +01:00
Christoph Lendenfeld
5c1cc79cf4 Fix T100879: Bake Action fails with "Nothing to Bake"
When applying the "Bake Action" operator in pose mode
it could throw an error saying "Nothing to Bake"
even though bones are selected

That is because the code was looking for a selected armature
But in Pose Mode, clicking into empty space to de-select would also
deselect the armature.
Then box selecting would not make the armature selected again

Reviewed by: Sybren A. Stüvel
Differential Revision: https://developer.blender.org/D16593
2022-11-30 17:24:14 +01:00
429771fed5 Add 'work around' to accessing data from volatile iterators in py API.
Re T102550.
2022-11-30 17:04:37 +01:00
Christoph Lendenfeld
18de712257 Fix T100879: Bake Action fails with "Nothing to Bake"
When applying the "Bake Action" operator in pose mode
it could throw an error saying "Nothing to Bake"
even though bones are selected

That is because the code was looking for a selected armature
But in Pose Mode, clicking into empty space to de-select would also
deselect the armature.
Then box selecting would not make the armature selected again

Reviewed by: Sybren A. Stüvel
Differential Revision: https://developer.blender.org/D16593
2022-11-30 16:57:21 +01:00
313c2e9105 Fix a test after recent changes to lib (in)directly linked ID handling.
rB133dde41bb5b changed handling of (in)directly linked status handling
for IDs, now IDs that are not directly linked get proper status and
handling on file save. this broke parts of the `pyapi_idprop_datablock`
tests.
2022-11-30 15:08:11 +01:00
19dc2157cd BLI: Add trigonometric functions to BLI_math_base.hh``
This is needed for the upcomming matrix library.
2022-11-30 12:59:47 +01:00
249acdf529 Cleanup: Unused variable warning in release build 2022-11-30 12:49:33 +01:00
Bastien Montagne
133dde41bb Improve handling of (in)direclty linked status for linked IDs.
This commit essentially ensures before writing .blend file that only
actualy locally used linked IDs are tagged as `LIB_TAG_EXTERN` (and
therefore get a reference stored in the .blend file).

Previously, a linked ID tagged as directly linked would never get back
to the indirectly linked status. Consequence was a lot of 'needless'
references to linked data in .blend files.

This commit also introduces a new flag for lib_query ID usage types,
`IDWALK_CB_DIRECT_WEAK_LINK`, used currently for base's Object
pointer, and for LayerCollection's Collection pointer.

NOTE: A side-effect of this patch is that even IDs explicitely linked by
the user won't be kept anymore when writing files, i.e. they will not be
there anymore after a file reload, if they are not actually used.

Overhead of new process is below 0.1% in whole file saving process in
a Heist production file e.g.

Reviewed By: brecht

Differential Revision: https://developer.blender.org/D16605
2022-11-30 11:16:14 +01:00
ae081b2de1 Cleanup: reduce variable scope in uv parametrizer
Also improve const correctness and update comments.

Simplify future fix for T78101
2022-11-30 12:01:40 +13:00
d602d4f15f Cleanup: reduce variable scope in uv parametrizer
Simplify future fix for T78101
2022-11-30 11:56:00 +13:00
5ce72bba7e Cleanup: simplify flush/blend logic in uv parametrizer
Simplify future fix for T78101
2022-11-30 11:50:12 +13:00
b99cdf7472 Cleanup: use blenlib geometry functions in uv parametrizer
Simplify future fix for T78101
2022-11-30 11:44:13 +13:00
b7d27ce914 Fix T102861: Store named attribute warning with empty geometry 2022-11-29 16:36:09 -06:00
cb7e36cfa5 Merge branch 'blender-v3.4-release' 2022-11-29 21:05:45 +01:00
6c4c09b2df Fix T102519: GPU Subdivision : selected vertices not visible in paint mode
Retrieve and load the vertices selected/hidden state in pos_nor extraction.

Reviewed By: fclem

Maniphest Tasks: T102519

Differential Revision: https://developer.blender.org/D16594
2022-11-29 21:05:14 +01:00
68a0846021 Fix T102657: Unable to add strip to new channel
This is usability improvement, rather than bugfix. By default, height of
VSE timeline is clamped to 7 channels, unless more are added. But adding
new strip is not intuitive, since user can't scroll up due to clamping.

Clamp timeline height to n+1 used channels, so there is always 1 free
channel visible.
2022-11-29 19:49:40 +01:00
3f9febcabf Merge branch 'blender-v3.4-release' 2022-11-29 19:09:42 +01:00
412fdc9698 Fix crash when dragging video into VSE preview area
Caused by NULL dereference, because `Editing` data were not initialized.
Ensure data are initialized prior to using them.
2022-11-29 19:07:34 +01:00
9f753e5649 Fix build error after PyGPU changes 2022-11-29 18:46:59 +01:00
8fa8cea8e0 Fix PyGPU: return NULL instead of PyNone on error
Error in d7f124f06f
2022-11-29 13:59:52 -03:00
d7f124f06f Fix T102845: GPU python crash in background mode
`BPYGPU_IS_INIT_OR_ERROR_OBJ` is not implemented in all pygpu functions.

Instead of copying and pasting that call across the API when it has no
gpu context, override the methods with one that always reports error.
2022-11-29 13:55:46 -03:00
71f9fbcf35 Merge branch 'blender-v3.4-release' 2022-11-29 16:46:07 +01:00
85d9f12339 BLI: increase default inline buffer capacity in BitVector
Using 32 does not make much sense, because there will be 4 remaining
padding bytes in the struct anyway. Using 64 instead does not actually
increase the size of the struct, but makes allocations less likely.
2022-11-29 16:35:59 +01:00
7b08298927 BLI: use no_unique_address in BitVector
This allows the vector to be smaller when it has no inline buffer (24 ->32 byte).
2022-11-29 16:34:57 +01:00
01f4b9b32d Fix T102795: Stereoscopy checkbox turns the VSE preview black
With stereoscopy enabled, sseq->multiview_eye is set to left and
right eye during drawing, but this value is not reset, even if
stereoscopy is disabled.

Reviewed By: fclem

Differential Revision: https://developer.blender.org/D16639
2022-11-29 16:34:31 +01:00
57613630c7 BLO: use blender::Map in OldNewMap
`OldNewMap` used to have its own map implementation. Given that
the file uses C++ now, it is easy to use a C++ map implementation
instead. This simplifies the code a lot.

Going forward it might make sense to remove the `OldNewMap`
abstraction or to split it up in two (currently, `NewAddress.nr` has
two different meanings in different contexts which is confusing).

No functional changes are expected.

Differential Revision: https://developer.blender.org/D16546
2022-11-29 16:24:45 +01:00
fdf1837120 Merge branch 'blender-v3.4-release' 2022-11-29 13:17:33 +01:00
eae2917fde Fix: crash in node poll function due to missing null check 2022-11-29 13:16:41 +01:00
2b639f671f GPencil: Create Keyframe using Eraser if Auto-key is ONn
Before, the frame was not created, but now if there is
a previous stroke and teh frame changed, the new keyframe
is created.

This is related to T102623
2022-11-29 12:15:10 +01:00
2b85151a32 Cleanup: Braces around initialization of subobject 2022-11-29 11:03:48 +01:00
863cd1ea8e Merge branch 'blender-v3.4-release' 2022-11-29 09:33:41 +01:00
e97443478e Fix T102797: Unlinking an Orphan Action crashes
In `Orphan Data` (or `Blender File`) view, the ID pointer of the
actions's parent tree-element wasn't actually pointing to an ID, but to
the list-base containing the IDs.

Early out (with a warning) if the object or object-data to unlink the
action from is not clear.

Caused by rBb4a2096415d9.

Similar to rBe772087ed664.

Maniphest Tasks: T102797

Differential Revision: https://developer.blender.org/D16635
2022-11-29 09:31:19 +01:00
4067e6bc41 Cleanup: format 2022-11-29 17:33:07 +13:00
3d1594417b UV: support constrain-to-bounds for uv shear operator
For uv rotation operator, see rBd527aa4dd53d4.
2022-11-29 12:04:36 +13:00
6fdddae2b0 Fix T102804: Click & Drag on toggles no longer possible
Typo in 136ea84d9a
2022-11-28 16:29:37 -06:00
01a38c2be9 Fix T102827: 3D View header layout broken after C++ conversion
I missed this flag when removing designated initializers.
2022-11-28 16:29:37 -06:00
0ed4865fd0 Sculpt: Fix T102337: Null pointer error circle (tube) brush test code 2022-11-28 13:11:52 -08:00
0ad8f3ff58 Sculpt: fix T102348: Don't fold area normal automasking into cache
Certain automasking modes build a factor cache.  Modes
that rely on the mirror symmetry pass should not fold into
this pass.
2022-11-28 13:11:51 -08:00
1fc5dc3bf3 Sculpt: fix T102664: Broken multires solid shading
Calculate quad normal directly instead of averaging
the vertex normals.
2022-11-28 13:11:51 -08:00
48143ab001 Sculpt: Fix T102337: Null pointer error circle (tube) brush test code 2022-11-28 13:07:50 -08:00
b1cc2a48f2 Sculpt: fix T102348: Don't fold area normal automasking into cache
Certain automasking modes build a factor cache.  Modes
that rely on the mirror symmetry pass should not fold into
this pass.
2022-11-28 12:59:53 -08:00
2e1a479e29 Sculpt: fix T102664: Broken multires solid shading
Calculate quad normal directly instead of averaging
the vertex normals.
2022-11-28 12:36:32 -08:00
d1d2946f59 Merge branch 'blender-v3.4-release' 2022-11-28 14:12:04 -06:00
07200eaa85 Merge branch 'blender-v3.4-release' 2022-11-28 21:07:13 +01:00
745851e26b Fix T102772: Propagate edge creases in subdivision surface modifier
Before rBa8a454287a27, edge creases were copied to the result mesh as
part of `MEdge`. Now they are stored in a separate `CD_CREASE` layer,
which I mistakenly disabled for interpolation. However, to maintain
the geometry node uses a field input, remove the interpolated attribute
there.
2022-11-28 14:06:25 -06:00
1bacd09abb Fix: Mishandled creases in Cycles adaptive subdivision
Caused by a8a454287a which assumed it was possible
to access the raw data of the edge creases layer. Also allow
processing vertex creases even if there aren't any edge creases.
2022-11-28 14:06:25 -06:00
719ad4f93f Fix T100537: wrong depth pass for background after recent fix for gaps
Also have to write if we hit the background and have not written any valid
value for the pass yet.
2022-11-28 21:03:07 +01:00
9bdde6ca96 Python Module: add source code and credits to project description 2022-11-28 21:03:07 +01:00
6d52975019 Cleanup: remove Cycles standalone repository lib detection
This is only needed in the Cycles repo and having it in the Blender repo
is making merging more complicated than it is helping.
2022-11-28 21:03:07 +01:00
7540842ca7 Fix T99592: Exact Boolean: Skip empty materials, add index-based option
**Empty Slot Fix**
Currently the boolean modifier transfers the default material from
meshes with no materials and empty material slots to the faces on the
base mesh. I added this in a2d59b2dac for the sake of consistency,
but the behavior is actually not useful at all. The default empty
material isn't chosen by users, it just signifies "nothing," so when
it replaces a material chosen by users, it feels like a bug.

This commit corrects that behavior by only transferring materials from
non-empty material slots. The implementation is now consistent between
exact mode of the boolean modifier and the geometry node.

**Index-Based Option**

"Index-based" is the new default material method for the boolean
modifier, to access the old behavior from before the breaking commit.

a2d59b2dac actually broke some Boolean workflows fundamentally, since
it was important to set up matching slot indices on each operand. That
isn't the cleanest workflow, and it breaks when materials change
procedurally, but historically that hasn't been a problem. The
"transfer" behavior transfers all materials except for empty slots,
but the fundamental problem is that there isn't a good way to specify
the result materials besides using the slot indices.

Even then, the transfer option is a bit more intuitive and useful for
some simpler situations, and it allows accessing the behavior that has
been in 3.2 and 3.3 for a long time, so it's also left in as an option.
The geometry node doesn't get this new option, in the hope that we'll
find a better solution in the future.

Differential Revision: https://developer.blender.org/D16187
2022-11-28 21:03:07 +01:00
ca5062071c Fix T100537: wrong depth pass for background after recent fix for gaps
Also have to write if we hit the background and have not written any valid
value for the pass yet.
2022-11-28 20:36:56 +01:00
008070e16c Python Module: add source code and credits to project description 2022-11-28 20:18:26 +01:00
1a34bbc27c Cleanup: remove Cycles standalone repository lib detection
This is only needed in the Cycles repo and having it in the Blender repo
is making merging more complicated than it is helping.
2022-11-28 20:18:26 +01:00
da363d831b Fix assert when calling transform operators in python handles
In these cases `t->spacetype` is `SPACE_EMPTY`.

Returning 0 is not problematic as this space does not support snapping
anyway.
2022-11-28 15:51:01 -03:00
bbb389589a Fix T99592: Exact Boolean: Skip empty materials, add index-based option
**Empty Slot Fix**
Currently the boolean modifier transfers the default material from
meshes with no materials and empty material slots to the faces on the
base mesh. I added this in a2d59b2dac for the sake of consistency,
but the behavior is actually not useful at all. The default empty
material isn't chosen by users, it just signifies "nothing," so when
it replaces a material chosen by users, it feels like a bug.

This commit corrects that behavior by only transferring materials from
non-empty material slots. The implementation is now consistent between
exact mode of the boolean modifier and the geometry node.

**Index-Based Option**

"Index-based" is the new default material method for the boolean
modifier, to access the old behavior from before the breaking commit.

a2d59b2dac actually broke some Boolean workflows fundamentally, since
it was important to set up matching slot indices on each operand. That
isn't the cleanest workflow, and it breaks when materials change
procedurally, but historically that hasn't been a problem. The
"transfer" behavior transfers all materials except for empty slots,
but the fundamental problem is that there isn't a good way to specify
the result materials besides using the slot indices.

Even then, the transfer option is a bit more intuitive and useful for
some simpler situations, and it allows accessing the behavior that has
been in 3.2 and 3.3 for a long time, so it's also left in as an option.
The geometry node doesn't get this new option, in the hope that we'll
find a better solution in the future.

Differential Revision: https://developer.blender.org/D16187
2022-11-28 12:42:08 -06:00
56ae4089eb GPencil: Allow interpolation to use breakdown keyframe as extremes
Actually, the interpolation can be done only between keyframes different of breakdown type,
but in some cases, this is not convenient.

Now, a new option is displayed to allow the interpolation using breakdown keyframes
as interpolation extremes.

Reviewed By: mendio, pepeland

Differential Revision: https://developer.blender.org/D16515
2022-11-28 19:32:18 +01:00
7e7c6bc468 Cleanup: Use spans and lambdas for mesh normal calculation
Makes code safer, easier to understand, and less verbose. I detected
negligible performance differences, only a slight improvement for the
normalize step where the function call overhead was probably more
of a bottleneck.

I kept `memset` instead of `.fill(float3(0))` because that gave
better performance in my tests. In the future that stage could be
parallelized, or we could make sure new arrays are allocated with
`calloc`.
2022-11-28 12:28:27 -06:00
b0bf10889b Merge branch 'blender-v3.4-release' 2022-11-28 18:26:21 +01:00
33ce83984d Fix T102801: Empty metasequence sliding away while moving
Meta strip position relies on strips within. When meta strip is empty,
update function, that would normally update it's position returns early
and this causes translaton to behave erratically.

When strip is empty, treat it as normal strip and move its start frame
as with other strip types.
2022-11-28 18:23:29 +01:00
33b3645d97 Merge branch 'blender-v3.4-release' 2022-11-28 17:11:46 +01:00
9eb1d62ba6 Fix T102638: GPencil: Modifier added on linked object restore its value on reloading the file
One line missing in code handling overrides insertions of gpencil
modifiers...

Fix also suitable for 3.3LTS.
2022-11-28 17:10:02 +01:00
Colin Basnett
c47b6978e3 Animation: Make Bake Animation operator use preview range when enabled
This patch makes the Bake Actions operator fills the Start Frame & End From with that of the Preview Range if "Use Preview Range" is enabled.

{F13973619}

Reviewed By: sybren

Differential Revision: https://developer.blender.org/D16630
2022-11-28 08:04:21 -08:00
19bb30baf6 Fix T102735: Knife tool does not work properly in perspective viewport
Use `ED_view3d_win_to_3d` to unproject the first click coords.

This is the same function used in other tools like Draw Curve.

Differential revision: https://developer.blender.org/D16617
2022-11-28 12:38:47 -03:00
5758d114c1 Dual Mesh: Avoid transferring position attribute twice
The node transferred position once as a generic attribute, and then set
the values again manually. This wastes processing during the attribute
transfer step. On a 1 million face grid, I observed roughly an 8%
improvement, from 231.5 to 217.1 ms average and 225.4 to 209.6 ms min.
2022-11-28 08:19:33 -06:00
d96859c5b1 Cleanup: Move dual mesh topology map to blenkernel
It's helpful to have these topology maps standardized and organized
a bit better so they can be optimized and considered for future caching
together. Also use a more standard name for the map for that purpose.
2022-11-28 08:19:33 -06:00
7a9fce28c0 Cleanup: Pass spans by value in cone mesh primitive
Also use more typical ordering for the arguments.
2022-11-28 08:19:33 -06:00
70041ced14 Cleanup: Remove unused mesh array variables and arguments 2022-11-28 08:19:33 -06:00
6e26d0645e Cleanup: Use spans for voxel remesh mesh data 2022-11-28 08:19:33 -06:00
0940719b5a Line Art: Use local spans for mesh arrays
Avoid accessing arrays from the mesh for every element and add safety
by using Span instead of raw pointers. Similar to previous commits.
2022-11-28 08:19:33 -06:00
baba5d2214 Multires: Avoid retriving mesh arrays for every element
Based on the surrounding code this probably wasn't a
bottleneck, but it's nice to avoid in principle anyway.
2022-11-28 08:19:33 -06:00
653e3e2689 Subdiv: Avoid repeatedly accessing mesh arrays
Fix a performance regression from 05952aa94d by storing pointers
to mesh arrays locally in the subdiv foreach context. In a simple test
of a 1 million face grid, this improved performance by 5% (from 0.31
to 0.295 seconds).
2022-11-28 08:19:33 -06:00
bcabd04e32 Mesh: Avoid retrieving edge and loop arrays repeatedly
A utility function retrieved mesh arrays for every element after
05952aa94d which can be easily avoided. This was used when
building the GPU indices for sculpt mode drawing. In my tests this
saves 0.1ms per PBVH node. There may be very slight improvements
in line art and shrinkwrap as well.
2022-11-28 08:19:33 -06:00
a059b1b0f1 Merge branch 'blender-v3.4-release' 2022-11-28 12:52:32 +01:00
99c5146e9a Fix T101433: crash when deleting hierarchy referenced by Collection Info node
The issue was that geometry nodes was not reevaluated after changing the
content of the collection. That resulted in the other object still having a
reference to the deleted object, which resulted in a crash when it tried to
access it.

Adding the `ID_RECALC_GEOMETRY` tag indicates that the content of the
collection has changed and will trigger geometry nodes setups that depend
on the collection to update.
2022-11-28 12:51:10 +01:00
4ed649352f 3D Texturing: Fix seam bleeding.
{F13294314}
# Process

In the pixel extraction process a larger domain will be extracted then the input mesh.
The borders of uv islands are extended with connected geometry of the input mesh.
The extended mesh is then fed into the pixel extraction process.
A mask is used to limit the extraction so UV islands will not overlap.

Input UV islands.
{F13206401}

Extended UV Island (only one showing).
{F13288764}

This patch doesn't include fixing uv seams at non-manifold edges (like suzannes eyes) as that
would require a different approach (edge extending or pixel copy-ing). The later has already been
implemented in D14702, but should be revisited to only use do the non-manifold edge fixing.

This patch supports fixing UV seams across UDIM textures.
There might be an issue when using a single texture on multiple uv maps.

Reviewed By: brecht, joeedh, JulienKaspar

Maniphest Tasks: T97352

Differential Revision: https://developer.blender.org/D14970
2022-11-28 08:32:06 +01:00
c02ec74405 Cleanup: format 2022-11-28 13:17:59 +13:00
143e74c0b8 Fix (unreported) uv unwrap selected was splitting selection
Add support for `pin_unselected` in new UV Packing API.

Regression introduced by API change in rBe3075f3cf7ce.

Duplicate change to rB0ce18561bc82 in master.
2022-11-28 13:14:42 +13:00
e010890e82 Cleanup: format 2022-11-28 13:14:27 +13:00
95003c99d9 GPU: Change inheritance of depth write and default values
This new inheritance behavior is more beneficial for the metal Backend.
Also change the default depth write behavior of shaders to be unchanged.
This makes fragment shader depth amendment more explicit.

This also add the missing depth_write for metal kernels.
2022-11-27 23:58:55 +01:00
d961119563 Python API Docs: document when fields use mathutils types.
When accessing certain structure fields from Python, they return
mathutils types instead of generic arrays (this is based on subtype).

This exposes this information in the Python API documentation.

Differential Revision: https://developer.blender.org/D16626
2022-11-28 00:33:41 +02:00
57a20b6d52 DRW: Add missing depth_write to certain shader create info
These are required by the Metal backend.
2022-11-27 22:58:10 +01:00
5b4efaeeb3 Merge branch 'blender-v3.4-release' 2022-11-27 21:42:37 +01:00
3a65d2f591 Fix T101211: Image jitters when scaling by large values
Issue was caused by imprecise math due to using float numbers.
Use double instead.

No negative performance impact was observed.

Reviewed By: jbakker

Differential Revision: https://developer.blender.org/D16517
2022-11-27 21:33:05 +01:00
5a504a0d9d Fix T102663: Meta strips from older versions are broken
Meta strip range was adjusted in versioning because of previous issues
by function `version_fix_seq_meta_range`. After `speed_factor` property
was added, this changed how function works and result was incorrect
function due to uninitialized property value.

Running `version_fix_seq_meta_range` after `seq_speed_factor_set` fixes
this issue.

Reviewed By: sergey

Differential Revision: https://developer.blender.org/D16606
2022-11-27 21:31:38 +01:00
ea384fc096 Merge branch 'blender-v3.4-release' 2022-11-27 14:41:36 +01:00
6cd441fdac Draw: GPU Subdivision: Add missing case in get_shader_code 2022-11-27 14:40:47 +01:00
41733a9c08 Fix T102773 & T102777: Regressions in GPU Subdivision
Introduced in D16420.
SHADER_BUFFER_TRIS and SHADER_BUFFER_TRIS_MULTIPLE_MATERIALS flags were accidentally swapped.
2022-11-27 14:40:00 +01:00
3ccf4a3944 Merge branch 'blender-v3.4-release' 2022-11-27 12:41:42 +01:00
b0bcdcdbef Fix: missing greasepencil hook modifier relationship lines
Overlay relationship lines were missing between the object having the
modifier and the target object.

To make this consistent with other objects types, now draw relationship
lines for greasepencil and hooks now, too.

Spotted while looking into T102741.

Maniphest Tasks: T102741

Differential Revision: https://developer.blender.org/D16609
2022-11-27 12:24:48 +01:00
179008f57a Fix T102685: Grease Pencil brush cursors missing on file open
Brush cursors were missing when opening a file saved in sculpt/vertex-/
weightpaint mode.

Since we dont do a full modeswitch on file load in `ED_editors_init` for
grease pencil since rBde994d6b7b1c, we were missing the brush cursor
toggling [`ED_gpencil_toggle_brush_cursor`] normally done in
`ED_gpencil_setup_modes`.

This is now explicitly added for any greasepencil paintmode (in case
object is active).

Maniphest Tasks: T102685

Differential Revision: https://developer.blender.org/D16603
2022-11-27 12:23:01 +01:00
c935a06edb Cleanup: correct comments in ED_editors_init
These comments were from rBcc7460eaa491 / rBde994d6b7b1c /
rB7037ff920447 but ended up in a confusing state.
Moved into the right places and reworded appropriately.

Spotted while looking into T102685.

Maniphest Tasks: T102685

Differential Revision: https://developer.blender.org/D16601
2022-11-27 12:22:42 +01:00
b0810f788c Fix T102650: Wrong cursor in greasepencil fill operation
Caused by {rBbf8507414889}

Above comit set the wrong cursors (WM_CURSOR_DEFAULT).
When releasing MMB, we need to return to WM_CURSOR_PAINT_BRUSH.
When exiting, we need to return to WM_CURSOR_DOT (since this is the
default cursor for the tool, see `_defs_gpencil_paint` >
`generate_from_brushes`)

Maniphest Tasks: T102650

Differential Revision: https://developer.blender.org/D16591
2022-11-27 12:21:22 +01:00
ee56c46c15 Fix: greasepencil selection mask picking points not working
When in greasepencil sculpt-/vertexpaint mode and using selection
masking, picking points wasnt working correctly.

So regular gpencil.select (without modifier keys) was not enabled for the
keymap. This only really makes sense for RCS (right click select) atm
(and not using RC fallback tools - which I dont think are present in
these modes anyways). With RCS, this can be supported and afaict, this
does not cause conflicts.

NOTE: prior to D16576, one could use the ALT modifier key (this
combination was actually in the keymap) -- but it should select the
entire stroke, which is handled in D16576.

Differential Revision: https://developer.blender.org/D16577
2022-11-27 12:21:02 +01:00
5d481d9e55 Fix: greasepencil selection of entire_strokes not working
Caused by rB85f90ed6fd88.

Above commit made sure whole strokes are selected when the
GP_SELECTMODE_STROKE is used in different modes, but ignored the fact
that this can also already be set by the entire_strokes select operator
property.

This is now corrected.

Differential Revision: https://developer.blender.org/D16576
2022-11-27 12:20:06 +01:00
Iliya Katueshenock
9fa4ceb340 Geometry Nodes: Change Collection Info output socket name to Instances
As described in T101948, this commit changes socket name to be more
consistent with other nodes that generate instances output.

Differential Revision: https://developer.blender.org/D16394
2022-11-26 18:11:01 -06:00
Iliya Katueshenock
42485b01d2 Geometry Nodes: Rename Transform node to Transform Geometry
Change name to make navigation easier for beginner users. This should
more clearly hint at the use of this node to change the full geometry,
and not work with fields, and makes the name more consistent.

Differential Revision: https://developer.blender.org/D16396
2022-11-26 18:05:41 -06:00
Iliya Katueshenock
beeeb6da30 Cleanup: Integer types, references in geometry node image texture node
While implementing T102289, I noticed that this node has
several solutions that are different from other, newer nodes.
 - Explicitly set default values
 - Use references
 - Reduce the size of the node settings structure

Differential Revision: https://developer.blender.org/D16548
2022-11-26 18:00:47 -06:00
3f5dfbf681 Geometry Nodes: Modify existing mesh in split edges node
Instead of creating a new mesh from scratch, modify an existing mesh.
This allows us to keep derived caches for triangulation and bounds
alive more easily, and allows keeping materials and non-generic
attributes like vertex groups alive on the mesh.

It also has other performance benefits, since face and face corner
attributes aren't affected at all, and because of reduced overhead
from not allocating a new mesh.

Updating edge attributes is a bit more complicated now, since we
have to completely replace the arrays but keep the existing attribute
IDs around. The new mesh update tag is also slightly too specific IMO.
But I think both of those things will improve in the future because
of existing plans for further refactoring these areas:
- New attribute storage that gives pointer stability
- Further use and granularity of mesh update tagging that will
  make the correct API more clear

Fixes T102711

Differential Revision: https://developer.blender.org/D16615
2022-11-26 17:54:05 -06:00
3a41e0f611 Tests: Automated geometry nodes benchmark
Add a script for a very simple object evaluation benchmark.
There could be more advanced ways of measuring the time
per-node or per modifier, but this just loads the file, tags
the active object for a reevaluation, and times how long
that takes.

Differential Revision: https://developer.blender.org/D16604
2022-11-26 17:15:55 -06:00
828525b268 Fix: MSVC build error without TBB
windows.h once more providing min/max macro's when you least want them.
2022-11-26 11:44:08 -07:00
4ecc7cf14a Cleanup: Move interface_intern.hh
The entire interface directory is now compiled as C++ files.
2022-11-26 10:12:58 -06:00
e47c75aa6e Cleanup: Move interface eyedroppers directory to C++ 2022-11-26 10:12:58 -06:00
136ea84d9a Cleanup: Move interface_handlers.c to C++ 2022-11-26 10:12:58 -06:00
162f0dcb2f Cleanup: Move six more interface files to C++ 2022-11-26 10:12:58 -06:00
1aff91b1a7 GPencil: Add Vertex Opacity Overlay parameter in Sculpt
This option was missing in overlay panel.
2022-11-26 15:39:59 +01:00
b43bdd8ba2 Fix T102751: missing tree update with muted nodes
This was accidentally caused by removing too much code in
{rBb4c3ea264439158df70e837e20f8dd9ec548de2d}.
2022-11-26 13:46:39 +01:00
86ade3df56 Nodes: move node registration to nodes module
The main goal here is to move towards more self contained node
definitions. Previously, one would have to change `blenkernel` to
add a new node which is not necessary anymore. There is no need
for all these register functions to "leak out" of the nodes module.

Differential Revision: https://developer.blender.org/D16612
2022-11-26 13:20:18 +01:00
8d269a2488 PyDocs: Fix incorrect data type of bmesh.types.BMFaceSeq.new
The API documentation of [[ https://docs.blender.org/api/current/bmesh.types.html?highlight=faces#bmesh.types.BMFaceSeq.new | bmesh.types.BMFaceSeq.new ]] indicates that the argument is only `BMVert`.
But the correct one is sequence of `BMVert`.
This patch fixes this mismatch.

Contributed by @Nutti

Differential Revision: https://developer.blender.org/D15668
2022-11-25 19:55:04 -05:00
ed6e1381dc Cleanup: Avoid using macros to refer to theme global variables
Prefer slightly more verbose code to the use of macros where
they aren't really necessary and just add indirection.
2022-11-25 17:11:44 -06:00
248ee6270b Cleanup: Remove unnecessary includes 2022-11-25 17:10:28 -06:00
afd16c487d Cleanup: Move four interface files to C++ 2022-11-25 17:09:47 -06:00
4029cdee7b Merge branch 'blender-v3.4-release' 2022-11-25 15:28:48 -06:00
680a0fb23b Fix T102752: Missing default radius when joining point clouds
Point clouds are meant to use a default radius of 0.01 when there is no
radius attribute. The curve to points node can create curves without a
radius attribute. This affects joining and the realize instances node.

Similar to 30f244d96f.
2022-11-25 15:25:37 -06:00
ed8fee16ac Fix: Hide "Squeeze Value" node from node search
Based on discussion in D10891, this node isn't meant to be exposed and
may be removed in the future. The fact that it was exposed in search
menus was a mistake from the implementation of link-drag-search
and bdb5754147.

I explicitly removed the link drag search implementation, and added
(Legacy) to the node name which hides it from the add node search.
2022-11-25 14:59:27 -06:00
4a0e19e608 Cleanup: Group deprecated mesh DNA fields, improve comments 2022-11-25 12:54:22 -06:00
5ca6965273 Merge branch 'blender-v3.4-release' 2022-11-25 18:54:49 +01:00
0147e09e0b Python Module: add project description for pypi 2022-11-25 18:54:00 +01:00
f07b09da27 Cycles: Improve oneAPI backend support for non-Intel platforms 2022-11-25 17:46:59 +01:00
f83aa1ae08 Fix T102764: Slow change of active material slot
The issue is caused by the combination of the following factors:

- There is a driver from custom property to the subdivision surface
  modifier.
- Active material index tags the ID for the copy-on-write update.

Dependency graph currently does not fully distinguish between
copy-on-write tag and properties-update tag, so the copy-on-write tag
makes the dependency graph believe that it is property which actually
affects evaluation has been changed.

The simple solution is to treat the active material slot index as an
interface data which does not need to trigger copy-on-write tag.

The possible downside of this solution is that if someone has a driver
from this property the driver will stop working. Whether there is such
a real-life setup or not is not clear. Is not something advisable to do
anyway.

Possible alternative would be to introduce more granularity into the
way how property tagging is done. This is something that would be nice
to implement eventually, but it is a much bigger refactor.

Differential Revision: https://developer.blender.org/D16613
2022-11-25 17:44:54 +01:00
d1c21d5673 Fix T102470: Make material LineArt properties animatable.
MaterialLineArt didn't have a path func, now corrected.
2022-11-25 23:18:41 +08:00
b5ca43b2a0 Fix T102470: Make material LineArt properties animatable.
MaterialLineArt didn't have a path func, now corrected.
2022-11-25 23:15:39 +08:00
994e3c6ac5 Clarify depsgraph API usage in the libraries code
Basically copy the information from the commit message of the
03e2f11d48 directly to the code.

This makes the information easier to find when working on the
code.
2022-11-25 15:25:55 +01:00
dad0f37c1e Fix T101824: Line art flickers when light object has scaling.
Line art doesn't expect light or camera objects to have scaling.
2022-11-25 22:21:26 +08:00
118afe1de3 Fix T101824: Line art flickers when light object has scaling.
Line art doesn't expect light or camera objects to have scaling.
2022-11-25 22:00:21 +08:00
ae6e35279f Clarify comment about ID_RECALC_COPY_ON_WRITE
The copy-on-write is really an implementation detail of the
dependency graph. While there are still cases where there is
no better tag to be used, the ID_RECALC_COPY_ON_WRITE should
not be used in combination with a dedicated tag.

For example if location of object changes the proper tag is
`ID_RECALC_TRANSFORM`. Tagging with `ID_RECALC_TRANSFORM |
ID_RECALC_COPY_ON_WRITE` will seemingly work, but this is
not an intended usage.
2022-11-25 14:55:50 +01:00
043673ca70 Cleanup: Alembic, deduplicate CacheObjectPath creation
No functionnal changes.
2022-11-25 14:37:48 +01:00
3cf803cf3c Cleanup: Alembic, use MEM_cnew
Avoids extra cast. No functionnal change.
2022-11-25 14:27:18 +01:00
6422f75088 Merge branch 'blender-v3.4-release' 2022-11-25 12:44:05 +01:00
b918c079da Fix T101402: EEVEE: Wrong Volume transforms
Ensure VolumeUniformPool uses is always incremented when retrieving a buffer in alloc().
Otherwise the same buffer will be retrieved for more than one object when incrementing the pool size.

Reviewed By: fclem

Maniphest Tasks: T101402

Differential Revision: https://developer.blender.org/D16607
2022-11-25 12:43:30 +01:00
6bc3311410 Fix: Missing node warning when compositor is enabled
If the compositor is enabled or disabled, the node warnings for
unsupported nodes is not updated because of a missing redraw. This patch
adds that missing redraw in order to make the change immediate.
2022-11-25 13:19:13 +02:00
60ad5f49fa Cleanup: move C++ declarations to separate .hh header 2022-11-25 12:18:10 +01:00
826535979a Nodes: add non-const utility to find socket by identifier
This does the same as the corresponding const method.
2022-11-25 12:11:13 +01:00
32690cafd1 Fix: Missing compositor warning for Render Layers
The Render Layers node didn't draw a warning in the viewport when an
unsupported pass is used. This patch adds that warning.
2022-11-25 12:59:50 +02:00
64c26d2862 Merge branch 'blender-v3.4-release' 2022-11-25 11:45:01 +01:00
848dff1e4c Fix: assert when converting between types in geometry nodes
This bug only affected debug builds.
2022-11-25 11:44:17 +01:00
b9c358392d BLI: Fix error in vector library and add more test for operators
The operator was wrongly returning a reference to local temp variable.

Add test for all uncovered operators.
2022-11-25 11:28:04 +01:00
0ce18561bc Fix (unreported) uv unwrap selected was splitting selection
Add support for `pin_unselected` in new UV Packing API.

Regression introduced by API change in rBe3075f3cf7ce.
2022-11-25 15:52:04 +13:00
851906744e Merge branch 'blender-v3.4-release' 2022-11-24 15:14:12 -06:00
c4cfa1b23e Fix T102611: Unable to change file output node sockets from python
Similar to 84c66fe9db
2022-11-24 15:11:03 -06:00
0710ec485e Cleanup: Remove unused IMB tile cache code (part 2)
Missed in the first commit[1].

Initially it was reported that the `flags` parameter was unused on
`imb_cache_filename` but it turns out another swath of code was unused
related to that same function. Clean this up now too.

[1] 38573d515e
2022-11-24 12:41:05 -08:00
49129180b2 Merge branch 'blender-v3.4-release' 2022-11-24 17:30:54 -03:00
cdc1c60f5b Fix T102466: push/pull doesn't work if applied to many vertices
The assignment of member`distance` was missing in rB2d4ec9049744.
2022-11-24 17:29:26 -03:00
58c8c4fde3 Animation: Improve performance of Bake Action operator
This dramatically improves baking performance by batch-adding keyframes
instead of adding them individually, reducing unnecessary overhead.

Testing indicates an approximate 4x performance uplift.

Reviewed By: sybren, RiggingDojo

Differential Revision: https://developer.blender.org/D8808
2022-11-24 11:26:17 -08:00
81754a0fc5 Cleanup: remove else after return. 2022-11-24 09:56:05 -08:00
412642865d Cleanup: Resolve a warning for the ambiguity on the parenthesis in oneAPI code
No functional changes.
2022-11-24 18:05:02 +01:00
14a0fb0cc6 Merge branch 'blender-v3.4-release' 2022-11-24 09:02:23 -08:00
959f3cd645 Sculpt: fix T102436: missing null pointer check in automasking code 2022-11-24 08:55:10 -08:00
Sebastian Herholz
008cc625aa Fix T102250: Cycles path guiding issue with equiangular sampling
The wrong guiding distribution was used when direct and indirect light
scattering happened at different locations. Now use a different distribution
for each location.

Recording is not quite correct since OpenPGL does not support spliting the
path like this, instead recording at the start of the volume ray. In practice
this seems to make little difference.

Differential Revision: https://developer.blender.org/D16448
2022-11-24 17:12:38 +01:00
de27925aea Fix (unreported) inconsistent name_map during file reading.
Swapping some ID lists between Mains must invalidate the name_map cache.

Note that in theory, at least WM type could be ignored by name_map
cache, since it is a singleton. However, don't think it's worth adding
extra complication here, for really marginal benefits. The overhead of
rebuilding the name cache here is extremly small.

For some reason, this issue did not show so far in master, only appeared
in some branch work on improving (in)direct status of linked IDs... Go
figure.
2022-11-24 17:08:18 +01:00
38c4f40159 Fix Cycles OSL issue with constant folding after recent fix
Constant folded values from linked nodes should override the OSL initializer.

Ref T102450
2022-11-24 16:59:58 +01:00
d6d5089c65 Metal: Fix a warning and compilation errors
These were oversight when developping without testing on MacOS.
2022-11-24 16:57:46 +01:00
20c1ce3d9b BLI: Make scalar vector constructor more generic
This makes it possible to use any type of scalar to init a vector and
reduce code duplication.
2022-11-24 16:16:42 +01:00
f47daa7ec9 Merge branch 'blender-v3.4-release' 2022-11-24 15:33:25 +01:00
6e37f14489 Windows: Run blender-launcher.exe instead of blender.exe
With this change Blender, delivered via the Microsoft store, will launch without the console window flashing.

Ref T88613

Differential Revision: https://developer.blender.org/D16589
2022-11-24 15:32:22 +01:00
Christoph Lendenfeld
bb665ef8d2 Merge branch 'blender-v3.4-release' 2022-11-24 15:20:30 +01:00
Christoph Lendenfeld
705fe08b61 Fix T99400: "Jump to Keyframe" preserves subframe offset
Previously when using the "Jump To Keyframe" operator
in conjunction with subframes, the decimal part would be kept.
Meaning that it wouldn't jump exactly to the frame.
This fix also makes it so it is possible to jump to keyframes
that are on subframes.

Reviewed by: Sybren
Differential Revision: https://developer.blender.org/D16595
2022-11-24 15:19:48 +01:00
1b7b996e16 Merge branch 'blender-v3.4-release' 2022-11-24 13:36:48 +01:00
17e562311f Draw: Use 32-bit floats for custom color attributes 2022-11-24 13:36:11 +01:00
2a53e0f437 FIX T102076: Add support for int attributes to draw manager.
Add int attributes interpolation support for GPU subdivision.
Ensure cached shaders match their intended defines.
(The defines parameter was ignored when requesting a second time the same shader with different defines)
De-duplicate the extract_attr_init code for subdiv/non-subdiv.

Reviewed By: jbakker, fclem

Maniphest Tasks: T102076

Differential Revision: https://developer.blender.org/D16420
2022-11-24 13:29:53 +01:00
5f626ac331 Cleanup: use more concise function names in function nodes
This is the same naming convention that's used for geometry nodes.
2022-11-24 12:49:17 +01:00
369914e7b1 Liblink: Add test over direct vs indirect link status.
Some checks are currently commented out, since Blender will never 'clear'
the directly linked status of an ID once it has been used by local data.
2022-11-24 10:52:27 +01:00
bbf09eb59c Merge branch 'blender-v3.4-release' 2022-11-24 10:15:36 +01:00
b3daf61ddf Fix T101020: Cycles Add Performance Preset is broken 2022-11-24 10:13:57 +01:00
2dcdfab94c Realtime Compositor: Warn about unsupported MacOS
This patch warns the user that MacOS is not supported for the viewport
compositor in the shading panel.

See T102353.
2022-11-24 09:25:44 +02:00
38573d515e Cleanup: Remove unused IMB tile cache code
This removes the unused code for the IBM tile cache APIs.  These have
been unused for as far back as I could manage to search.

Since TIFF was used for the cached images, this removal will allow for
an easier review when it comes time to move TIFF to OIIO as part of
T101413.

Differential Revision: https://developer.blender.org/D16587
2022-11-23 19:31:10 -08:00
f4e1f62c62 Merge branch 'blender-v3.4-release' 2022-11-23 19:35:39 +01:00
dc1ed9c1aa Cycles: add OSL support for hiding input socket value
When either initializing with a non-constant value, or using the standard
[[ string widget = "null" ]] metadata. This can be used for inputs like
normals and texture coordinates, where you don't want to default to a
constant value.

In previous OSL versions the input value was automatically ignore when it
was left unchanged for such inputs. However that's no longer the case in
the latest version, breaking existing nodes. There is no good entirely
backwards compatible fix, but I believe the new behavior is better and will
keep most existing cases working.

Fix T102450: OSL node with normal input not working
2022-11-23 18:59:52 +01:00
584089879c BLI: Follow up and fix recent span slicing change
a5e7657cee didn't account for slices of zero sizes, and the asserts
were slightly incorrect otherwise. Also, the change didn't apply to
`Span`, only `MutableSpan`, which was a mistake. This also adds "safe"
methods to `IndexMask`, and switches function calls where necessary.
2022-11-23 11:36:06 -06:00
38cf48f62b Fix: Missing caches in curves bounds evaluation 2022-11-23 11:36:06 -06:00
50aad904b3 Merge branch 'blender-v3.4-release' 2022-11-23 14:15:22 -03:00
962e221cd3 Fix T102720: Snapping in 3dViewport affects Graph, Action and NLA editors
These editors have their own "Auto-Snap" activation option.

So ignore the option in the 3D View in these cases.

The generic incremental snap function doesn't seem really useful in these cases.

If an incremental snap needs to be implemented, this should be a new option of `eAnimEdit_AutoSnap`.
2022-11-23 14:06:39 -03:00
f13160d188 Cleanup: quiet deprecation warnings
This fixes these warnings: P3340.
2022-11-23 17:15:33 +01:00
583f19d692 Merge branch 'blender-v3.4-release' 2022-11-23 17:03:17 +01:00
a87d78860b Fix T102693: adding shader node group to geometry nodes crashes
This is not supported currently, so just cancelling the operation
solves it. This might be supported in the future.
2022-11-23 17:01:49 +01:00
c1eeb38f7c Cleanup: Move poly normal calculation to mesh_normals.cc 2022-11-23 09:49:18 -06:00
c3d6f5ecf3 Merge branch 'blender-v3.4-release' 2022-11-23 16:39:09 +01:00
ea57c011de Fix T102579: object mode wireframe overlay not shown on geometry node primitive
After a recent refactor in b247588dc0, object mode would not show wireframe
edges that do not exist in the original mesh. Now only hide such edges while in
edit mode, where they would otherwise look as if they can be selected.

Before the refactor, edit and paint modes would sometimes show wireframes and
sometimes not, depending on the modifier stack in unpredictable ways.
2022-11-23 16:19:00 +01:00
737d363e02 Cleanup: remove unused node type
This wasn't used for backwards compatibility, because Blender does not
read from the `nodetype` anywhere. It also wasn't used for forward
compatibility, because it was not initialized for new node groups.
2022-11-23 16:15:25 +01:00
fe1b8b671a Fix part of T102696: Cycles Python error with macOS version 13.0.1 2022-11-23 16:03:19 +01:00
687272c409 Cleanup: compiler warnings 2022-11-23 16:03:19 +01:00
460f7ec7aa Windows: Run blender-launcher.exe instead of blender.exe
With this change Blender, delivered via the Microsoft store, will launch without the console window flashing.

Ref T88613

Differential Revision: https://developer.blender.org/D16589
2022-11-23 15:14:13 +01:00
Jeroen Bakker
a819523dff Vulkan: Add VK memory allocator 3.0.1 to extern.
Vulkan doesn't have a memory allocator builtin. The application should
provide the memory allocator at runtime. Vulkan Memory Allocator is a
widely used implementation.

Vulkan Memory Allocator is a header only implementation, but the using
application should compile a part in a CPP compile unit. The file
`vk_mem_alloc_impl.cc` and `extern_vulkan_memory_allocator` library
is therefore introduced.

Reviewed By: fclem

Differential Revision: https://developer.blender.org/D16572
2022-11-23 14:42:27 +01:00
68a450cbe4 Cleanup: Remove unused parameter in node draw 2022-11-23 15:06:40 +02:00
aa0c2c0f47 Cleanup: move some data from bNodeTree to run-time data
No functional changes are expected.
2022-11-23 14:05:30 +01:00
4f02817367 Nodes: remove bNodeTree->interface_type
This is not used for anything in practice currently. The original intention
was probably to generate different socket subtypes, but that is solved
differently now (e.g. using `NodeSocketFloatDistance`). It's possible
that an addon tried to use this but it's rather unlikely.

Differential Revision: https://developer.blender.org/D13188
2022-11-23 13:49:07 +01:00
247d75d2b1 Realtime Compositor: Warn about unsupported setups
This patch warns the user that the compositor setup is not fully
supported when an unsupported node is used. The warning is displayed as
an engine warning overlay and in the node header itself.

See T102353.

Differential Revision: https://developer.blender.org/D16508

Reviewed By: Clement Foucault
2022-11-23 14:35:26 +02:00
6396d29779 Merge branch 'blender-v3.4-release' 2022-11-23 13:25:12 +01:00
ff9606ddc4 Fix use-after-free of asset catalog data in node add menu
(Probably requires ASan for a reliable crash.)

Steps to reproduce were:
* Enter Geometry Nodes Workspace
* Press "New" button in the geometry nodes editor header
* Right-click the data-block selector -> "Mark as Asset"
* Change 3D View to Asset Browser
* Create a catalog
* Drag new Geometry Nodes asset into the catalog
* Save the file
* Press Shift+A in the geometry nodes editor

There was a general issue here with keeping catalog pointers around
during the add menu building. The way it does things, catalogs may be
reloaded in between.
Since the Current File asset library isn't loaded in a separate thread,
the use-after-free would always happen in between. For other libraries
it could still happen, but apparently didn't by chance.
2022-11-23 13:23:21 +01:00
1c00b2ef70 Cleanup: move paint_cursor.c and paint_image_proj.c to C++
This makes it easier to use c++ when improving the internal node api.
2022-11-23 12:56:34 +01:00
106277be43 Merge branch 'blender-v3.4-release' 2022-11-23 12:54:15 +01:00
3250ab31cd Cleanup: Remove debug print
Was introduced in 0e6d893d07.
2022-11-23 12:53:35 +01:00
7d44676b5f Realtime Compositor: Disable on MacOS
This patch disables the realtime compositor on MacOS until Metal is
supported. This is because MacOS doesn't support the necessary GPU
features to make it work.

An engine error overlay is displayed if it is enabled and the option
itself is greyed out.

See T102353.

Differential Revision: https://developer.blender.org/D16510

Reviewed By: Clement Foucault
2022-11-23 13:34:31 +02:00
11275b7363 Realtime Compositor: Extend option to enable compositor
This patch turns the checkbox option to enable the viewport compositor
into a 3-option enum that allows:

- Disabled.
- Enabled.
- Enabled only in camera view.

See T102353.

Differential Revision: https://developer.blender.org/D16509

Reviewed By: Clement Foucault
2022-11-23 13:27:47 +02:00
80249ce6e4 Asset Browser: Allow changing active catalog from Python
The active catalog ID (UUID) was a read only property. From a studio I
got the request to make this editable, so their pipeline tooling can
make certain assets visible.

Differential Revision: https://developer.blender.org/D16356

Reviewed by: Sybren Stüvel
2022-11-23 12:05:16 +01:00
e0c5ff87b7 Realtime Compositor: Implement Track Position node
This patch implements the Track Position node for the realtime
compositor.

Differential Revision: https://developer.blender.org/D16387

Reviewed By: Clement Foucault
2022-11-23 12:56:24 +02:00
571f373155 UI: Don't render missing linked material previews, avoids UI freezing
Opening the material selector after reloading files could cause long UI
freezes, because some linked in materials don't have the preview stored
in the source file. So Blender would keep rerendering it after every
file load, which may involve compiling OpenGL shaders, which again
freezes the UI typically. This was reported as quite an issue for the
Heist Production by the Blender Studio.

Don't render these missing material previews from linked data-blocks
anymore.

Differential Revision: https://developer.blender.org/D16538

Reviewed by: Brecht Van Lommel, Jeroen Bakker
2022-11-23 11:39:53 +01:00
c464fd724b Fix T102697: Gpencil Subdiv modifier level increased
The old hard limit was 5, but now it's possible set to a max
value of 16. UI limit remains to 5.

This extreme value is only used in some corner case, but it
was a request by some artists.

Warning: Using very high values could produce a long calculation time, especially in strokes with a high density of points.
2022-11-23 11:23:38 +01:00
356373ff7a Cleanup: move some data from bNodeSocket to run-time data
No functional changes are expected.
2022-11-23 10:42:17 +01:00
5938e97a24 Merge branch 'blender-v3.4-release' 2022-11-23 10:36:11 +01:00
0d73d5c1a2 Fix frozen image editor when Cycles compiles kernels
It is possible that the image editor redraw happens prior to the
"Loading render kernels" status is reported from status but after
the display driver is created. This will make the image editor to
wait on the scene mutex to update the display pass in the film.
If it happens to be that the kernels are actually to be compiled
then the Blender interface appears to be completely frozen, without
any information line in the image editor.

This change makes it so the amount of time the scene mutex is held
during the kernel compilation is minimal.

It is a bit unideal to unlock and re-lock the scene mutex in the
middle of update, while nested reset mutex is held, but this is
already what is needed for the OptiX denoiser optimization some
lines below. We can probably reduce the lifetime of some locks,
avoiding such potential out-of-order re-locking. Doing so is
outside of the scope of this patch.

The scene update only happens from the single place in the session,
which makes it easy to ensure the kernels are loaded prior the rest
of the scene update.

Not only this change makes it so that the "Loading render kernels"
status appears in the image editor, but also allows to pan and zoom
in the image editor, potentially allowing artists to re-adjust their
point of interest.

Differential Revision: https://developer.blender.org/D16581
2022-11-23 10:23:05 +01:00
01e479b790 Cleanup: simplify removing asset code
Differential Revision: https://developer.blender.org/D16570
2022-11-23 10:04:24 +01:00
63ae0939ed Merge branch 'blender-v3.4-release' 2022-11-23 10:22:42 +09:00
db28a8b3d1 Fix T102322: Crash on exiting VR session when using DirectX backend
This prevents Blender from crashing with an access violation when
stopping a VR session using the DirectX backend. The issue occurred for
any headset on Windows+Nvidia when using the SteamVR runtime and thus
affected a large number of users.

The workaround presented here is to simply skip unregistering the
shared resources on exit, as either of the calls to
`wglDXUnregisterObjectNV()` or `wglDXCloseDeviceNV()` will result in an
access violation. While not ideal, this avoids the crash and doesn't
present any issues when starting a new VR session.

Reviewed By: Severin

Differential Revision: https://developer.blender.org/D16569
2022-11-23 10:21:17 +09:00
cdcbf05ea8 BLI: Make Report Missing Files display message when no files are missing
Before this, if there were no missing files, the operator would run
successfully but there would be no user feedback at all, making the
user wonder if the operator was even run.

Reviewed By: brecht

Differential Revision: https://developer.blender.org/D16585
2022-11-22 16:48:51 -08:00
2cb6b0b4eb Merge branch 'blender-v3.4-release' 2022-11-22 15:52:33 -06:00
41ae2c6438 Fix T102700: Viewer node missing check for empty geometry component
`GeometrySet::has()` can return an empty component. It's more convenient
if it doesn't, since other code rarely wants to access an empty component.

The alternative would be adding an `is_empty()` check in the lazy function
for the viewer node, that would work fine too, for this case.

Differential Revision: https://developer.blender.org/D16584
2022-11-22 15:40:42 -06:00
0e6d893d07 Fix: Assert, invisible instances with mixed instancing types
When combining the internal geometry component instancing (used when
the original object type doesn't match the evaluated data type) with
the "vertex dupli" instancing could cause the fix from e508de0417
to fail, because the subsequent fix from 864af51d6a popped from the
"instance generator type" stack even when there was nothing added to it
(for geometry instancing).
2022-11-22 15:38:19 -06:00
e3567aad0a Merge branch 'blender-v3.4-release' 2022-11-22 12:49:48 -08:00
af8a449ca5 blender-v3.4-release: fix more merge discrepencies in pbvh.c 2022-11-22 12:48:39 -08:00
0f3b3ee679 Merge branch 'blender-v3.4-release' 2022-11-22 12:48:29 -08:00
f5e99abb60 blender-v3.4-release: Rearrange function order to fix merging
BKE_pbvh_sync_visibility_from_verts was in the wrong spot
relative to master, was leading to very nasty merge errors.
2022-11-22 12:45:09 -08:00
40ac3776db Merge branch 'blender-v3.4-release' 2022-11-22 12:41:54 -08:00
acf8f6220d Sculpt: Fix box face sets not working for multires 2022-11-22 12:30:36 -08:00
df788ecfd9 Sculpt: Standardize face set undo steps, optimize memory usage
Currently the face set of every single face is saved for every sculpt undo step.
When only changing the face sets of a small section of the mesh, this can be quite
wasteful. It also makes face sets a special case compare to all other sculpt undo step
types, which makes the whole system more complex and harder to improve.

Fixes T101203.

Reviewed By: Hans Goudey
Differential Revision: https://developer.blender.org/D16224
Ref D16224
2022-11-22 12:14:14 -08:00
a777c09d5f Sculpt: Standardize face set undo steps, optimize memory usage
Currently the face set of every single face is saved for every sculpt undo step.
When only changing the face sets of a small section of the mesh, this can be quite
wasteful. It also makes face sets a special case compare to all other sculpt undo step
types, which makes the whole system more complex and harder to improve.

Fixes T101203.

Reviewed By: Hans Goudey
Differential Revision: https://developer.blender.org/D16224
Ref D16224
2022-11-22 12:13:33 -08:00
41f58fadae Cleanup: Decrease variable scope, change names in BMesh layer handling 2022-11-22 14:03:39 -06:00
aa1a51ff9f Merge branch 'blender-v3.4-release' 2022-11-22 13:52:30 -06:00
3f294a37f5 Fix: Use after free when removing attribute
We currently check multiple dynamic attribute providers for the
attribute ID, even after it has been removed (which can free the name).
This was used as a simple way to remove multiple attributes with the
same name (dealing with name collisions). However, that doesn't happen
in practice at this point, since so much code has moved to the
attribute API which checks for it.
2022-11-22 13:48:58 -06:00
461cb550cc Fix T102670: Crash after deleting attribute in edit mode
Since we free BMesh attributes by attempting on every domain,
sometimes the attribute wouldn't be found for a CustomData.
We avoid reallocating custom data blocks in that case, so we
need to pass the ownership of the "pool" back to the BMesh.
2022-11-22 13:48:58 -06:00
55db44ca2c Merge branch 'blender-v3.4-release' 2022-11-22 11:17:30 -08:00
fa8a59689b Sculpt: fix const error 2022-11-22 11:14:36 -08:00
c3e919b317 Sculpt: Fix broken multires hidden undo
Wrote a new API method, BKE_pbvh_sync_visibility_from_verts
that flushes vertex hidden flags to edges & faces.

Fixes not being able to sculpt outside a face set after
undoing the fkey hide-all-but-this operator.
2022-11-22 11:11:03 -08:00
a2a72d89b1 Sculpt: Fix broken multires hidden undo
Wrote a new API method, BKE_pbvh_sync_visibility_from_verts
that flushes vertex hidden flags to edges & faces.

Fixes not being able to sculpt outside a face set after
undoing the fkey hide-all-but-this operator.
2022-11-22 10:56:39 -08:00
14d0b57be7 Cleanup: Use array_utils to copy evaluated field array 2022-11-22 12:49:51 -06:00
8e535ee9b4 Cleanup: Remove unused math min max utility
This has a friendlier multithreaded implementation in BLI_bounds.hh now.
2022-11-22 12:31:31 -06:00
772696a1a5 Geometry Nodes: Parallelize bounds compuation in points to volume
On my computer this saves a few milliseconds when there are
over 1 million points.
2022-11-22 12:13:52 -06:00
8990983b07 Cleanup: Use const arguments for custom normals 2022-11-22 11:48:16 -06:00
a5e7657cee BLI: Remove clamping from span slicing
Currently slicing a span clamped the final size so that it would be
within bounds of the input. However, in the vast majority of cases
that is already the case anyway, and we can use asserts to detect
when that assumption fails.

The clamping had a performance cost. On a test interpolating a boolean
attribute from 1 million curves to 4 million points, removing the
clamping saved about 10% of the time. That's an extreme case but
this probably slightly improves performance in other cases too.
Slicing is used a lot in the new curve code.

This commit introduces `slice_safe` which still does the clamping,
and uses it in the few places that needed it or where I wasn't
sure.
2022-11-22 11:29:24 -06:00
822dab9a42 Cleanup: Strict compiler warnings
Mark function as local to the translation unit.
2022-11-22 17:52:44 +01:00
67194fb247 Merge branch 'blender-v3.4-release' 2022-11-22 16:35:26 +01:00
f8d6bbeb63 Fix compilation error.
Regression introduced by 6d7a067a83.
Fixed by adding an import to BKE_node_runtime.hh.
2022-11-22 15:02:08 +01:00
ed82bbfc2c Cleanup: use designated initializers in C 2022-11-22 10:48:16 -03:00
dd5fdb9370 GPU: Add MoltenVK as dependencies to Vulkan SDK.
MoltenVK is part of the vulkan SDK. Blender requires the vulkan SDK
to compile. This patch adds the MoltenVK includes and libraries to
the Vulkan includes and libraries.
2022-11-22 14:07:34 +01:00
51f56e71cb Merge branch 'blender-v3.4-release' 2022-11-22 13:41:07 +01:00
6d7a067a83 Fix T100904: Shader Nodes: Optimize-out inactive mix branches
Ensure inactive mix branches don't modify material flags or generate code.

Reviewed By: fclem

Maniphest Tasks: T100904

Differential Revision: https://developer.blender.org/D16533
2022-11-22 13:39:20 +01:00
02c6136958 Fix compile error with msvc
error C2059: syntax error: '}'
2022-11-22 09:13:16 -03:00
b79e5ae4f2 GHOST: Add missing C_API function to header file.
- GHOST_GetDrawingContext was missing.
2022-11-22 12:45:49 +01:00
7dea18b3aa Tracking: Store lens principal point in normalized space
This avoids need to do special trickery detecting whether the principal
point is to be changed when reloading movie clip. This also allows to
transfer the optical center from high-res footage to possibly its lower
resolution proxy without manual adjustment.

On a user level the difference is that the principal point is exposed in
the normalized coordinates: frame center has coordinate of (0, 0), left
bottom corner of a frame has coordinate of (-1, -1) and the right top
corner has coordinate of (1, 1).

Another user-visible change is that there is no more operator for setting
the principal point to center: use backspace on the center sliders will
reset values to 0 which corresponds to the center.

The code implements versioning in both directions, so it should be
possible to open file in older Blender versions without loosing
configuration.

For the Python API there are two ways to access the property:
- `tracking.camera.principal_point` which is measured in the normalized
  space.
- `tracking.camera.principal_point_pixels` to access the pixel-space
  principal point.

Both properties are not animatable, so there will by no conflict coming.

Differential Revision: https://developer.blender.org/D16573
2022-11-22 11:49:56 +01:00
04dc58df83 Tracking: Mark more deprecated RNA access as such
Also clarify a bit the new way of accessing the data.
2022-11-22 11:49:56 +01:00
294ff0de43 Cleanup: More clear function name
Make it explicit in the name that track index is the one used for
selection (previously it read as if the track index is within its
list).
2022-11-22 11:49:56 +01:00
f383dfabf7 Cleanup: Simplify public tracking API
Remove functions which are a trivial accessor.
2022-11-22 11:49:56 +01:00
d37efe332c Tracking: Mark deprecated active tracks access as such 2022-11-22 11:49:56 +01:00
ceaf4779da Fix active track always assigned for the active object
A missing part from the storage split refactor.
2022-11-22 11:49:56 +01:00
88d9ed3c1c Fix missing update on active motion track change from Python 2022-11-22 11:49:56 +01:00
0b251493c8 Tracking: Raise python exception when assigning wrong active track
Before this an attempt to assign track from another object wos
silently assigning active object to null. Such silencing of
errors is not really a good way.
2022-11-22 11:49:56 +01:00
ea969ccc02 Refactor: Replace marker visibility macro with function
Also optimize sub-optimal request for active object for every
call of the check.

Should be no functional changes.
2022-11-22 11:49:56 +01:00
b864397201 Refactor: Clip editor tracking selection operator
De-duplicate selection logic and threshold between various
operators (selection and sliding).

The user measurable difference is that regular selection
threshold now matches sliding threshold: it is more strict
now. The possible downside of this is that it might be more
tricky to select tracks, but this is what needs to happen
for tools support. Also, this matches object selection in
viewport.
2022-11-22 11:49:56 +01:00
4d1a116cdf Refactor: Simplify tracking active element accessor API
Use active object accessor, and then access data from the
object. There is no need to have an API call for shortcut
of all object fields.

Should be no functional change.
2022-11-22 11:49:56 +01:00
1300da6d39 Cleanup: More clear function name in tracking module
Make it more obvious in the name that an operation is not
cheap, and that the function operates on a tracks from
object and does not need a global tracking structure.
2022-11-22 11:49:56 +01:00
016f9c2cf5 Cleanup: Variable naming in tracking files
Make it obvious that the object is the motion tracking one, and
not the ID_OB type.
2022-11-22 11:49:56 +01:00
953f719e58 Cleanup: Variable scope in tracking files 2022-11-22 11:49:56 +01:00
1168665653 Refactor: Remove trivial accessor functions from tracking 2022-11-22 11:49:56 +01:00
fe38715600 Refactor: Unify storage for motion tracking camera and objects
Historically tracks and reconstruction for motion tracking camera
object were stored in the motion tracking structure. This is because
the data structures pre-dates object tracking support, and it was
never changed to preserve compatibility.

Now the compatibility code supports more tricks and allows to change
the ownership without breaking any compatibility. This is what this
change does: it moves tracks from motion tracking structure to the
motion tracking camera object, and does it in a way that no
compatibility is broken.

One of the side-effects of this change is that the active track is
now stored on motion tracking object level, which allows to change
active motion tracking object without loosing active track. Other
than that there are no expected user-level changes.
2022-11-22 11:49:56 +01:00
4d497721ec Refactor: Streamline tracking data copying a bit
Prepare the code to more easily support pointers remapping
for tracking objects.

Should be no functional changes.
2022-11-22 11:49:56 +01:00
3c479b9823 Movie clip: Remove special selection synchronization function
The function is already doing a lot of memory indirections and
sub-optimal lookups, so for the simplicity and robustness of the
system might as well just do copy-on-write update.
2022-11-22 11:49:56 +01:00
7411fa4e0d Cleanup: Better const-correctness in tracking code 2022-11-22 11:49:56 +01:00
44d7ec7e80 Clip Editor: Migrate orientation file to C++
Should be no functional changes.

Some of the code is suboptimal from C++ syntax point of view.
It will be worked on as a further development.
2022-11-22 11:49:56 +01:00
Jeroen Bakker
6dac345a64 GHOST: Vulkan Backend.
This adds a vulkan backend to GHOST. The code was extracted from the
tmp-vulkan branch. The main difference with the original code is that
GHOST isn't responsible for fallback. For Metal backend there is already
an idea that the GPU module is responsible for the fallback, not the system.

For Blender we target Vulkan 1.2 at the time of this patch.
MoltenVK (needed to convert Vulkan calls to Metal) has been added as
a separate package.

This patch isn't useful for end-users, currently when starting blender with
`--gpu-backend vulkan` it would crash as the `VBBackend` doesn't initialize
the expected global structs in the GPU module.

Validated to be working on Windows and Apple. Linux still needs to be tested.

Reviewed By: fclem

Differential Revision: https://developer.blender.org/D13155
2022-11-22 11:29:09 +01:00
722 changed files with 42260 additions and 16988 deletions

View File

@@ -1244,9 +1244,6 @@ endif()
if(WITH_VULKAN_BACKEND)
list(APPEND BLENDER_GL_LIBRARIES ${VULKAN_LIBRARIES})
if(APPLE)
list(APPEND BLENDER_GL_LIBRARIES ${MOLTENVK_LIBRARIES})
endif()
endif()
# -----------------------------------------------------------------------------

View File

@@ -101,8 +101,20 @@ if(WITH_USD)
endif()
if(WITH_VULKAN_BACKEND)
find_package(Vulkan REQUIRED)
find_package(MoltenVK REQUIRED)
if(EXISTS ${LIBDIR}/vulkan)
set(VULKAN_FOUND On)
set(VULKAN_ROOT_DIR ${LIBDIR}/vulkan/macOS)
set(VULKAN_INCLUDE_DIR ${VULKAN_ROOT_DIR}/include)
set(VULKAN_LIBRARY ${VULKAN_ROOT_DIR}/lib/libvulkan.1.dylib)
set(VULKAN_INCLUDE_DIRS ${VULKAN_INCLUDE_DIR} ${MOLTENVK_INCLUDE_DIRS})
set(VULKAN_LIBRARIES ${VULKAN_LIBRARY} ${MOLTENVK_LIBRARIES})
else()
message(WARNING "Vulkan SDK was not found, disabling WITH_VULKAN_BACKEND")
set(WITH_VULKAN_BACKEND OFF)
endif()
endif()
if(WITH_OPENSUBDIV)

View File

@@ -326,9 +326,10 @@ if(WITH_CYCLES AND WITH_CYCLES_DEVICE_ONEAPI)
file(GLOB _sycl_runtime_libraries
${SYCL_ROOT_DIR}/lib/libsycl.so
${SYCL_ROOT_DIR}/lib/libsycl.so.*
${SYCL_ROOT_DIR}/lib/libpi_level_zero.so
${SYCL_ROOT_DIR}/lib/libpi_*.so
)
list(FILTER _sycl_runtime_libraries EXCLUDE REGEX ".*\.py")
list(REMOVE_ITEM _sycl_runtime_libraries "${SYCL_ROOT_DIR}/lib/libpi_opencl.so")
list(APPEND PLATFORM_BUNDLED_LIBRARIES ${_sycl_runtime_libraries})
unset(_sycl_runtime_libraries)
endif()
@@ -969,16 +970,9 @@ if(WITH_COMPILER_CCACHE)
endif()
endif()
# On some platforms certain atomic operations are not possible with assembly and/or intrinsics and
# they are emulated in software with locks. For example, on armel there is no intrinsics to grant
# 64 bit atomic operations and STL library uses libatomic to offload software emulation of atomics
# to.
# This function will check whether libatomic is required and if so will configure linker flags.
# If atomic operations are possible without libatomic then linker flags are left as-is.
function(CONFIGURE_ATOMIC_LIB_IF_NEEDED)
# Source which is used to enforce situation when software emulation of atomics is required.
# Assume that using 64bit integer gives a definitive answer (as in, if 64bit atomic operations
# are possible using assembly/intrinsics 8, 16, and 32 bit operations will also be possible.
# Always link with libatomic if available, as it is required for data types
# which don't have intrinsics.
function(configure_atomic_lib_if_needed)
set(_source
"#include <atomic>
#include <cstdint>
@@ -989,25 +983,12 @@ function(CONFIGURE_ATOMIC_LIB_IF_NEEDED)
)
include(CheckCXXSourceCompiles)
check_cxx_source_compiles("${_source}" ATOMIC_OPS_WITHOUT_LIBATOMIC)
set(CMAKE_REQUIRED_LIBRARIES atomic)
check_cxx_source_compiles("${_source}" ATOMIC_OPS_WITH_LIBATOMIC)
unset(CMAKE_REQUIRED_LIBRARIES)
if(NOT ATOMIC_OPS_WITHOUT_LIBATOMIC)
# Compilation of the test program has failed.
# Try it again with -latomic to see if this is what is needed, or whether something else is
# going on.
set(CMAKE_REQUIRED_LIBRARIES atomic)
check_cxx_source_compiles("${_source}" ATOMIC_OPS_WITH_LIBATOMIC)
unset(CMAKE_REQUIRED_LIBRARIES)
if(ATOMIC_OPS_WITH_LIBATOMIC)
set(PLATFORM_LINKFLAGS "${PLATFORM_LINKFLAGS} -latomic" PARENT_SCOPE)
else()
# Atomic operations are required part of Blender and it is not possible to process forward.
# We expect that either standard library or libatomic will make atomics to work. If both
# cases has failed something fishy o na bigger scope is going on.
message(FATAL_ERROR "Failed to detect required configuration for atomic operations")
endif()
if(ATOMIC_OPS_WITH_LIBATOMIC)
set(PLATFORM_LINKFLAGS "${PLATFORM_LINKFLAGS} -latomic" PARENT_SCOPE)
endif()
endfunction()

View File

@@ -935,7 +935,7 @@ if(WITH_VULKAN_BACKEND)
set(VULKAN_LIBRARY ${VULKAN_ROOT_DIR}/lib/vulkan-1.lib)
set(VULKAN_LIBRARIES ${VULKAN_LIBRARY})
else()
message(WARNING "Vulkan was not found, disabling WITH_VULKAN_BACKEND")
message(WARNING "Vulkan SDK was not found, disabling WITH_VULKAN_BACKEND")
set(WITH_VULKAN_BACKEND OFF)
endif()
endif()
@@ -972,7 +972,13 @@ if(WITH_CYCLES AND WITH_CYCLES_DEVICE_ONEAPI)
endforeach()
unset(_sycl_runtime_libraries_glob)
list(APPEND _sycl_runtime_libraries ${SYCL_ROOT_DIR}/bin/pi_level_zero.dll)
file(GLOB _sycl_pi_runtime_libraries_glob
${SYCL_ROOT_DIR}/bin/pi_*.dll
)
list(REMOVE_ITEM _sycl_pi_runtime_libraries_glob "${SYCL_ROOT_DIR}/bin/pi_opencl.dll")
list (APPEND _sycl_runtime_libraries ${_sycl_pi_runtime_libraries_glob})
unset(_sycl_pi_runtime_libraries_glob)
list(APPEND PLATFORM_BUNDLED_LIBRARIES ${_sycl_runtime_libraries})
unset(_sycl_runtime_libraries)
endif()

View File

@@ -35,10 +35,41 @@ from typing import (
Tuple,
)
# ------------------------------------------------------------------------------
# Long Description
long_description = """# Blender
[Blender](https://www.blender.org) is the free and open source 3D creation suite. It supports the entirety of the 3D pipeline—modeling, rigging, animation, simulation, rendering, compositing and motion tracking, even video editing.
This package provides Blender as a Python module for use in studio pipelines, web services, scientific research, and more.
## Documentation
* [Blender Python API](https://docs.blender.org/api/current/)
* [Blender as a Python Module](https://docs.blender.org/api/current/info_advanced_blender_as_bpy.html)
## Requirements
[System requirements](https://www.blender.org/download/requirements/) are the same as Blender.
Each Blender release supports one Python version, and the package is only compatible with that version.
## Source Code
* [Releases](https://download.blender.org/source/)
* Repository: [git.blender.org/blender.git](https://git.blender.org/gitweb/gitweb.cgi/blender.git)
## Credits
Created by the [Blender developer community](https://www.blender.org/about/credits/).
Thanks to Tyler Alden Gubala for maintaining the original version of this package."""
# ------------------------------------------------------------------------------
# Generic Functions
def find_dominating_file(
path: str,
search: Sequence[str],
@@ -195,6 +226,8 @@ def main() -> None:
options={"bdist_wheel": {"plat_name": platform_tag}},
description="Blender as a Python module",
long_description=long_description,
long_description_content_type='text/markdown',
license="GPL-3.0",
author="Blender Foundation",
author_email="bf-committers@blender.org",

View File

@@ -870,6 +870,26 @@ an issue but, due to internal implementation details, currently are:
thus breaking any current iteration over ``Collection.all_objects``.
.. rubric:: Do not:
.. code-block:: python
# `all_objects` is an iterator. Using it directly while performing operations on its members that will update
# the memory accessed by the `all_objects` iterator will lead to invalid memory accesses and crashes.
for object in bpy.data.collections["Collection"].all_objects:
object.hide_viewport = True
.. rubric:: Do:
.. code-block:: python
# `all_objects[:]` is an independent list generated from the iterator. As long as no objects are deleted,
# its content will remain valid even if the data accessed by the `all_objects` iterator is modified.
for object in bpy.data.collections["Collection"].all_objects[:]:
object.hide_viewport = True
sys.exit
========

View File

@@ -1294,6 +1294,7 @@ def pycontext2sphinx(basepath):
type_descr = prop.get_type_description(
class_fmt=":class:`bpy.types.%s`",
mathutils_fmt=":class:`mathutils.%s`",
collection_id=_BPY_PROP_COLLECTION_ID,
enum_descr_override=enum_descr_override,
)
@@ -1446,6 +1447,7 @@ def pyrna2sphinx(basepath):
identifier = " %s" % prop.identifier
kwargs["class_fmt"] = ":class:`%s`"
kwargs["mathutils_fmt"] = ":class:`mathutils.%s`"
kwargs["collection_id"] = _BPY_PROP_COLLECTION_ID
@@ -1565,6 +1567,7 @@ def pyrna2sphinx(basepath):
type_descr = prop.get_type_description(
class_fmt=":class:`%s`",
mathutils_fmt=":class:`mathutils.%s`",
collection_id=_BPY_PROP_COLLECTION_ID,
enum_descr_override=enum_descr_override,
)
@@ -1631,6 +1634,7 @@ def pyrna2sphinx(basepath):
type_descr = prop.get_type_description(
as_ret=True, class_fmt=":class:`%s`",
mathutils_fmt=":class:`mathutils.%s`",
collection_id=_BPY_PROP_COLLECTION_ID,
enum_descr_override=enum_descr_override,
)

View File

@@ -91,3 +91,7 @@ endif()
if(WITH_COMPOSITOR_CPU)
add_subdirectory(smaa_areatex)
endif()
if(WITH_VULKAN_BACKEND)
add_subdirectory(vulkan_memory_allocator)
endif()

View File

@@ -0,0 +1,24 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright 2022 Blender Foundation. All rights reserved.
set(INC
.
)
set(INC_SYS
${VULKAN_INCLUDE_DIRS}
)
set(SRC
vk_mem_alloc_impl.cc
vk_mem_alloc.h
)
blender_add_lib(extern_vulkan_memory_allocator "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang")
target_compile_options(extern_vulkan_memory_allocator
PRIVATE "-Wno-nullability-completeness"
)
endif()

View File

@@ -0,0 +1,19 @@
Copyright (c) 2017-2022 Advanced Micro Devices, Inc. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,5 @@
Project: VulkanMemoryAllocator
URL: https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator
License: MIT
Upstream version: a6bfc23
Local modifications: None

175
extern/vulkan_memory_allocator/README.md vendored Normal file
View File

@@ -0,0 +1,175 @@
# Vulkan Memory Allocator
Easy to integrate Vulkan memory allocation library.
**Documentation:** Browse online: [Vulkan Memory Allocator](https://gpuopen-librariesandsdks.github.io/VulkanMemoryAllocator/html/) (generated from Doxygen-style comments in [include/vk_mem_alloc.h](include/vk_mem_alloc.h))
**License:** MIT. See [LICENSE.txt](LICENSE.txt)
**Changelog:** See [CHANGELOG.md](CHANGELOG.md)
**Product page:** [Vulkan Memory Allocator on GPUOpen](https://gpuopen.com/gaming-product/vulkan-memory-allocator/)
**Build status:**
- Windows: [![Build status](https://ci.appveyor.com/api/projects/status/4vlcrb0emkaio2pn/branch/master?svg=true)](https://ci.appveyor.com/project/adam-sawicki-amd/vulkanmemoryallocator/branch/master)
- Linux: [![Build Status](https://app.travis-ci.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.svg?branch=master)](https://app.travis-ci.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator)
[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.svg)](http://isitmaintained.com/project/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator "Average time to resolve an issue")
# Problem
Memory allocation and resource (buffer and image) creation in Vulkan is difficult (comparing to older graphics APIs, like D3D11 or OpenGL) for several reasons:
- It requires a lot of boilerplate code, just like everything else in Vulkan, because it is a low-level and high-performance API.
- There is additional level of indirection: `VkDeviceMemory` is allocated separately from creating `VkBuffer`/`VkImage` and they must be bound together.
- Driver must be queried for supported memory heaps and memory types. Different GPU vendors provide different types of it.
- It is recommended to allocate bigger chunks of memory and assign parts of them to particular resources, as there is a limit on maximum number of memory blocks that can be allocated.
# Features
This library can help game developers to manage memory allocations and resource creation by offering some higher-level functions:
1. Functions that help to choose correct and optimal memory type based on intended usage of the memory.
- Required or preferred traits of the memory are expressed using higher-level description comparing to Vulkan flags.
2. Functions that allocate memory blocks, reserve and return parts of them (`VkDeviceMemory` + offset + size) to the user.
- Library keeps track of allocated memory blocks, used and unused ranges inside them, finds best matching unused ranges for new allocations, respects all the rules of alignment and buffer/image granularity.
3. Functions that can create an image/buffer, allocate memory for it and bind them together - all in one call.
Additional features:
- Well-documented - description of all functions and structures provided, along with chapters that contain general description and example code.
- Thread-safety: Library is designed to be used in multithreaded code. Access to a single device memory block referred by different buffers and textures (binding, mapping) is synchronized internally. Memory mapping is reference-counted.
- Configuration: Fill optional members of `VmaAllocatorCreateInfo` structure to provide custom CPU memory allocator, pointers to Vulkan functions and other parameters.
- Customization and integration with custom engines: Predefine appropriate macros to provide your own implementation of all external facilities used by the library like assert, mutex, atomic.
- Support for memory mapping, reference-counted internally. Support for persistently mapped memory: Just allocate with appropriate flag and access the pointer to already mapped memory.
- Support for non-coherent memory. Functions that flush/invalidate memory. `nonCoherentAtomSize` is respected automatically.
- Support for resource aliasing (overlap).
- Support for sparse binding and sparse residency: Convenience functions that allocate or free multiple memory pages at once.
- Custom memory pools: Create a pool with desired parameters (e.g. fixed or limited maximum size) and allocate memory out of it.
- Linear allocator: Create a pool with linear algorithm and use it for much faster allocations and deallocations in free-at-once, stack, double stack, or ring buffer fashion.
- Support for Vulkan 1.0, 1.1, 1.2, 1.3.
- Support for extensions (and equivalent functionality included in new Vulkan versions):
- VK_KHR_dedicated_allocation: Just enable it and it will be used automatically by the library.
- VK_KHR_buffer_device_address: Flag `VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR` is automatically added to memory allocations where needed.
- VK_EXT_memory_budget: Used internally if available to query for current usage and budget. If not available, it falls back to an estimation based on memory heap sizes.
- VK_EXT_memory_priority: Set `priority` of allocations or custom pools and it will be set automatically using this extension.
- VK_AMD_device_coherent_memory
- Defragmentation of GPU and CPU memory: Let the library move data around to free some memory blocks and make your allocations better compacted.
- Statistics: Obtain brief or detailed statistics about the amount of memory used, unused, number of allocated blocks, number of allocations etc. - globally, per memory heap, and per memory type.
- Debug annotations: Associate custom `void* pUserData` and debug `char* pName` with each allocation.
- JSON dump: Obtain a string in JSON format with detailed map of internal state, including list of allocations, their string names, and gaps between them.
- Convert this JSON dump into a picture to visualize your memory. See [tools/GpuMemDumpVis](tools/GpuMemDumpVis/README.md).
- Debugging incorrect memory usage: Enable initialization of all allocated memory with a bit pattern to detect usage of uninitialized or freed memory. Enable validation of a magic number after every allocation to detect out-of-bounds memory corruption.
- Support for interoperability with OpenGL.
- Virtual allocator: Interface for using core allocation algorithm to allocate any custom data, e.g. pieces of one large buffer.
# Prerequisites
- Self-contained C++ library in single header file. No external dependencies other than standard C and C++ library and of course Vulkan. Some features of C++14 used. STL containers, RTTI, or C++ exceptions are not used.
- Public interface in C, in same convention as Vulkan API. Implementation in C++.
- Error handling implemented by returning `VkResult` error codes - same way as in Vulkan.
- Interface documented using Doxygen-style comments.
- Platform-independent, but developed and tested on Windows using Visual Studio. Continuous integration setup for Windows and Linux. Used also on Android, MacOS, and other platforms.
# Example
Basic usage of this library is very simple. Advanced features are optional. After you created global `VmaAllocator` object, a complete code needed to create a buffer may look like this:
```cpp
VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufferInfo.size = 65536;
bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
VmaAllocationCreateInfo allocInfo = {};
allocInfo.usage = VMA_MEMORY_USAGE_AUTO;
VkBuffer buffer;
VmaAllocation allocation;
vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer, &allocation, nullptr);
```
With this one function call:
1. `VkBuffer` is created.
2. `VkDeviceMemory` block is allocated if needed.
3. An unused region of the memory block is bound to this buffer.
`VmaAllocation` is an object that represents memory assigned to this buffer. It can be queried for parameters like `VkDeviceMemory` handle and offset.
# How to build
On Windows it is recommended to use [CMake UI](https://cmake.org/runningcmake/). Alternatively you can generate a Visual Studio project map using CMake in command line: `cmake -B./build/ -DCMAKE_BUILD_TYPE=Debug -G "Visual Studio 16 2019" -A x64 ./`
On Linux:
```
mkdir build
cd build
cmake ..
make
```
The following targets are available
| Target | Description | CMake option | Default setting |
| ------------- | ------------- | ------------- | ------------- |
| VmaSample | VMA sample application | `VMA_BUILD_SAMPLE` | `OFF` |
| VmaBuildSampleShaders | Shaders for VmaSample | `VMA_BUILD_SAMPLE_SHADERS` | `OFF` |
Please note that while VulkanMemoryAllocator library is supported on other platforms besides Windows, VmaSample is not.
These CMake options are available
| CMake option | Description | Default setting |
| ------------- | ------------- | ------------- |
| `VMA_RECORDING_ENABLED` | Enable VMA memory recording for debugging | `OFF` |
| `VMA_USE_STL_CONTAINERS` | Use C++ STL containers instead of VMA's containers | `OFF` |
| `VMA_STATIC_VULKAN_FUNCTIONS` | Link statically with Vulkan API | `OFF` |
| `VMA_DYNAMIC_VULKAN_FUNCTIONS` | Fetch pointers to Vulkan functions internally (no static linking) | `ON` |
| `VMA_DEBUG_ALWAYS_DEDICATED_MEMORY` | Every allocation will have its own memory block | `OFF` |
| `VMA_DEBUG_INITIALIZE_ALLOCATIONS` | Automatically fill new allocations and destroyed allocations with some bit pattern | `OFF` |
| `VMA_DEBUG_GLOBAL_MUTEX` | Enable single mutex protecting all entry calls to the library | `OFF` |
| `VMA_DEBUG_DONT_EXCEED_MAX_MEMORY_ALLOCATION_COUNT` | Never exceed [VkPhysicalDeviceLimits::maxMemoryAllocationCount](https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#limits-maxMemoryAllocationCount) and return error | `OFF` |
# Binaries
The release comes with precompiled binary executable for "VulkanSample" application which contains test suite. It is compiled using Visual Studio 2019, so it requires appropriate libraries to work, including "MSVCP140.dll", "VCRUNTIME140.dll", "VCRUNTIME140_1.dll". If the launch fails with error message telling about those files missing, please download and install [Microsoft Visual C++ Redistributable for Visual Studio 2015, 2017 and 2019](https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads), "x64" version.
# Read more
See **[Documentation](https://gpuopen-librariesandsdks.github.io/VulkanMemoryAllocator/html/)**.
# Software using this library
- **[X-Plane](https://x-plane.com/)**
- **[Detroit: Become Human](https://gpuopen.com/learn/porting-detroit-3/)**
- **[Vulkan Samples](https://github.com/LunarG/VulkanSamples)** - official Khronos Vulkan samples. License: Apache-style.
- **[Anvil](https://github.com/GPUOpen-LibrariesAndSDKs/Anvil)** - cross-platform framework for Vulkan. License: MIT.
- **[Filament](https://github.com/google/filament)** - physically based rendering engine for Android, Windows, Linux and macOS, from Google. Apache License 2.0.
- **[Atypical Games - proprietary game engine](https://developer.samsung.com/galaxy-gamedev/gamedev-blog/infinitejet.html)**
- **[Flax Engine](https://flaxengine.com/)**
- **[Godot Engine](https://github.com/godotengine/godot/)** - multi-platform 2D and 3D game engine. License: MIT.
- **[Lightweight Java Game Library (LWJGL)](https://www.lwjgl.org/)** - includes binding of the library for Java. License: BSD.
- **[PowerVR SDK](https://github.com/powervr-graphics/Native_SDK)** - C++ cross-platform 3D graphics SDK, from Imagination. License: MIT.
- **[Skia](https://github.com/google/skia)** - complete 2D graphic library for drawing Text, Geometries, and Images, from Google.
- **[The Forge](https://github.com/ConfettiFX/The-Forge)** - cross-platform rendering framework. Apache License 2.0.
- **[VK9](https://github.com/disks86/VK9)** - Direct3D 9 compatibility layer using Vulkan. Zlib lincese.
- **[vkDOOM3](https://github.com/DustinHLand/vkDOOM3)** - Vulkan port of GPL DOOM 3 BFG Edition. License: GNU GPL.
- **[vkQuake2](https://github.com/kondrak/vkQuake2)** - vanilla Quake 2 with Vulkan support. License: GNU GPL.
- **[Vulkan Best Practice for Mobile Developers](https://github.com/ARM-software/vulkan_best_practice_for_mobile_developers)** from ARM. License: MIT.
- **[RPCS3](https://github.com/RPCS3/rpcs3)** - PlayStation 3 emulator/debugger. License: GNU GPLv2.
- **[PPSSPP](https://github.com/hrydgard/ppsspp)** - Playstation Portable emulator/debugger. License: GNU GPLv2+.
[Many other projects on GitHub](https://github.com/search?q=AMD_VULKAN_MEMORY_ALLOCATOR_H&type=Code) and some game development studios that use Vulkan in their games.
# See also
- **[D3D12 Memory Allocator](https://github.com/GPUOpen-LibrariesAndSDKs/D3D12MemoryAllocator)** - equivalent library for Direct3D 12. License: MIT.
- **[Awesome Vulkan](https://github.com/vinjn/awesome-vulkan)** - a curated list of awesome Vulkan libraries, debuggers and resources.
- **[VulkanMemoryAllocator-Hpp](https://github.com/malte-v/VulkanMemoryAllocator-Hpp)** - C++ binding for this library. License: CC0-1.0.
- **[PyVMA](https://github.com/realitix/pyvma)** - Python wrapper for this library. Author: Jean-Sébastien B. (@realitix). License: Apache 2.0.
- **[vk-mem](https://github.com/gwihlidal/vk-mem-rs)** - Rust binding for this library. Author: Graham Wihlidal. License: Apache 2.0 or MIT.
- **[Haskell bindings](https://hackage.haskell.org/package/VulkanMemoryAllocator)**, **[github](https://github.com/expipiplus1/vulkan/tree/master/VulkanMemoryAllocator)** - Haskell bindings for this library. Author: Ellie Hermaszewska (@expipiplus1). License BSD-3-Clause.
- **[vma_sample_sdl](https://github.com/rextimmy/vma_sample_sdl)** - SDL port of the sample app of this library (with the goal of running it on multiple platforms, including MacOS). Author: @rextimmy. License: MIT.
- **[vulkan-malloc](https://github.com/dylanede/vulkan-malloc)** - Vulkan memory allocation library for Rust. Based on version 1 of this library. Author: Dylan Ede (@dylanede). License: MIT / Apache 2.0.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2022 Blender Foundation. All rights reserved. */
#ifdef __APPLE__
# include <MoltenVK/vk_mvk_moltenvk.h>
#else
# include <vulkan/vulkan.h>
#endif
#define VMA_IMPLEMENTATION
#include "vk_mem_alloc.h"

View File

@@ -204,7 +204,6 @@ def list_render_passes(scene, srl):
if crl.use_pass_volume_indirect: yield ("VolumeInd", "RGB", 'COLOR')
if srl.use_pass_emit: yield ("Emit", "RGB", 'COLOR')
if srl.use_pass_environment: yield ("Env", "RGB", 'COLOR')
if srl.use_pass_shadow: yield ("Shadow", "RGB", 'COLOR')
if srl.use_pass_ambient_occlusion: yield ("AO", "RGB", 'COLOR')
if crl.use_pass_shadow_catcher: yield ("Shadow Catcher", "RGB", 'COLOR')
# autopep8: on

View File

@@ -91,7 +91,7 @@ class AddPresetPerformance(AddPresetBase, Operator):
preset_menu = "CYCLES_PT_performance_presets"
preset_defines = [
"render = bpy.context.scene.render"
"render = bpy.context.scene.render",
"cycles = bpy.context.scene.cycles"
]

View File

@@ -86,6 +86,29 @@ enum_sampling_pattern = (
('PROGRESSIVE_MULTI_JITTER', "Progressive Multi-Jitter", "Use Progressive Multi-Jitter random sampling pattern", 1),
)
enum_emission_sampling = (
('NONE',
'None',
"Do not use this surface as a light for sampling",
0),
('AUTO',
'Auto',
"Automatically determine if the surface should be treated as a light for sampling, based on estimated emission intensity",
1),
('FRONT',
'Front',
"Treat only front side of the surface as a light, usually for closed meshes whose interior is not visible",
2),
('BACK',
'Back',
"Treat only back side of the surface as a light for sampling",
3),
('FRONT_BACK',
'Front and Back',
"Treat surface as a light for sampling, emitting from both the front and back side",
4),
)
enum_volume_sampling = (
('DISTANCE',
"Distance",
@@ -147,7 +170,6 @@ enum_view3d_shading_render_pass = (
('EMISSION', "Emission", "Show the Emission render pass"),
('BACKGROUND', "Background", "Show the Background render pass"),
('AO', "Ambient Occlusion", "Show the Ambient Occlusion render pass"),
('SHADOW', "Shadow", "Show the Shadow render pass"),
('SHADOW_CATCHER', "Shadow Catcher", "Show the Shadow Catcher render pass"),
('', "Light", ""),
@@ -481,6 +503,12 @@ class CyclesRenderSettings(bpy.types.PropertyGroup):
default='MULTIPLE_IMPORTANCE_SAMPLING',
)
use_light_tree: BoolProperty(
name="Light Tree",
description="Sample multiple lights more efficiently based on estimated contribution at every shading point",
default=True,
)
min_light_bounces: IntProperty(
name="Min Light Bounces",
description="Minimum number of light bounces. Setting this higher reduces noise in the first bounces, "
@@ -622,7 +650,7 @@ class CyclesRenderSettings(bpy.types.PropertyGroup):
transparent_max_bounces: IntProperty(
name="Transparent Max Bounces",
description="Maximum number of transparent bounces. This is independent of maximum number of other bounces ",
description="Maximum number of transparent bounces. This is independent of maximum number of other bounces",
min=0, max=1024,
default=8,
)
@@ -1043,13 +1071,13 @@ class CyclesCameraSettings(bpy.types.PropertyGroup):
class CyclesMaterialSettings(bpy.types.PropertyGroup):
sample_as_light: BoolProperty(
name="Multiple Importance Sample",
description="Use multiple importance sampling for this material, "
"disabling may reduce overall noise for large "
"objects that emit little light compared to other light sources",
default=True,
emission_sampling: EnumProperty(
name="Emission Sampling",
description="Sampling strategy for emissive surfaces",
items=enum_emission_sampling,
default="AUTO",
)
use_transparent_shadow: BoolProperty(
name="Transparent Shadows",
description="Use transparent shadows for this material if it contains a Transparent BSDF, "
@@ -1642,7 +1670,7 @@ class CyclesPreferences(bpy.types.AddonPreferences):
col.label(text="and Windows driver version 101.3430 or newer", icon='BLANK1')
elif sys.platform.startswith("linux"):
col.label(text="Requires Intel GPU with Xe-HPG architecture and", icon='BLANK1')
col.label(text=" - Linux driver version xx.xx.23904 or newer", icon='BLANK1')
col.label(text=" - intel-level-zero-gpu version 1.3.23904 or newer", icon='BLANK1')
col.label(text=" - oneAPI Level-Zero Loader", icon='BLANK1')
elif device_type == 'METAL':
col.label(text="Requires Apple Silicon with macOS 12.2 or newer", icon='BLANK1')

View File

@@ -154,8 +154,9 @@ def use_mnee(context):
# The MNEE kernel doesn't compile on macOS < 13.
if use_metal(context):
import platform
v, _, _ = platform.mac_ver()
if float(v) < 13.0:
version, _, _ = platform.mac_ver()
major_version = version.split(".")[0]
if int(major_version) < 13:
return False
return True
@@ -382,7 +383,6 @@ class CYCLES_RENDER_PT_sampling_advanced(CyclesButtonsPanel, Panel):
col = layout.column(align=True)
col.prop(cscene, "min_light_bounces")
col.prop(cscene, "min_transparent_bounces")
col.prop(cscene, "light_sampling_threshold", text="Light Threshold")
for view_layer in scene.view_layers:
if view_layer.samples > 0:
@@ -391,6 +391,31 @@ class CYCLES_RENDER_PT_sampling_advanced(CyclesButtonsPanel, Panel):
break
class CYCLES_RENDER_PT_sampling_lights(CyclesButtonsPanel, Panel):
bl_label = "Lights"
bl_parent_id = "CYCLES_RENDER_PT_sampling"
bl_options = {'DEFAULT_CLOSED'}
def draw_header(self, context):
layout = self.layout
scene = context.scene
cscene = scene.cycles
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
scene = context.scene
cscene = scene.cycles
col = layout.column(align=True)
col.prop(cscene, "use_light_tree")
sub = col.row()
sub.prop(cscene, "light_sampling_threshold", text="Light Threshold")
sub.active = not cscene.use_light_tree
class CYCLES_RENDER_PT_subdivision(CyclesButtonsPanel, Panel):
bl_label = "Subdivision"
bl_options = {'DEFAULT_CLOSED'}
@@ -953,7 +978,6 @@ class CYCLES_RENDER_PT_passes_light(CyclesButtonsPanel, Panel):
col = layout.column(heading="Other", align=True)
col.prop(view_layer, "use_pass_emit", text="Emission")
col.prop(view_layer, "use_pass_environment")
col.prop(view_layer, "use_pass_shadow")
col.prop(view_layer, "use_pass_ambient_occlusion", text="Ambient Occlusion")
col.prop(cycles_view_layer, "use_pass_shadow_catcher")
@@ -1831,9 +1855,9 @@ class CYCLES_MATERIAL_PT_settings_surface(CyclesButtonsPanel, Panel):
cmat = mat.cycles
col = layout.column()
col.prop(cmat, "sample_as_light", text="Multiple Importance")
col.prop(cmat, "use_transparent_shadow")
col.prop(cmat, "displacement_method", text="Displacement")
col.prop(cmat, "emission_sampling")
col.prop(cmat, "use_transparent_shadow")
def draw(self, context):
self.draw_shared(self, context.material)
@@ -2365,6 +2389,7 @@ classes = (
CYCLES_RENDER_PT_sampling_render_denoise,
CYCLES_RENDER_PT_sampling_path_guiding,
CYCLES_RENDER_PT_sampling_path_guiding_debug,
CYCLES_RENDER_PT_sampling_lights,
CYCLES_RENDER_PT_sampling_advanced,
CYCLES_RENDER_PT_light_paths,
CYCLES_RENDER_PT_light_paths_max_bounces,

View File

@@ -99,7 +99,7 @@ def do_versions(self):
library_versions.setdefault(library.version, []).append(library)
# Do versioning per library, since they might have different versions.
max_need_versioning = (3, 0, 25)
max_need_versioning = (3, 5, 2)
for version, libraries in library_versions.items():
if version > max_need_versioning:
continue
@@ -297,3 +297,8 @@ def do_versions(self):
cmat = mat.cycles
if not cmat.is_property_set("displacement_method"):
cmat.displacement_method = 'DISPLACEMENT'
if version <= (3, 5, 3):
cmat = mat.cycles
if not cmat.get("sample_as_light", True):
cmat.emission_sampling = 'NONE'

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,10 @@
#include "util/unique_ptr.h"
#include "util/vector.h"
typedef struct GPUContext GPUContext;
typedef struct GPUFence GPUFence;
typedef struct GPUShader GPUShader;
CCL_NAMESPACE_BEGIN
/* Base class of shader used for display driver rendering. */
@@ -29,7 +33,7 @@ class BlenderDisplayShader {
BlenderDisplayShader() = default;
virtual ~BlenderDisplayShader() = default;
virtual void bind(int width, int height) = 0;
virtual GPUShader *bind(int width, int height) = 0;
virtual void unbind() = 0;
/* Get attribute location for position and texture coordinate respectively.
@@ -40,7 +44,7 @@ class BlenderDisplayShader {
protected:
/* Get program of this display shader.
* NOTE: The shader needs to be bound to have access to this. */
virtual uint get_shader_program() = 0;
virtual GPUShader *get_shader_program() = 0;
/* Cached values of various OpenGL resources. */
int position_attribute_location_ = -1;
@@ -51,16 +55,16 @@ class BlenderDisplayShader {
* display space shader. */
class BlenderFallbackDisplayShader : public BlenderDisplayShader {
public:
virtual void bind(int width, int height) override;
virtual GPUShader *bind(int width, int height) override;
virtual void unbind() override;
protected:
virtual uint get_shader_program() override;
virtual GPUShader *get_shader_program() override;
void create_shader_if_needed();
void destroy_shader();
uint shader_program_ = 0;
GPUShader *shader_program_ = 0;
int image_texture_location_ = -1;
int fullscreen_location_ = -1;
@@ -73,17 +77,17 @@ class BlenderDisplaySpaceShader : public BlenderDisplayShader {
public:
BlenderDisplaySpaceShader(BL::RenderEngine &b_engine, BL::Scene &b_scene);
virtual void bind(int width, int height) override;
virtual GPUShader *bind(int width, int height) override;
virtual void unbind() override;
protected:
virtual uint get_shader_program() override;
virtual GPUShader *get_shader_program() override;
BL::RenderEngine b_engine_;
BL::Scene &b_scene_;
/* Cached values of various OpenGL resources. */
uint shader_program_ = 0;
GPUShader *shader_program_ = nullptr;
};
/* Display driver implementation which is specific for Blender viewport integration. */
@@ -122,6 +126,9 @@ class BlenderDisplayDriver : public DisplayDriver {
void gpu_context_lock();
void gpu_context_unlock();
/* Create GPU resources used by the display driver. */
bool gpu_resources_create();
/* Destroy all GPU resources which are being used by this object. */
void gpu_resources_destroy();
@@ -137,8 +144,8 @@ class BlenderDisplayDriver : public DisplayDriver {
struct Tiles;
unique_ptr<Tiles> tiles_;
void *gl_render_sync_ = nullptr;
void *gl_upload_sync_ = nullptr;
GPUFence *gpu_render_sync_ = nullptr;
GPUFence *gpu_upload_sync_ = nullptr;
float2 zoom_ = make_float2(1.0f, 1.0f);
};

View File

@@ -1085,11 +1085,11 @@ static void create_subd_mesh(Scene *scene,
const int edges_num = b_mesh.edges.length();
if (edges_num != 0 && b_mesh.edge_creases.length() > 0) {
size_t num_creases = 0;
const float *creases = static_cast<float *>(b_mesh.edge_creases[0].ptr.data);
BL::MeshEdgeCreaseLayer creases = b_mesh.edge_creases[0];
size_t num_creases = 0;
for (int i = 0; i < edges_num; i++) {
if (creases[i] != 0.0f) {
if (creases.data[i].value() != 0.0f) {
num_creases++;
}
}
@@ -1098,17 +1098,18 @@ static void create_subd_mesh(Scene *scene,
const MEdge *edges = static_cast<MEdge *>(b_mesh.edges[0].ptr.data);
for (int i = 0; i < edges_num; i++) {
if (creases[i] != 0.0f) {
const float crease = creases.data[i].value();
if (crease != 0.0f) {
const MEdge &b_edge = edges[i];
mesh->add_edge_crease(b_edge.v1, b_edge.v2, creases[i]);
mesh->add_edge_crease(b_edge.v1, b_edge.v2, crease);
}
}
}
for (BL::MeshVertexCreaseLayer &c : b_mesh.vertex_creases) {
for (int i = 0; i < c.data.length(); ++i) {
if (c.data[i].value() != 0.0f) {
mesh->add_vertex_crease(i, c.data[i].value());
}
for (BL::MeshVertexCreaseLayer &c : b_mesh.vertex_creases) {
for (int i = 0; i < c.data.length(); ++i) {
if (c.data[i].value() != 0.0f) {
mesh->add_vertex_crease(i, c.data[i].value());
}
}
}

View File

@@ -18,7 +18,6 @@
#include "util/guiding.h"
#include "util/log.h"
#include "util/md5.h"
#include "util/opengl.h"
#include "util/openimagedenoise.h"
#include "util/path.h"
#include "util/string.h"
@@ -26,6 +25,8 @@
#include "util/tbb.h"
#include "util/types.h"
#include "GPU_state.h"
#ifdef WITH_OSL
# include "scene/osl.h"
@@ -337,7 +338,7 @@ static PyObject *view_draw_func(PyObject * /*self*/, PyObject *args)
if (PyLong_AsVoidPtr(pyrv3d)) {
/* 3d view drawing */
int viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
GPU_viewport_size_get_i(viewport);
session->view_draw(viewport[2], viewport[3]);
}
@@ -478,6 +479,7 @@ static PyObject *osl_update_node_func(PyObject * /*self*/, PyObject *args)
/* Read metadata. */
bool is_bool_param = false;
bool hide_value = !param->validdefault;
ustring param_label = param->name;
for (const OSL::OSLQuery::Parameter &metadata : param->metadata) {
@@ -487,6 +489,9 @@ static PyObject *osl_update_node_func(PyObject * /*self*/, PyObject *args)
if (metadata.sdefault[0] == "boolean" || metadata.sdefault[0] == "checkBox") {
is_bool_param = true;
}
else if (metadata.sdefault[0] == "null") {
hide_value = true;
}
}
else if (metadata.name == "label") {
/* Socket label. */
@@ -596,6 +601,9 @@ static PyObject *osl_update_node_func(PyObject * /*self*/, PyObject *args)
if (b_sock.name() != param_label) {
b_sock.name(param_label.string());
}
if (b_sock.hide_value() != hide_value) {
b_sock.hide_value(hide_value);
}
used_sockets.insert(b_sock.ptr.data);
found_existing = true;
}
@@ -635,6 +643,8 @@ static PyObject *osl_update_node_func(PyObject * /*self*/, PyObject *args)
set_boolean(b_sock.ptr, "default_value", default_boolean);
}
b_sock.hide_value(hide_value);
used_sockets.insert(b_sock.ptr.data);
}
}

View File

@@ -559,11 +559,6 @@ static bool bake_setup_pass(Scene *scene, const string &bake_type_str, const int
0);
integrator->set_use_emission((bake_filter & BL::BakeSettings::pass_filter_EMIT) != 0);
}
/* Shadow pass. */
else if (strcmp(bake_type, "SHADOW") == 0) {
type = PASS_SHADOW;
use_direct_light = true;
}
/* Light component passes. */
else if (strcmp(bake_type, "DIFFUSE") == 0) {
if ((bake_filter & BL::BakeSettings::pass_filter_DIRECT) &&

View File

@@ -61,6 +61,12 @@ static DisplacementMethod get_displacement_method(PointerRNA &ptr)
ptr, "displacement_method", DISPLACE_NUM_METHODS, DISPLACE_BUMP);
}
static EmissionSampling get_emission_sampling(PointerRNA &ptr)
{
return (EmissionSampling)get_enum(
ptr, "emission_sampling", EMISSION_SAMPLING_NUM, EMISSION_SAMPLING_AUTO);
}
static int validate_enum_value(int value, int num_values, int default_value)
{
if (value >= num_values) {
@@ -1559,7 +1565,7 @@ void BlenderSync::sync_materials(BL::Depsgraph &b_depsgraph, bool update_all)
/* settings */
PointerRNA cmat = RNA_pointer_get(&b_mat.ptr, "cycles");
shader->set_use_mis(get_boolean(cmat, "sample_as_light"));
shader->set_emission_sampling_method(get_emission_sampling(cmat));
shader->set_use_transparent_shadow(get_boolean(cmat, "use_transparent_shadow"));
shader->set_heterogeneous_volume(!get_boolean(cmat, "homogeneous_volume"));
shader->set_volume_sampling_method(get_volume_sampling(cmat));

View File

@@ -26,7 +26,6 @@
#include "util/foreach.h"
#include "util/hash.h"
#include "util/log.h"
#include "util/opengl.h"
#include "util/openimagedenoise.h"
CCL_NAMESPACE_BEGIN
@@ -348,7 +347,14 @@ void BlenderSync::sync_integrator(BL::ViewLayer &b_view_layer, bool background)
integrator->set_motion_blur(view_layer.use_motion_blur);
}
integrator->set_light_sampling_threshold(get_float(cscene, "light_sampling_threshold"));
bool use_light_tree = get_boolean(cscene, "use_light_tree");
integrator->set_use_light_tree(use_light_tree);
integrator->set_light_sampling_threshold(
(use_light_tree) ? 0.0f : get_float(cscene, "light_sampling_threshold"));
if (integrator->use_light_tree_is_modified()) {
scene->light_manager->tag_update(scene, LightManager::UPDATE_ALL);
}
SamplingPattern sampling_pattern = (SamplingPattern)get_enum(
cscene, "sampling_pattern", SAMPLING_NUM_PATTERNS, SAMPLING_PATTERN_PMJ);
@@ -617,7 +623,6 @@ static bool get_known_pass_type(BL::RenderPass &b_pass, PassType &type, PassMode
MAP_PASS("Emit", PASS_EMISSION, false);
MAP_PASS("Env", PASS_BACKGROUND, false);
MAP_PASS("AO", PASS_AO, false);
MAP_PASS("Shadow", PASS_SHADOW, false);
MAP_PASS("BakePrimitive", PASS_BAKE_PRIMITIVE, false);
MAP_PASS("BakeDifferential", PASS_BAKE_DIFFERENTIAL, false);

View File

@@ -1,584 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright 2011-2022 Blender Foundation
###########################################################################
# Helper macros
###########################################################################
macro(_set_default variable value)
if(NOT ${variable})
set(${variable} ${value})
endif()
endmacro()
###########################################################################
# Precompiled libraries detection
#
# Use precompiled libraries from Blender repository
###########################################################################
if(CYCLES_STANDALONE_REPOSITORY)
if(APPLE)
if("${CMAKE_OSX_ARCHITECTURES}" STREQUAL "x86_64")
set(_cycles_lib_dir "${CMAKE_SOURCE_DIR}/../lib/darwin")
else()
set(_cycles_lib_dir "${CMAKE_SOURCE_DIR}/../lib/darwin_arm64")
endif()
# Always use system zlib
find_package(ZLIB REQUIRED)
elseif(WIN32)
if(CMAKE_CL_64)
set(_cycles_lib_dir "${CMAKE_SOURCE_DIR}/../lib/win64_vc15")
else()
message(FATAL_ERROR "Unsupported Visual Studio Version")
endif()
else()
# Path to a locally compiled libraries.
set(LIBDIR_NAME ${CMAKE_SYSTEM_NAME}_${CMAKE_SYSTEM_PROCESSOR})
string(TOLOWER ${LIBDIR_NAME} LIBDIR_NAME)
set(LIBDIR_NATIVE_ABI ${CMAKE_SOURCE_DIR}/../lib/${LIBDIR_NAME})
# Path to precompiled libraries with known CentOS 7 ABI.
set(LIBDIR_CENTOS7_ABI ${CMAKE_SOURCE_DIR}/../lib/linux_centos7_x86_64)
# Choose the best suitable libraries.
if(EXISTS ${LIBDIR_NATIVE_ABI})
set(_cycles_lib_dir ${LIBDIR_NATIVE_ABI})
elseif(EXISTS ${LIBDIR_CENTOS7_ABI})
set(_cycles_lib_dir ${LIBDIR_CENTOS7_ABI})
set(WITH_CXX11_ABI OFF)
if(CMAKE_COMPILER_IS_GNUCC AND
CMAKE_C_COMPILER_VERSION VERSION_LESS 9.3)
message(FATAL_ERROR "GCC version must be at least 9.3 for precompiled libraries, found ${CMAKE_C_COMPILER_VERSION}")
endif()
endif()
if(DEFINED _cycles_lib_dir)
message(STATUS "Using precompiled libraries at ${_cycles_lib_dir}")
endif()
# Avoid namespace pollustion.
unset(LIBDIR_NATIVE_ABI)
unset(LIBDIR_CENTOS7_ABI)
endif()
if(EXISTS ${_cycles_lib_dir})
_set_default(ALEMBIC_ROOT_DIR "${_cycles_lib_dir}/alembic")
_set_default(BOOST_ROOT "${_cycles_lib_dir}/boost")
_set_default(BLOSC_ROOT_DIR "${_cycles_lib_dir}/blosc")
_set_default(EMBREE_ROOT_DIR "${_cycles_lib_dir}/embree")
_set_default(EPOXY_ROOT_DIR "${_cycles_lib_dir}/epoxy")
_set_default(IMATH_ROOT_DIR "${_cycles_lib_dir}/imath")
_set_default(GLEW_ROOT_DIR "${_cycles_lib_dir}/glew")
_set_default(JPEG_ROOT "${_cycles_lib_dir}/jpeg")
_set_default(LLVM_ROOT_DIR "${_cycles_lib_dir}/llvm")
_set_default(CLANG_ROOT_DIR "${_cycles_lib_dir}/llvm")
_set_default(NANOVDB_ROOT_DIR "${_cycles_lib_dir}/openvdb")
_set_default(OPENCOLORIO_ROOT_DIR "${_cycles_lib_dir}/opencolorio")
_set_default(OPENEXR_ROOT_DIR "${_cycles_lib_dir}/openexr")
_set_default(OPENIMAGEDENOISE_ROOT_DIR "${_cycles_lib_dir}/openimagedenoise")
_set_default(OPENIMAGEIO_ROOT_DIR "${_cycles_lib_dir}/openimageio")
_set_default(OPENJPEG_ROOT_DIR "${_cycles_lib_dir}/openjpeg")
_set_default(OPENSUBDIV_ROOT_DIR "${_cycles_lib_dir}/opensubdiv")
_set_default(OPENVDB_ROOT_DIR "${_cycles_lib_dir}/openvdb")
_set_default(OSL_ROOT_DIR "${_cycles_lib_dir}/osl")
_set_default(PNG_ROOT "${_cycles_lib_dir}/png")
_set_default(PUGIXML_ROOT_DIR "${_cycles_lib_dir}/pugixml")
_set_default(SDL2_ROOT_DIR "${_cycles_lib_dir}/sdl")
_set_default(TBB_ROOT_DIR "${_cycles_lib_dir}/tbb")
_set_default(TIFF_ROOT "${_cycles_lib_dir}/tiff")
_set_default(USD_ROOT_DIR "${_cycles_lib_dir}/usd")
_set_default(WEBP_ROOT_DIR "${_cycles_lib_dir}/webp")
_set_default(ZLIB_ROOT "${_cycles_lib_dir}/zlib")
if(WIN32)
set(LEVEL_ZERO_ROOT_DIR ${_cycles_lib_dir}/level_zero)
else()
set(LEVEL_ZERO_ROOT_DIR ${_cycles_lib_dir}/level-zero)
endif()
_set_default(SYCL_ROOT_DIR "${_cycles_lib_dir}/dpcpp")
# Ignore system libraries
set(CMAKE_IGNORE_PATH "${CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES};${CMAKE_SYSTEM_INCLUDE_PATH};${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES};${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}")
else()
unset(_cycles_lib_dir)
endif()
endif()
###########################################################################
# Zlib
###########################################################################
if(CYCLES_STANDALONE_REPOSITORY)
if(MSVC AND EXISTS ${_cycles_lib_dir})
set(ZLIB_INCLUDE_DIRS ${_cycles_lib_dir}/zlib/include)
set(ZLIB_LIBRARIES ${_cycles_lib_dir}/zlib/lib/libz_st.lib)
set(ZLIB_INCLUDE_DIR ${_cycles_lib_dir}/zlib/include)
set(ZLIB_LIBRARY ${_cycles_lib_dir}/zlib/lib/libz_st.lib)
set(ZLIB_DIR ${_cycles_lib_dir}/zlib)
set(ZLIB_FOUND ON)
elseif(NOT APPLE)
find_package(ZLIB REQUIRED)
endif()
endif()
###########################################################################
# PThreads
###########################################################################
if(CYCLES_STANDALONE_REPOSITORY)
if(MSVC AND EXISTS ${_cycles_lib_dir})
set(PTHREADS_LIBRARIES "${_cycles_lib_dir}/pthreads/lib/pthreadVC3.lib")
include_directories("${_cycles_lib_dir}/pthreads/include")
else()
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
find_package(Threads REQUIRED)
set(PTHREADS_LIBRARIES ${CMAKE_THREAD_LIBS_INIT})
endif()
endif()
###########################################################################
# OpenImageIO and image libraries
###########################################################################
if(CYCLES_STANDALONE_REPOSITORY)
if(MSVC AND EXISTS ${_cycles_lib_dir})
add_definitions(
# OIIO changed the name of this define in newer versions
# we define both, so it would work with both old and new
# versions.
-DOIIO_STATIC_BUILD
-DOIIO_STATIC_DEFINE
)
set(OPENIMAGEIO_INCLUDE_DIR ${OPENIMAGEIO_ROOT_DIR}/include)
set(OPENIMAGEIO_INCLUDE_DIRS ${OPENIMAGEIO_INCLUDE_DIR} ${OPENIMAGEIO_INCLUDE_DIR}/OpenImageIO)
# Special exceptions for libraries which needs explicit debug version
set(OPENIMAGEIO_LIBRARIES
optimized ${OPENIMAGEIO_ROOT_DIR}/lib/OpenImageIO.lib
optimized ${OPENIMAGEIO_ROOT_DIR}/lib/OpenImageIO_Util.lib
debug ${OPENIMAGEIO_ROOT_DIR}/lib/OpenImageIO_d.lib
debug ${OPENIMAGEIO_ROOT_DIR}/lib/OpenImageIO_Util_d.lib
)
set(PUGIXML_INCLUDE_DIR ${PUGIXML_ROOT_DIR}/include)
set(PUGIXML_LIBRARIES
optimized ${PUGIXML_ROOT_DIR}/lib/pugixml.lib
debug ${PUGIXML_ROOT_DIR}/lib/pugixml_d.lib
)
else()
find_package(OpenImageIO REQUIRED)
if(OPENIMAGEIO_PUGIXML_FOUND)
set(PUGIXML_INCLUDE_DIR "${OPENIMAGEIO_INCLUDE_DIR}/OpenImageIO")
set(PUGIXML_LIBRARIES "")
else()
find_package(PugiXML REQUIRED)
endif()
endif()
# Dependencies
if(MSVC AND EXISTS ${_cycles_lib_dir})
set(OPENJPEG_INCLUDE_DIR ${OPENJPEG}/include/openjpeg-2.3)
set(OPENJPEG_LIBRARIES ${_cycles_lib_dir}/openjpeg/lib/openjp2${CMAKE_STATIC_LIBRARY_SUFFIX})
else()
find_package(OpenJPEG REQUIRED)
endif()
find_package(JPEG REQUIRED)
find_package(TIFF REQUIRED)
find_package(WebP)
if(EXISTS ${_cycles_lib_dir})
set(PNG_NAMES png16 libpng16 png libpng)
endif()
find_package(PNG REQUIRED)
endif()
###########################################################################
# OpenEXR
###########################################################################
if(CYCLES_STANDALONE_REPOSITORY)
if(MSVC AND EXISTS ${_cycles_lib_dir})
set(OPENEXR_INCLUDE_DIR ${OPENEXR_ROOT_DIR}/include)
set(OPENEXR_INCLUDE_DIRS ${OPENEXR_INCLUDE_DIR} ${OPENEXR_ROOT_DIR}/include/OpenEXR ${IMATH_ROOT_DIR}/include ${IMATH_ROOT_DIR}/include/Imath)
set(OPENEXR_LIBRARIES
optimized ${OPENEXR_ROOT_DIR}/lib/OpenEXR_s.lib
optimized ${OPENEXR_ROOT_DIR}/lib/OpenEXRCore_s.lib
optimized ${OPENEXR_ROOT_DIR}/lib/Iex_s.lib
optimized ${IMATH_ROOT_DIR}/lib/Imath_s.lib
optimized ${OPENEXR_ROOT_DIR}/lib/IlmThread_s.lib
debug ${OPENEXR_ROOT_DIR}/lib/OpenEXR_s_d.lib
debug ${OPENEXR_ROOT_DIR}/lib/OpenEXRCore_s_d.lib
debug ${OPENEXR_ROOT_DIR}/lib/Iex_s_d.lib
debug ${IMATH_ROOT_DIR}/lib/Imath_s_d.lib
debug ${OPENEXR_ROOT_DIR}/lib/IlmThread_s_d.lib
)
else()
find_package(OpenEXR REQUIRED)
endif()
endif()
###########################################################################
# OpenShadingLanguage & LLVM
###########################################################################
if(CYCLES_STANDALONE_REPOSITORY AND WITH_CYCLES_OSL)
if(EXISTS ${_cycles_lib_dir})
set(LLVM_STATIC ON)
endif()
if(MSVC AND EXISTS ${_cycles_lib_dir})
# TODO(sergey): On Windows llvm-config doesn't give proper results for the
# library names, use hardcoded libraries for now.
file(GLOB _llvm_libs_release ${LLVM_ROOT_DIR}/lib/*.lib)
file(GLOB _llvm_libs_debug ${LLVM_ROOT_DIR}/debug/lib/*.lib)
set(_llvm_libs)
foreach(_llvm_lib_path ${_llvm_libs_release})
get_filename_component(_llvm_lib_name ${_llvm_lib_path} ABSOLUTE)
list(APPEND _llvm_libs optimized ${_llvm_lib_name})
endforeach()
foreach(_llvm_lib_path ${_llvm_libs_debug})
get_filename_component(_llvm_lib_name ${_llvm_lib_path} ABSOLUTE)
list(APPEND _llvm_libs debug ${_llvm_lib_name})
endforeach()
set(LLVM_LIBRARY ${_llvm_libs})
unset(_llvm_lib_name)
unset(_llvm_lib_path)
unset(_llvm_libs)
unset(_llvm_libs_debug)
unset(_llvm_libs_release)
set(OSL_INCLUDE_DIR ${OSL_ROOT_DIR}/include)
set(OSL_LIBRARIES
optimized ${OSL_ROOT_DIR}/lib/oslcomp.lib
optimized ${OSL_ROOT_DIR}/lib/oslexec.lib
optimized ${OSL_ROOT_DIR}/lib/oslquery.lib
debug ${OSL_ROOT_DIR}/lib/oslcomp_d.lib
debug ${OSL_ROOT_DIR}/lib/oslexec_d.lib
debug ${OSL_ROOT_DIR}/lib/oslquery_d.lib
${PUGIXML_LIBRARIES}
)
find_program(OSL_COMPILER NAMES oslc PATHS ${OSL_ROOT_DIR}/bin)
else()
find_package(OSL REQUIRED)
find_package(LLVM REQUIRED)
find_package(Clang REQUIRED)
endif()
endif()
###########################################################################
# OpenPGL
###########################################################################
if(CYCLES_STANDALONE_REPOSITORY AND WITH_CYCLES_PATH_GUIDING)
if(NOT openpgl_DIR AND EXISTS ${_cycles_lib_dir})
set(openpgl_DIR ${_cycles_lib_dir}/openpgl/lib/cmake/openpgl)
endif()
find_package(openpgl QUIET)
if(openpgl_FOUND)
if(WIN32)
get_target_property(OPENPGL_LIBRARIES_RELEASE openpgl::openpgl LOCATION_RELEASE)
get_target_property(OPENPGL_LIBRARIES_DEBUG openpgl::openpgl LOCATION_DEBUG)
set(OPENPGL_LIBRARIES optimized ${OPENPGL_LIBRARIES_RELEASE} debug ${OPENPGL_LIBRARIES_DEBUG})
else()
get_target_property(OPENPGL_LIBRARIES openpgl::openpgl LOCATION)
endif()
get_target_property(OPENPGL_INCLUDE_DIR openpgl::openpgl INTERFACE_INCLUDE_DIRECTORIES)
else()
set_and_warn_library_found("OpenPGL" openpgl_FOUND WITH_CYCLES_PATH_GUIDING)
endif()
endif()
###########################################################################
# OpenColorIO
###########################################################################
if(CYCLES_STANDALONE_REPOSITORY AND WITH_CYCLES_OPENCOLORIO)
set(WITH_OPENCOLORIO ON)
if(NOT USD_OVERRIDE_OPENCOLORIO)
if(MSVC AND EXISTS ${_cycles_lib_dir})
set(OPENCOLORIO_INCLUDE_DIRS ${OPENCOLORIO_ROOT_DIR}/include)
set(OPENCOLORIO_LIBRARIES
optimized ${OPENCOLORIO_ROOT_DIR}/lib/OpenColorIO.lib
optimized ${OPENCOLORIO_ROOT_DIR}/lib/libyaml-cpp.lib
optimized ${OPENCOLORIO_ROOT_DIR}/lib/libexpatMD.lib
optimized ${OPENCOLORIO_ROOT_DIR}/lib/pystring.lib
debug ${OPENCOLORIO_ROOT_DIR}/lib/OpencolorIO_d.lib
debug ${OPENCOLORIO_ROOT_DIR}/lib/libyaml-cpp_d.lib
debug ${OPENCOLORIO_ROOT_DIR}/lib/libexpatdMD.lib
debug ${OPENCOLORIO_ROOT_DIR}/lib/pystring_d.lib
)
else()
find_package(OpenColorIO REQUIRED)
endif()
endif()
endif()
###########################################################################
# Boost
###########################################################################
if(CYCLES_STANDALONE_REPOSITORY)
if(EXISTS ${_cycles_lib_dir})
if(MSVC)
set(Boost_USE_STATIC_RUNTIME OFF)
set(Boost_USE_MULTITHREADED ON)
set(Boost_USE_STATIC_LIBS ON)
else()
set(BOOST_LIBRARYDIR ${_cycles_lib_dir}/boost/lib)
set(Boost_NO_BOOST_CMAKE ON)
set(Boost_NO_SYSTEM_PATHS ON)
endif()
endif()
if(MSVC AND EXISTS ${_cycles_lib_dir})
set(BOOST_INCLUDE_DIR ${BOOST_ROOT}/include)
set(BOOST_VERSION_HEADER ${BOOST_INCLUDE_DIR}/boost/version.hpp)
if(EXISTS ${BOOST_VERSION_HEADER})
file(STRINGS "${BOOST_VERSION_HEADER}" BOOST_LIB_VERSION REGEX "#define BOOST_LIB_VERSION ")
if(BOOST_LIB_VERSION MATCHES "#define BOOST_LIB_VERSION \"([0-9_]+)\"")
set(BOOST_VERSION "${CMAKE_MATCH_1}")
endif()
endif()
if(NOT BOOST_VERSION)
message(FATAL_ERROR "Unable to determine Boost version")
endif()
set(BOOST_POSTFIX "vc142-mt-x64-${BOOST_VERSION}.lib")
set(BOOST_DEBUG_POSTFIX "vc142-mt-gd-x64-${BOOST_VERSION}.lib")
set(BOOST_LIBRARIES
optimized ${BOOST_ROOT}/lib/libboost_date_time-${BOOST_POSTFIX}
optimized ${BOOST_ROOT}/lib/libboost_iostreams-${BOOST_POSTFIX}
optimized ${BOOST_ROOT}/lib/libboost_filesystem-${BOOST_POSTFIX}
optimized ${BOOST_ROOT}/lib/libboost_regex-${BOOST_POSTFIX}
optimized ${BOOST_ROOT}/lib/libboost_system-${BOOST_POSTFIX}
optimized ${BOOST_ROOT}/lib/libboost_thread-${BOOST_POSTFIX}
optimized ${BOOST_ROOT}/lib/libboost_chrono-${BOOST_POSTFIX}
debug ${BOOST_ROOT}/lib/libboost_date_time-${BOOST_DEBUG_POSTFIX}
debug ${BOOST_ROOT}/lib/libboost_iostreams-${BOOST_DEBUG_POSTFIX}
debug ${BOOST_ROOT}/lib/libboost_filesystem-${BOOST_DEBUG_POSTFIX}
debug ${BOOST_ROOT}/lib/libboost_regex-${BOOST_DEBUG_POSTFIX}
debug ${BOOST_ROOT}/lib/libboost_system-${BOOST_DEBUG_POSTFIX}
debug ${BOOST_ROOT}/lib/libboost_thread-${BOOST_DEBUG_POSTFIX}
debug ${BOOST_ROOT}/lib/libboost_chrono-${BOOST_DEBUG_POSTFIX}
)
if(WITH_CYCLES_OSL)
set(BOOST_LIBRARIES ${BOOST_LIBRARIES}
optimized ${BOOST_ROOT}/lib/libboost_wave-${BOOST_POSTFIX}
debug ${BOOST_ROOT}/lib/libboost_wave-${BOOST_DEBUG_POSTFIX})
endif()
else()
set(__boost_packages iostreams filesystem regex system thread date_time)
if(WITH_CYCLES_OSL)
list(APPEND __boost_packages wave)
endif()
find_package(Boost 1.48 COMPONENTS ${__boost_packages} REQUIRED)
if(NOT Boost_FOUND)
# Try to find non-multithreaded if -mt not found, this flag
# doesn't matter for us, it has nothing to do with thread
# safety, but keep it to not disturb build setups.
set(Boost_USE_MULTITHREADED OFF)
find_package(Boost 1.48 COMPONENTS ${__boost_packages})
endif()
unset(__boost_packages)
set(BOOST_INCLUDE_DIR ${Boost_INCLUDE_DIRS})
set(BOOST_LIBRARIES ${Boost_LIBRARIES})
set(BOOST_LIBPATH ${Boost_LIBRARY_DIRS})
endif()
set(BOOST_DEFINITIONS "-DBOOST_ALL_NO_LIB ${BOOST_DEFINITIONS}")
endif()
###########################################################################
# Embree
###########################################################################
if(CYCLES_STANDALONE_REPOSITORY AND WITH_CYCLES_EMBREE)
if(MSVC AND EXISTS ${_cycles_lib_dir})
set(EMBREE_INCLUDE_DIRS ${EMBREE_ROOT_DIR}/include)
set(EMBREE_LIBRARIES
optimized ${EMBREE_ROOT_DIR}/lib/embree3.lib
optimized ${EMBREE_ROOT_DIR}/lib/embree_avx2.lib
optimized ${EMBREE_ROOT_DIR}/lib/embree_avx.lib
optimized ${EMBREE_ROOT_DIR}/lib/embree_sse42.lib
optimized ${EMBREE_ROOT_DIR}/lib/lexers.lib
optimized ${EMBREE_ROOT_DIR}/lib/math.lib
optimized ${EMBREE_ROOT_DIR}/lib/simd.lib
optimized ${EMBREE_ROOT_DIR}/lib/tasking.lib
optimized ${EMBREE_ROOT_DIR}/lib/sys.lib
debug ${EMBREE_ROOT_DIR}/lib/embree3_d.lib
debug ${EMBREE_ROOT_DIR}/lib/embree_avx2_d.lib
debug ${EMBREE_ROOT_DIR}/lib/embree_avx_d.lib
debug ${EMBREE_ROOT_DIR}/lib/embree_sse42_d.lib
debug ${EMBREE_ROOT_DIR}/lib/lexers_d.lib
debug ${EMBREE_ROOT_DIR}/lib/math_d.lib
debug ${EMBREE_ROOT_DIR}/lib/simd_d.lib
debug ${EMBREE_ROOT_DIR}/lib/sys_d.lib
debug ${EMBREE_ROOT_DIR}/lib/tasking_d.lib
)
else()
find_package(Embree 3.8.0 REQUIRED)
endif()
endif()
###########################################################################
# Logging
###########################################################################
if(CYCLES_STANDALONE_REPOSITORY AND WITH_CYCLES_LOGGING)
find_package(Glog REQUIRED)
find_package(Gflags REQUIRED)
endif()
###########################################################################
# OpenSubdiv
###########################################################################
if(CYCLES_STANDALONE_REPOSITORY AND WITH_CYCLES_OPENSUBDIV)
set(WITH_OPENSUBDIV ON)
if(NOT USD_OVERRIDE_OPENSUBDIV)
if(MSVC AND EXISTS ${_cycles_lib_dir})
set(OPENSUBDIV_INCLUDE_DIRS ${OPENSUBDIV_ROOT_DIR}/include)
set(OPENSUBDIV_LIBRARIES
optimized ${OPENSUBDIV_ROOT_DIR}/lib/osdCPU.lib
optimized ${OPENSUBDIV_ROOT_DIR}/lib/osdGPU.lib
debug ${OPENSUBDIV_ROOT_DIR}/lib/osdCPU_d.lib
debug ${OPENSUBDIV_ROOT_DIR}/lib/osdGPU_d.lib
)
else()
find_package(OpenSubdiv REQUIRED)
endif()
endif()
endif()
###########################################################################
# OpenVDB
###########################################################################
if(CYCLES_STANDALONE_REPOSITORY AND WITH_CYCLES_OPENVDB)
set(WITH_OPENVDB ON)
set(OPENVDB_DEFINITIONS -DNOMINMAX -D_USE_MATH_DEFINES)
if(NOT USD_OVERRIDE_OPENVDB)
find_package(OpenVDB REQUIRED)
if(MSVC AND EXISTS ${_cycles_lib_dir})
set(BLOSC_LIBRARY
optimized ${BLOSC_ROOT_DIR}/lib/libblosc.lib
debug ${BLOSC_ROOT_DIR}/lib/libblosc_d.lib
)
else()
find_package(Blosc REQUIRED)
endif()
endif()
endif()
###########################################################################
# NanoVDB
###########################################################################
if(CYCLES_STANDALONE_REPOSITORY AND WITH_CYCLES_NANOVDB)
set(WITH_NANOVDB ON)
if(MSVC AND EXISTS ${_cycles_lib_dir})
set(NANOVDB_INCLUDE_DIR ${NANOVDB_ROOT_DIR}/include)
set(NANOVDB_INCLUDE_DIRS ${NANOVDB_INCLUDE_DIR})
else()
find_package(NanoVDB REQUIRED)
endif()
endif()
###########################################################################
# OpenImageDenoise
###########################################################################
if(CYCLES_STANDALONE_REPOSITORY AND WITH_CYCLES_OPENIMAGEDENOISE)
set(WITH_OPENIMAGEDENOISE ON)
if(MSVC AND EXISTS ${_cycles_lib_dir})
set(OPENIMAGEDENOISE_INCLUDE_DIRS ${OPENIMAGEDENOISE_ROOT_DIR}/include)
set(OPENIMAGEDENOISE_LIBRARIES
optimized ${OPENIMAGEDENOISE_ROOT_DIR}/lib/OpenImageDenoise.lib
optimized ${OPENIMAGEDENOISE_ROOT_DIR}/lib/common.lib
optimized ${OPENIMAGEDENOISE_ROOT_DIR}/lib/dnnl.lib
debug ${OPENIMAGEDENOISE_ROOT_DIR}/lib/OpenImageDenoise_d.lib
debug ${OPENIMAGEDENOISE_ROOT_DIR}/lib/common_d.lib
debug ${OPENIMAGEDENOISE_ROOT_DIR}/lib/dnnl_d.lib
)
else()
find_package(OpenImageDenoise REQUIRED)
endif()
endif()
###########################################################################
# TBB
###########################################################################
if(CYCLES_STANDALONE_REPOSITORY)
if(NOT USD_OVERRIDE_TBB)
if(MSVC AND EXISTS ${_cycles_lib_dir})
set(TBB_INCLUDE_DIRS ${TBB_ROOT_DIR}/include)
set(TBB_LIBRARIES
optimized ${TBB_ROOT_DIR}/lib/tbb.lib
debug ${TBB_ROOT_DIR}/lib/tbb_debug.lib
)
else()
find_package(TBB REQUIRED)
endif()
endif()
endif()
###########################################################################
# Epoxy
###########################################################################
if(CYCLES_STANDALONE_REPOSITORY)
if((WITH_CYCLES_STANDALONE AND WITH_CYCLES_STANDALONE_GUI) OR
WITH_CYCLES_HYDRA_RENDER_DELEGATE)
if(MSVC AND EXISTS ${_cycles_lib_dir})
set(Epoxy_LIBRARIES "${_cycles_lib_dir}/epoxy/lib/epoxy.lib")
set(Epoxy_INCLUDE_DIRS "${_cycles_lib_dir}/epoxy/include")
else()
find_package(Epoxy REQUIRED)
endif()
endif()
endif()
###########################################################################
# Alembic
###########################################################################
if(WITH_CYCLES_ALEMBIC)
if(CYCLES_STANDALONE_REPOSITORY)
if(MSVC AND EXISTS ${_cycles_lib_dir})
set(ALEMBIC_INCLUDE_DIRS ${_cycles_lib_dir}/alembic/include)
set(ALEMBIC_LIBRARIES
optimized ${_cycles_lib_dir}/alembic/lib/Alembic.lib
debug ${_cycles_lib_dir}/alembic/lib/Alembic_d.lib)
else()
find_package(Alembic REQUIRED)
endif()
set(WITH_ALEMBIC ON)
endif()
endif()
###########################################################################
# System Libraries
###########################################################################
# Detect system libraries again
if(EXISTS ${_cycles_lib_dir})
unset(CMAKE_IGNORE_PATH)
unset(_cycles_lib_dir)
endif()
###########################################################################
# SDL
###########################################################################
@@ -687,5 +109,3 @@ if(WITH_CYCLES_DEVICE_ONEAPI)
set(WITH_CYCLES_DEVICE_ONEAPI OFF)
endif()
endif()
unset(_cycles_lib_dir)

View File

@@ -38,7 +38,7 @@ class CUDADeviceGraphicsInterop : public DeviceGraphicsInterop {
CUDADevice *device_ = nullptr;
/* OpenGL PBO which is currently registered as the destination for the CUDA buffer. */
uint opengl_pbo_id_ = 0;
int64_t opengl_pbo_id_ = 0;
/* Buffer area in pixels of the corresponding PBO. */
int64_t buffer_area_ = 0;

View File

@@ -351,6 +351,7 @@ DeviceInfo Device::get_multi_device(const vector<DeviceInfo> &subdevices,
info.num = 0;
info.has_nanovdb = true;
info.has_light_tree = true;
info.has_osl = true;
info.has_guiding = true;
info.has_profiling = true;
@@ -399,6 +400,7 @@ DeviceInfo Device::get_multi_device(const vector<DeviceInfo> &subdevices,
/* Accumulate device info. */
info.has_nanovdb &= device.has_nanovdb;
info.has_light_tree &= device.has_light_tree;
info.has_osl &= device.has_osl;
info.has_guiding &= device.has_guiding;
info.has_profiling &= device.has_profiling;

View File

@@ -65,6 +65,7 @@ class DeviceInfo {
int num;
bool display_device; /* GPU is used as a display device. */
bool has_nanovdb; /* Support NanoVDB volumes. */
bool has_light_tree; /* Support light tree. */
bool has_osl; /* Support Open Shading Language. */
bool has_guiding; /* Support path guiding. */
bool has_profiling; /* Supports runtime collection of profiling info. */
@@ -84,6 +85,7 @@ class DeviceInfo {
cpu_threads = 0;
display_device = false;
has_nanovdb = false;
has_light_tree = true;
has_osl = false;
has_guiding = false;
has_profiling = false;

View File

@@ -137,6 +137,7 @@ void device_hip_info(vector<DeviceInfo> &devices)
info.num = num;
info.has_nanovdb = true;
info.has_light_tree = false;
info.denoisers = 0;
info.has_gpu_queue = true;

View File

@@ -36,7 +36,7 @@ class HIPDeviceGraphicsInterop : public DeviceGraphicsInterop {
HIPDevice *device_ = nullptr;
/* OpenGL PBO which is currently registered as the destination for the HIP buffer. */
uint opengl_pbo_id_ = 0;
int64_t opengl_pbo_id_ = 0;
/* Buffer area in pixels of the corresponding PBO. */
int64_t buffer_area_ = 0;

View File

@@ -117,6 +117,8 @@ class MetalDevice : public Device {
/* ------------------------------------------------------------------ */
/* low-level memory management */
bool max_working_set_exceeded(size_t safety_margin = 8 * 1024 * 1024) const;
MetalMem *generic_alloc(device_memory &mem);
void generic_copy_to(device_memory &mem);

View File

@@ -446,6 +446,14 @@ void MetalDevice::erase_allocation(device_memory &mem)
}
}
bool MetalDevice::max_working_set_exceeded(size_t safety_margin) const
{
/* We're allowed to allocate beyond the safe working set size, but then if all resources are made
* resident we will get command buffer failures at render time. */
size_t available = [mtlDevice recommendedMaxWorkingSetSize] - safety_margin;
return (stats.mem_used > available);
}
MetalDevice::MetalMem *MetalDevice::generic_alloc(device_memory &mem)
{
size_t size = mem.memory_size();
@@ -523,6 +531,11 @@ MetalDevice::MetalMem *MetalDevice::generic_alloc(device_memory &mem)
mmem->use_UMA = false;
}
if (max_working_set_exceeded()) {
set_error("System is out of GPU memory");
return nullptr;
}
return mmem;
}
@@ -921,9 +934,8 @@ void MetalDevice::tex_alloc(device_texture &mem)
<< string_human_readable_size(mem.memory_size()) << ")";
mtlTexture = [mtlDevice newTextureWithDescriptor:desc];
assert(mtlTexture);
if (!mtlTexture) {
set_error("System is out of GPU memory");
return;
}
@@ -955,7 +967,10 @@ void MetalDevice::tex_alloc(device_texture &mem)
<< string_human_readable_size(mem.memory_size()) << ")";
mtlTexture = [mtlDevice newTextureWithDescriptor:desc];
assert(mtlTexture);
if (!mtlTexture) {
set_error("System is out of GPU memory");
return;
}
[mtlTexture replaceRegion:MTLRegionMake2D(0, 0, mem.data_width, mem.data_height)
mipmapLevel:0
@@ -1017,6 +1032,10 @@ void MetalDevice::tex_alloc(device_texture &mem)
need_texture_info = true;
texture_info[slot].data = uint64_t(slot) | (sampler_index << 32);
if (max_working_set_exceeded()) {
set_error("System is out of GPU memory");
}
}
void MetalDevice::tex_free(device_texture &mem)
@@ -1077,6 +1096,10 @@ void MetalDevice::build_bvh(BVH *bvh, Progress &progress, bool refit)
}
}
}
if (max_working_set_exceeded()) {
set_error("System is out of GPU memory");
}
}
CCL_NAMESPACE_END

View File

@@ -31,6 +31,8 @@ bool device_oneapi_init()
* improves stability as of intel/LLVM SYCL-nightly/20220529.
* All these env variable can be set beforehand by end-users and
* will in that case -not- be overwritten. */
/* By default, enable only Level-Zero and if all devices are allowed, also CUDA and HIP.
* OpenCL backend isn't currently well supported. */
# ifdef _WIN32
if (getenv("SYCL_CACHE_PERSISTENT") == nullptr) {
_putenv_s("SYCL_CACHE_PERSISTENT", "1");
@@ -39,7 +41,12 @@ bool device_oneapi_init()
_putenv_s("SYCL_CACHE_THRESHOLD", "0");
}
if (getenv("SYCL_DEVICE_FILTER") == nullptr) {
_putenv_s("SYCL_DEVICE_FILTER", "level_zero");
if (getenv("CYCLES_ONEAPI_ALL_DEVICES") == nullptr) {
_putenv_s("SYCL_DEVICE_FILTER", "level_zero");
}
else {
_putenv_s("SYCL_DEVICE_FILTER", "level_zero,cuda,hip");
}
}
if (getenv("SYCL_ENABLE_PCI") == nullptr) {
_putenv_s("SYCL_ENABLE_PCI", "1");
@@ -50,7 +57,12 @@ bool device_oneapi_init()
# elif __linux__
setenv("SYCL_CACHE_PERSISTENT", "1", false);
setenv("SYCL_CACHE_THRESHOLD", "0", false);
setenv("SYCL_DEVICE_FILTER", "level_zero", false);
if (getenv("CYCLES_ONEAPI_ALL_DEVICES") == nullptr) {
setenv("SYCL_DEVICE_FILTER", "level_zero", false);
}
else {
setenv("SYCL_DEVICE_FILTER", "level_zero,cuda,hip", false);
}
setenv("SYCL_ENABLE_PCI", "1", false);
setenv("SYCL_PI_LEVEL_ZERO_USE_COPY_ENGINE_FOR_IN_ORDER_QUEUE", "0", false);
# endif

View File

@@ -430,9 +430,9 @@ void OneapiDevice::check_usm(SyclQueue *queue_, const void *usm_ptr, bool allow_
sycl::usm::alloc usm_type = get_pointer_type(usm_ptr, queue->get_context());
(void)usm_type;
assert(usm_type == sycl::usm::alloc::device ||
((device_type == sycl::info::device_type::cpu || allow_host) &&
usm_type == sycl::usm::alloc::host ||
usm_type == sycl::usm::alloc::unknown));
(usm_type == sycl::usm::alloc::host &&
(allow_host || device_type == sycl::info::device_type::cpu)) ||
usm_type == sycl::usm::alloc::unknown);
# else
/* Silence warning about unused arguments. */
(void)queue_;

View File

@@ -66,7 +66,9 @@ struct SocketType {
LINK_NORMAL = (1 << 8),
LINK_POSITION = (1 << 9),
LINK_TANGENT = (1 << 10),
DEFAULT_LINK_MASK = (1 << 4) | (1 << 5) | (1 << 6) | (1 << 7) | (1 << 8) | (1 << 9) | (1 << 10)
LINK_OSL_INITIALIZER = (1 << 11),
DEFAULT_LINK_MASK = (1 << 4) | (1 << 5) | (1 << 6) | (1 << 7) | (1 << 8) | (1 << 9) |
(1 << 10) | (1 << 11)
};
ustring name;

View File

@@ -285,10 +285,17 @@ set(SRC_KERNEL_INTEGRATOR_HEADERS
)
set(SRC_KERNEL_LIGHT_HEADERS
light/light.h
light/area.h
light/background.h
light/common.h
light/distant.h
light/distribution.h
light/light.h
light/point.h
light/sample.h
light/spot.h
light/tree.h
light/triangle.h
)
set(SRC_KERNEL_SAMPLE_HEADERS
@@ -466,6 +473,7 @@ if(WITH_CYCLES_CUDA_BINARIES)
if(WITH_CYCLES_DEBUG)
set(cuda_flags ${cuda_flags} -D WITH_CYCLES_DEBUG)
set(cuda_flags ${cuda_flags} --ptxas-options="-v")
endif()
set(_cuda_nvcc_args
@@ -473,7 +481,6 @@ if(WITH_CYCLES_CUDA_BINARIES)
${CUDA_NVCC_FLAGS}
--${format}
${CMAKE_CURRENT_SOURCE_DIR}${cuda_kernel_src}
--ptxas-options="-v"
${cuda_flags})
if(WITH_COMPILER_CCACHE AND CCACHE_PROGRAM)

View File

@@ -69,7 +69,7 @@ ccl_device int bsdf_diffuse_sample(ccl_private const ShaderClosure *sc,
ccl_device int bsdf_translucent_setup(ccl_private DiffuseBsdf *bsdf)
{
bsdf->type = CLOSURE_BSDF_TRANSLUCENT_ID;
return SD_BSDF | SD_BSDF_HAS_EVAL;
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_HAS_TRANSMISSION;
}
ccl_device Spectrum bsdf_translucent_eval(ccl_private const ShaderClosure *sc,

View File

@@ -34,7 +34,7 @@ ccl_device int bsdf_hair_transmission_setup(ccl_private HairBsdf *bsdf)
bsdf->type = CLOSURE_BSDF_HAIR_TRANSMISSION_ID;
bsdf->roughness1 = clamp(bsdf->roughness1, 0.001f, 1.0f);
bsdf->roughness2 = clamp(bsdf->roughness2, 0.001f, 1.0f);
return SD_BSDF | SD_BSDF_HAS_EVAL;
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_HAS_TRANSMISSION;
}
ccl_device Spectrum bsdf_hair_reflection_eval(ccl_private const ShaderClosure *sc,

View File

@@ -196,7 +196,7 @@ ccl_device int bsdf_principled_hair_setup(ccl_private ShaderData *sd,
bsdf->extra->geom = make_float4(Y.x, Y.y, Y.z, h);
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_NEEDS_LCG;
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_NEEDS_LCG | SD_BSDF_HAS_TRANSMISSION;
}
#endif /* __HAIR__ */

View File

@@ -346,7 +346,7 @@ ccl_device int bsdf_microfacet_ggx_refraction_setup(ccl_private MicrofacetBsdf *
bsdf->type = CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID;
return SD_BSDF | SD_BSDF_HAS_EVAL;
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_HAS_TRANSMISSION;
}
ccl_device void bsdf_microfacet_ggx_blur(ccl_private ShaderClosure *sc, float roughness)
@@ -776,7 +776,7 @@ ccl_device int bsdf_microfacet_beckmann_refraction_setup(ccl_private MicrofacetB
bsdf->alpha_y = bsdf->alpha_x;
bsdf->type = CLOSURE_BSDF_MICROFACET_BECKMANN_REFRACTION_ID;
return SD_BSDF | SD_BSDF_HAS_EVAL;
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_HAS_TRANSMISSION;
}
ccl_device void bsdf_microfacet_beckmann_blur(ccl_private ShaderClosure *sc, float roughness)

View File

@@ -559,7 +559,7 @@ ccl_device int bsdf_microfacet_multi_ggx_glass_setup(ccl_private MicrofacetBsdf
bsdf->type = CLOSURE_BSDF_MICROFACET_MULTI_GGX_GLASS_ID;
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_NEEDS_LCG;
return SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSDF_NEEDS_LCG | SD_BSDF_HAS_TRANSMISSION;
}
ccl_device int bsdf_microfacet_multi_ggx_glass_fresnel_setup(ccl_private MicrofacetBsdf *bsdf,

View File

@@ -60,6 +60,13 @@ KERNEL_DATA_ARRAY(KernelLight, lights)
KERNEL_DATA_ARRAY(float2, light_background_marginal_cdf)
KERNEL_DATA_ARRAY(float2, light_background_conditional_cdf)
/* light tree */
KERNEL_DATA_ARRAY(KernelLightTreeNode, light_tree_nodes)
KERNEL_DATA_ARRAY(KernelLightTreeEmitter, light_tree_emitters)
KERNEL_DATA_ARRAY(uint, light_to_tree)
KERNEL_DATA_ARRAY(uint, object_lookup_offset)
KERNEL_DATA_ARRAY(uint, triangle_to_tree)
/* particles */
KERNEL_DATA_ARRAY(KernelParticle, particles)

View File

@@ -23,24 +23,19 @@ KERNEL_STRUCT_MEMBER(background, int, volume_shader)
KERNEL_STRUCT_MEMBER(background, float, volume_step_size)
KERNEL_STRUCT_MEMBER(background, int, transparent)
KERNEL_STRUCT_MEMBER(background, float, transparent_roughness_squared_threshold)
/* Portal sampling. */
KERNEL_STRUCT_MEMBER(background, float, portal_weight)
KERNEL_STRUCT_MEMBER(background, int, num_portals)
KERNEL_STRUCT_MEMBER(background, int, portal_offset)
/* Sun sampling. */
KERNEL_STRUCT_MEMBER(background, float, sun_weight)
/* Importance map sampling. */
KERNEL_STRUCT_MEMBER(background, float, map_weight)
KERNEL_STRUCT_MEMBER(background, float, portal_weight)
KERNEL_STRUCT_MEMBER(background, int, map_res_x)
KERNEL_STRUCT_MEMBER(background, int, map_res_y)
/* Multiple importance sampling. */
KERNEL_STRUCT_MEMBER(background, int, use_mis)
/* Lightgroup. */
KERNEL_STRUCT_MEMBER(background, int, lightgroup)
/* Padding. */
KERNEL_STRUCT_MEMBER(background, int, pad1)
KERNEL_STRUCT_MEMBER(background, int, pad2)
KERNEL_STRUCT_MEMBER(background, int, pad3)
/* Light Index. */
KERNEL_STRUCT_MEMBER(background, int, light_index)
KERNEL_STRUCT_END(KernelBackground)
/* BVH: own BVH2 if no native device acceleration struct used. */
@@ -102,8 +97,6 @@ KERNEL_STRUCT_MEMBER(film, int, pass_emission)
KERNEL_STRUCT_MEMBER(film, int, pass_background)
KERNEL_STRUCT_MEMBER(film, int, pass_ao)
KERNEL_STRUCT_MEMBER(film, float, pass_alpha_threshold)
KERNEL_STRUCT_MEMBER(film, int, pass_shadow)
KERNEL_STRUCT_MEMBER(film, float, pass_shadow_scale)
KERNEL_STRUCT_MEMBER(film, int, pass_shadow_catcher)
KERNEL_STRUCT_MEMBER(film, int, pass_shadow_catcher_sample_count)
KERNEL_STRUCT_MEMBER(film, int, pass_shadow_catcher_matte)
@@ -137,9 +130,6 @@ KERNEL_STRUCT_MEMBER(film, int, use_approximate_shadow_catcher)
KERNEL_STRUCT_MEMBER(film, int, pass_guiding_color)
KERNEL_STRUCT_MEMBER(film, int, pass_guiding_probability)
KERNEL_STRUCT_MEMBER(film, int, pass_guiding_avg_roughness)
/* Padding. */
KERNEL_STRUCT_MEMBER(film, int, pad1)
KERNEL_STRUCT_MEMBER(film, int, pad2)
KERNEL_STRUCT_END(KernelFilm)
/* Integrator. */
@@ -147,10 +137,18 @@ KERNEL_STRUCT_END(KernelFilm)
KERNEL_STRUCT_BEGIN(KernelIntegrator, integrator)
/* Emission. */
KERNEL_STRUCT_MEMBER(integrator, int, use_direct_light)
KERNEL_STRUCT_MEMBER(integrator, int, use_light_mis)
KERNEL_STRUCT_MEMBER(integrator, int, use_light_tree)
KERNEL_STRUCT_MEMBER(integrator, int, num_lights)
KERNEL_STRUCT_MEMBER(integrator, int, num_distant_lights)
KERNEL_STRUCT_MEMBER(integrator, int, num_background_lights)
/* Portal sampling. */
KERNEL_STRUCT_MEMBER(integrator, int, num_portals)
KERNEL_STRUCT_MEMBER(integrator, int, portal_offset)
/* Flat light distribution. */
KERNEL_STRUCT_MEMBER(integrator, int, num_distribution)
KERNEL_STRUCT_MEMBER(integrator, int, num_all_lights)
KERNEL_STRUCT_MEMBER(integrator, float, pdf_triangles)
KERNEL_STRUCT_MEMBER(integrator, float, pdf_lights)
KERNEL_STRUCT_MEMBER(integrator, float, distribution_pdf_triangles)
KERNEL_STRUCT_MEMBER(integrator, float, distribution_pdf_lights)
KERNEL_STRUCT_MEMBER(integrator, float, light_inv_rr_threshold)
/* Bounces. */
KERNEL_STRUCT_MEMBER(integrator, int, min_bounce)
@@ -177,8 +175,6 @@ KERNEL_STRUCT_MEMBER(integrator, int, seed)
/* Clamp. */
KERNEL_STRUCT_MEMBER(integrator, float, sample_clamp_direct)
KERNEL_STRUCT_MEMBER(integrator, float, sample_clamp_indirect)
/* MIS. */
KERNEL_STRUCT_MEMBER(integrator, int, use_lamp_mis)
/* Caustics. */
KERNEL_STRUCT_MEMBER(integrator, int, use_caustics)
/* Sampling pattern. */
@@ -195,7 +191,6 @@ KERNEL_STRUCT_MEMBER(integrator, int, has_shadow_catcher)
KERNEL_STRUCT_MEMBER(integrator, int, filter_closures)
/* MIS debugging. */
KERNEL_STRUCT_MEMBER(integrator, int, direct_light_sampling_type)
/* Path Guiding */
KERNEL_STRUCT_MEMBER(integrator, float, surface_guiding_probability)
KERNEL_STRUCT_MEMBER(integrator, float, volume_guiding_probability)
@@ -210,7 +205,6 @@ KERNEL_STRUCT_MEMBER(integrator, int, use_guiding_mis_weights)
/* Padding. */
KERNEL_STRUCT_MEMBER(integrator, int, pad1)
KERNEL_STRUCT_MEMBER(integrator, int, pad2)
KERNEL_STRUCT_MEMBER(integrator, int, pad3)
KERNEL_STRUCT_END(KernelIntegrator)
/* SVM. For shader specialization. */

View File

@@ -314,11 +314,7 @@ ccl_gpu_kernel_threads(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE)
int kernel_index);
ccl_gpu_kernel_lambda_pass.kernel_index = kernel_index;
gpu_parallel_active_index_array(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE,
num_states,
indices,
num_indices,
ccl_gpu_kernel_lambda_pass);
gpu_parallel_active_index_array(num_states, indices, num_indices, ccl_gpu_kernel_lambda_pass);
}
ccl_gpu_kernel_postfix
@@ -333,11 +329,7 @@ ccl_gpu_kernel_threads(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE)
int kernel_index);
ccl_gpu_kernel_lambda_pass.kernel_index = kernel_index;
gpu_parallel_active_index_array(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE,
num_states,
indices,
num_indices,
ccl_gpu_kernel_lambda_pass);
gpu_parallel_active_index_array(num_states, indices, num_indices, ccl_gpu_kernel_lambda_pass);
}
ccl_gpu_kernel_postfix
@@ -349,11 +341,7 @@ ccl_gpu_kernel_threads(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE)
{
ccl_gpu_kernel_lambda(INTEGRATOR_STATE(state, path, queued_kernel) != 0);
gpu_parallel_active_index_array(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE,
num_states,
indices,
num_indices,
ccl_gpu_kernel_lambda_pass);
gpu_parallel_active_index_array(num_states, indices, num_indices, ccl_gpu_kernel_lambda_pass);
}
ccl_gpu_kernel_postfix
@@ -366,11 +354,8 @@ ccl_gpu_kernel_threads(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE)
{
ccl_gpu_kernel_lambda(INTEGRATOR_STATE(state, path, queued_kernel) == 0);
gpu_parallel_active_index_array(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE,
num_states,
indices + indices_offset,
num_indices,
ccl_gpu_kernel_lambda_pass);
gpu_parallel_active_index_array(
num_states, indices + indices_offset, num_indices, ccl_gpu_kernel_lambda_pass);
}
ccl_gpu_kernel_postfix
@@ -383,11 +368,8 @@ ccl_gpu_kernel_threads(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE)
{
ccl_gpu_kernel_lambda(INTEGRATOR_STATE(state, shadow_path, queued_kernel) == 0);
gpu_parallel_active_index_array(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE,
num_states,
indices + indices_offset,
num_indices,
ccl_gpu_kernel_lambda_pass);
gpu_parallel_active_index_array(
num_states, indices + indices_offset, num_indices, ccl_gpu_kernel_lambda_pass);
}
ccl_gpu_kernel_postfix
@@ -431,11 +413,7 @@ ccl_gpu_kernel_threads(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE)
int num_active_paths);
ccl_gpu_kernel_lambda_pass.num_active_paths = num_active_paths;
gpu_parallel_active_index_array(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE,
num_states,
indices,
num_indices,
ccl_gpu_kernel_lambda_pass);
gpu_parallel_active_index_array(num_states, indices, num_indices, ccl_gpu_kernel_lambda_pass);
}
ccl_gpu_kernel_postfix
@@ -469,11 +447,7 @@ ccl_gpu_kernel_threads(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE)
int num_active_paths);
ccl_gpu_kernel_lambda_pass.num_active_paths = num_active_paths;
gpu_parallel_active_index_array(GPU_PARALLEL_ACTIVE_INDEX_DEFAULT_BLOCK_SIZE,
num_states,
indices,
num_indices,
ccl_gpu_kernel_lambda_pass);
gpu_parallel_active_index_array(num_states, indices, num_indices, ccl_gpu_kernel_lambda_pass);
}
ccl_gpu_kernel_postfix

View File

@@ -56,7 +56,7 @@ void gpu_parallel_active_index_array_impl(const uint num_states,
const uint is_active = (state_index < num_states) ? is_active_op(state_index) : 0;
#else /* !__KERNEL__ONEAPI__ */
# ifndef __KERNEL_METAL__
template<uint blocksize, typename IsActiveOp>
template<typename IsActiveOp>
__device__
# endif
void
@@ -79,6 +79,10 @@ __device__
{
extern ccl_gpu_shared int warp_offset[];
# ifndef __KERNEL_METAL__
const uint blocksize = ccl_gpu_block_dim_x;
# endif
const uint thread_index = ccl_gpu_thread_idx_x;
const uint thread_warp = thread_index % ccl_gpu_warp_size;
@@ -149,7 +153,7 @@ __device__
#ifdef __KERNEL_METAL__
# define gpu_parallel_active_index_array(dummy, num_states, indices, num_indices, is_active_op) \
# define gpu_parallel_active_index_array(num_states, indices, num_indices, is_active_op) \
const uint is_active = (ccl_gpu_global_id_x() < num_states) ? \
is_active_op(ccl_gpu_global_id_x()) : \
0; \
@@ -167,15 +171,13 @@ __device__
simdgroup_offset)
#elif defined(__KERNEL_ONEAPI__)
# define gpu_parallel_active_index_array( \
blocksize, num_states, indices, num_indices, is_active_op) \
# define gpu_parallel_active_index_array(num_states, indices, num_indices, is_active_op) \
gpu_parallel_active_index_array_impl(num_states, indices, num_indices, is_active_op)
#else
# define gpu_parallel_active_index_array( \
blocksize, num_states, indices, num_indices, is_active_op) \
gpu_parallel_active_index_array_impl<blocksize>(num_states, indices, num_indices, is_active_op)
# define gpu_parallel_active_index_array(num_states, indices, num_indices, is_active_op) \
gpu_parallel_active_index_array_impl(num_states, indices, num_indices, is_active_op)
#endif

View File

@@ -157,4 +157,47 @@ ccl_device_inline void film_write_data_passes(KernelGlobals kg,
#endif
}
ccl_device_inline void film_write_data_passes_background(
KernelGlobals kg, IntegratorState state, ccl_global float *ccl_restrict render_buffer)
{
#ifdef __PASSES__
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
if (!(path_flag & PATH_RAY_TRANSPARENT_BACKGROUND)) {
return;
}
/* Don't write data passes for paths that were split off for shadow catchers
* to avoid double-counting. */
if (path_flag & PATH_RAY_SHADOW_CATCHER_PASS) {
return;
}
const int flag = kernel_data.film.pass_flag;
if (!(flag & PASS_ANY)) {
return;
}
if (!(path_flag & PATH_RAY_SINGLE_PASS_DONE)) {
ccl_global float *buffer = film_pass_pixel_render_buffer(kg, state, render_buffer);
if (INTEGRATOR_STATE(state, path, sample) == 0) {
if (flag & PASSMASK(DEPTH)) {
film_overwrite_pass_float(buffer + kernel_data.film.pass_depth, 0.0f);
}
if (flag & PASSMASK(OBJECT_ID)) {
film_overwrite_pass_float(buffer + kernel_data.film.pass_object_id, 0.0f);
}
if (flag & PASSMASK(MATERIAL_ID)) {
film_overwrite_pass_float(buffer + kernel_data.film.pass_material_id, 0.0f);
}
if (flag & PASSMASK(POSITION)) {
film_overwrite_pass_float3(buffer + kernel_data.film.pass_position, zero_float3());
}
}
}
#endif
}
CCL_NAMESPACE_END

View File

@@ -527,17 +527,6 @@ ccl_device_inline void film_write_direct_light(KernelGlobals kg,
film_write_pass_spectrum(buffer + pass_offset, contribution);
}
}
/* Write shadow pass. */
if (kernel_data.film.pass_shadow != PASS_UNUSED && (path_flag & PATH_RAY_SHADOW_FOR_LIGHT) &&
(path_flag & PATH_RAY_TRANSPARENT_BACKGROUND)) {
const Spectrum unshadowed_throughput = INTEGRATOR_STATE(
state, shadow_path, unshadowed_throughput);
const Spectrum shadowed_throughput = INTEGRATOR_STATE(state, shadow_path, throughput);
const Spectrum shadow = safe_divide(shadowed_throughput, unshadowed_throughput) *
kernel_data.film.pass_shadow_scale;
film_write_pass_spectrum(buffer + kernel_data.film.pass_shadow, shadow);
}
}
#endif
}

View File

@@ -11,10 +11,10 @@
#include "kernel/integrator/path_state.h"
#include "kernel/integrator/shadow_catcher.h"
#include "kernel/light/light.h"
#include "kernel/geom/geom.h"
#include "kernel/light/light.h"
#include "kernel/bvh/bvh.h"
CCL_NAMESPACE_BEGIN
@@ -387,7 +387,7 @@ ccl_device void integrator_intersect_closest(KernelGlobals kg,
#endif /* __MNEE__ */
/* Light intersection for MIS. */
if (kernel_data.integrator.use_lamp_mis) {
if (kernel_data.integrator.use_light_mis) {
/* NOTE: if we make lights visible to camera rays, we'll need to initialize
* these in the path_state_init. */
const int last_type = INTEGRATOR_STATE(state, isect, type);

View File

@@ -108,48 +108,6 @@ ccl_device_inline float mat22_inverse(const float4 m, ccl_private float4 &m_inve
return det;
}
/* Update light sample */
ccl_device_forceinline void mnee_update_light_sample(KernelGlobals kg,
const float3 P,
ccl_private LightSample *ls)
{
/* correct light sample position/direction and pdf
* NOTE: preserve pdf in area measure */
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, ls->lamp);
if (ls->type == LIGHT_POINT || ls->type == LIGHT_SPOT) {
ls->D = normalize_len(ls->P - P, &ls->t);
ls->Ng = -ls->D;
float2 uv = map_to_sphere(ls->Ng);
ls->u = uv.x;
ls->v = uv.y;
float invarea = klight->spot.invarea;
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
ls->pdf = invarea;
if (ls->type == LIGHT_SPOT) {
/* spot light attenuation */
float3 dir = make_float3(klight->spot.dir[0], klight->spot.dir[1], klight->spot.dir[2]);
ls->eval_fac *= spot_light_attenuation(
dir, klight->spot.spot_angle, klight->spot.spot_smooth, ls->Ng);
}
}
else if (ls->type == LIGHT_AREA) {
float invarea = fabsf(klight->area.invarea);
ls->D = normalize_len(ls->P - P, &ls->t);
ls->pdf = invarea;
if (klight->area.tan_spread > 0.f) {
ls->eval_fac = 0.25f * invarea;
ls->eval_fac *= light_spread_attenuation(
ls->D, ls->Ng, klight->area.tan_spread, klight->area.normalize_spread);
}
}
ls->pdf *= kernel_data.integrator.pdf_lights;
}
/* Manifold vertex setup from ray and intersection data */
ccl_device_forceinline void mnee_setup_manifold_vertex(KernelGlobals kg,
ccl_private ManifoldVertex *vtx,
@@ -819,7 +777,7 @@ ccl_device_forceinline bool mnee_path_contribution(KernelGlobals kg,
/* Update light sample with new position / direct.ion
* and keep pdf in vertex area measure */
mnee_update_light_sample(kg, vertices[vertex_count - 1].p, ls);
light_sample_update_position(kg, ls, vertices[vertex_count - 1].p);
/* Save state path bounce info in case a light path node is used in the refractive interface or
* light shader graph. */

View File

@@ -91,7 +91,10 @@ ccl_device_inline void path_state_init_integrator(KernelGlobals kg,
#endif
}
ccl_device_inline void path_state_next(KernelGlobals kg, IntegratorState state, int label)
ccl_device_inline void path_state_next(KernelGlobals kg,
IntegratorState state,
const int label,
const int shader_flag)
{
uint32_t flag = INTEGRATOR_STATE(state, path, flag);
@@ -120,12 +123,12 @@ ccl_device_inline void path_state_next(KernelGlobals kg, IntegratorState state,
flag |= PATH_RAY_TERMINATE_AFTER_TRANSPARENT;
}
flag &= ~(PATH_RAY_ALL_VISIBILITY | PATH_RAY_MIS_SKIP);
flag &= ~(PATH_RAY_ALL_VISIBILITY | PATH_RAY_MIS_SKIP | PATH_RAY_MIS_HAD_TRANSMISSION);
#ifdef __VOLUME__
if (label & LABEL_VOLUME_SCATTER) {
/* volume scatter */
flag |= PATH_RAY_VOLUME_SCATTER;
flag |= PATH_RAY_VOLUME_SCATTER | PATH_RAY_MIS_HAD_TRANSMISSION;
flag &= ~PATH_RAY_TRANSPARENT_BACKGROUND;
if (!(flag & PATH_RAY_ANY_PASS)) {
flag |= PATH_RAY_VOLUME_PASS;
@@ -188,6 +191,11 @@ ccl_device_inline void path_state_next(KernelGlobals kg, IntegratorState state,
flag |= PATH_RAY_GLOSSY | PATH_RAY_SINGULAR | PATH_RAY_MIS_SKIP;
}
/* Flag for consistent MIS weights with light tree. */
if (shader_flag & SD_BSDF_HAS_TRANSMISSION) {
flag |= PATH_RAY_MIS_HAD_TRANSMISSION;
}
/* Render pass categories. */
if (!(flag & PATH_RAY_ANY_PASS) && !(flag & PATH_RAY_TRANSPARENT_BACKGROUND)) {
flag |= PATH_RAY_SURFACE_PASS;

View File

@@ -3,6 +3,7 @@
#pragma once
#include "kernel/film/data_passes.h"
#include "kernel/film/light_passes.h"
#include "kernel/integrator/guiding.h"
@@ -68,9 +69,9 @@ ccl_device_inline void integrate_background(KernelGlobals kg,
bool eval_background = true;
float transparent = 0.0f;
int path_flag = INTEGRATOR_STATE(state, path, flag);
const bool is_transparent_background_ray = kernel_data.background.transparent &&
(INTEGRATOR_STATE(state, path, flag) &
PATH_RAY_TRANSPARENT_BACKGROUND);
(path_flag & PATH_RAY_TRANSPARENT_BACKGROUND);
if (is_transparent_background_ray) {
transparent = average(INTEGRATOR_STATE(state, path, throughput));
@@ -85,7 +86,7 @@ ccl_device_inline void integrate_background(KernelGlobals kg,
#ifdef __MNEE__
if (INTEGRATOR_STATE(state, path, mnee) & PATH_MNEE_CULL_LIGHT_CONNECTION) {
if (kernel_data.background.use_mis) {
for (int lamp = 0; lamp < kernel_data.integrator.num_all_lights; lamp++) {
for (int lamp = 0; lamp < kernel_data.integrator.num_lights; lamp++) {
/* This path should have been resolved with mnee, it will
* generate a firefly for small lights since it is improbable. */
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, lamp);
@@ -112,17 +113,10 @@ ccl_device_inline void integrate_background(KernelGlobals kg,
/* Background MIS weights. */
float mis_weight = 1.0f;
/* Check if background light exists or if we should skip pdf. */
/* Check if background light exists or if we should skip PDF. */
if (!(INTEGRATOR_STATE(state, path, flag) & PATH_RAY_MIS_SKIP) &&
kernel_data.background.use_mis) {
const float3 ray_P = INTEGRATOR_STATE(state, ray, P);
const float3 ray_D = INTEGRATOR_STATE(state, ray, D);
const float mis_ray_pdf = INTEGRATOR_STATE(state, path, mis_ray_pdf);
/* multiple importance sampling, get background light pdf for ray
* direction, and compute weight with respect to BSDF pdf */
const float pdf = background_light_pdf(kg, ray_P, ray_D);
mis_weight = light_sample_mis_weight_forward(kg, mis_ray_pdf, pdf);
mis_weight = light_sample_mis_weight_forward_background(kg, state, path_flag);
}
guiding_record_background(kg, state, L, mis_weight);
@@ -131,6 +125,7 @@ ccl_device_inline void integrate_background(KernelGlobals kg,
/* Write to render buffer. */
film_write_background(kg, state, L, transparent, is_transparent_background_ray, render_buffer);
film_write_data_passes_background(kg, state, render_buffer);
}
ccl_device_inline void integrate_distant_lights(KernelGlobals kg,
@@ -140,8 +135,8 @@ ccl_device_inline void integrate_distant_lights(KernelGlobals kg,
const float3 ray_D = INTEGRATOR_STATE(state, ray, D);
const float ray_time = INTEGRATOR_STATE(state, ray, time);
LightSample ls ccl_optional_struct_init;
for (int lamp = 0; lamp < kernel_data.integrator.num_all_lights; lamp++) {
if (light_sample_from_distant_ray(kg, ray_D, lamp, &ls)) {
for (int lamp = 0; lamp < kernel_data.integrator.num_lights; lamp++) {
if (distant_light_sample_from_intersection(kg, ray_D, lamp, &ls)) {
/* Use visibility flag to skip lights. */
#ifdef __PASSES__
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
@@ -180,10 +175,7 @@ ccl_device_inline void integrate_distant_lights(KernelGlobals kg,
/* MIS weighting. */
float mis_weight = 1.0f;
if (!(path_flag & PATH_RAY_MIS_SKIP)) {
/* multiple importance sampling, get regular light pdf,
* and compute weight with respect to BSDF pdf */
const float mis_ray_pdf = INTEGRATOR_STATE(state, path, mis_ray_pdf);
mis_weight = light_sample_mis_weight_forward(kg, mis_ray_pdf, ls.pdf);
mis_weight = light_sample_mis_weight_forward_distant(kg, state, path_flag, &ls);
}
/* Write to render buffer. */

View File

@@ -61,10 +61,7 @@ ccl_device_inline void integrate_light(KernelGlobals kg,
/* MIS weighting. */
float mis_weight = 1.0f;
if (!(path_flag & PATH_RAY_MIS_SKIP)) {
/* multiple importance sampling, get regular light pdf,
* and compute weight with respect to BSDF pdf */
const float mis_ray_pdf = INTEGRATOR_STATE(state, path, mis_ray_pdf);
mis_weight = light_sample_mis_weight_forward(kg, mis_ray_pdf, ls.pdf);
mis_weight = light_sample_mis_weight_forward_lamp(kg, state, path_flag, &ls, ray_P);
}
/* Write to render buffer. */

View File

@@ -15,7 +15,6 @@
#include "kernel/integrator/surface_shader.h"
#include "kernel/integrator/volume_stack.h"
#include "kernel/light/light.h"
#include "kernel/light/sample.h"
CCL_NAMESPACE_BEGIN
@@ -113,20 +112,16 @@ ccl_device_forceinline void integrate_surface_emission(KernelGlobals kg,
Spectrum L = surface_shader_emission(sd);
float mis_weight = 1.0f;
const bool has_mis = !(path_flag & PATH_RAY_MIS_SKIP) &&
(sd->flag & ((sd->flag & SD_BACKFACING) ? SD_MIS_BACK : SD_MIS_FRONT));
#ifdef __HAIR__
if (!(path_flag & PATH_RAY_MIS_SKIP) && (sd->flag & SD_USE_MIS) &&
(sd->type & PRIMITIVE_TRIANGLE))
if (has_mis && (sd->type & PRIMITIVE_TRIANGLE))
#else
if (!(path_flag & PATH_RAY_MIS_SKIP) && (sd->flag & SD_USE_MIS))
if (has_mis)
#endif
{
const float bsdf_pdf = INTEGRATOR_STATE(state, path, mis_ray_pdf);
const float t = sd->ray_length;
/* Multiple importance sampling, get triangle light pdf,
* and compute weight with respect to BSDF pdf. */
float pdf = triangle_light_pdf(kg, sd, t);
mis_weight = light_sample_mis_weight_forward(kg, bsdf_pdf, pdf);
mis_weight = light_sample_mis_weight_forward_surface(kg, state, path_flag, sd);
}
guiding_record_surface_emission(kg, state, L, mis_weight);
@@ -154,8 +149,17 @@ ccl_device_forceinline void integrate_surface_direct_light(KernelGlobals kg,
const uint bounce = INTEGRATOR_STATE(state, path, bounce);
const float2 rand_light = path_state_rng_2D(kg, rng_state, PRNG_LIGHT);
if (!light_distribution_sample_from_position(
kg, rand_light.x, rand_light.y, sd->time, sd->P, bounce, path_flag, &ls)) {
if (!light_sample_from_position(kg,
rng_state,
rand_light.x,
rand_light.y,
sd->time,
sd->P,
sd->N,
sd->flag,
bounce,
path_flag,
&ls)) {
return;
}
}
@@ -322,10 +326,6 @@ ccl_device_forceinline void integrate_surface_direct_light(KernelGlobals kg,
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, throughput) = throughput;
if (kernel_data.kernel_features & KERNEL_FEATURE_SHADOW_PASS) {
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, unshadowed_throughput) = throughput;
}
/* Write Lightgroup, +1 as lightgroup is int but we need to encode into a uint8_t. */
INTEGRATOR_STATE_WRITE(
shadow_state, shadow_path, lightgroup) = (ls.type != LIGHT_BACKGROUND) ?
@@ -441,11 +441,12 @@ ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce(
/* Update path state */
if (!(label & LABEL_TRANSPARENT)) {
INTEGRATOR_STATE_WRITE(state, path, mis_ray_pdf) = bsdf_pdf;
INTEGRATOR_STATE_WRITE(state, path, mis_origin_n) = sd->N;
INTEGRATOR_STATE_WRITE(state, path, min_ray_pdf) = fminf(
unguided_bsdf_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf));
}
path_state_next(kg, state, label);
path_state_next(kg, state, label, sd->flag);
guiding_record_surface_bounce(kg,
state,

View File

@@ -34,6 +34,9 @@ typedef struct VolumeIntegrateResult {
Spectrum direct_throughput;
float direct_t;
ShaderVolumePhases direct_phases;
# ifdef __PATH_GUIDING__
VolumeSampleMethod direct_sample_method;
# endif
/* Throughput and offset for indirect light scattering. */
bool indirect_scatter;
@@ -580,6 +583,9 @@ ccl_device_forceinline void volume_integrate_heterogeneous(
result.direct_t = volume_equiangular_sample(
ray, equiangular_light_P, vstate.rscatter, &vstate.equiangular_pdf);
}
# ifdef __PATH_GUIDING__
result.direct_sample_method = vstate.direct_sample_method;
# endif
# ifdef __DENOISING_FEATURES__
const bool write_denoising_features = (INTEGRATOR_STATE(state, path, flag) &
@@ -679,14 +685,14 @@ ccl_device_forceinline void volume_integrate_heterogeneous(
# endif /* __DENOISING_FEATURES__ */
}
/* Path tracing: sample point on light and evaluate light shader, then
* queue shadow ray to be traced. */
ccl_device_forceinline bool integrate_volume_sample_light(
/* Path tracing: sample point on light for equiangular sampling. */
ccl_device_forceinline bool integrate_volume_equiangular_sample_light(
KernelGlobals kg,
IntegratorState state,
ccl_private const Ray *ccl_restrict ray,
ccl_private const ShaderData *ccl_restrict sd,
ccl_private const RNGState *ccl_restrict rng_state,
ccl_private LightSample *ccl_restrict ls)
ccl_private float3 *ccl_restrict P)
{
/* Test if there is a light or BSDF that needs direct light. */
if (!kernel_data.integrator.use_direct_light) {
@@ -698,15 +704,30 @@ ccl_device_forceinline bool integrate_volume_sample_light(
const uint bounce = INTEGRATOR_STATE(state, path, bounce);
const float2 rand_light = path_state_rng_2D(kg, rng_state, PRNG_LIGHT);
if (!light_distribution_sample_from_volume_segment(
kg, rand_light.x, rand_light.y, sd->time, sd->P, bounce, path_flag, ls)) {
LightSample ls ccl_optional_struct_init;
if (!light_sample_from_volume_segment(kg,
rand_light.x,
rand_light.y,
sd->time,
sd->P,
ray->D,
ray->tmax - ray->tmin,
bounce,
path_flag,
&ls)) {
return false;
}
if (ls->shader & SHADER_EXCLUDE_SCATTER) {
if (ls.shader & SHADER_EXCLUDE_SCATTER) {
return false;
}
if (ls.t == FLT_MAX) {
return false;
}
*P = ls.P;
return true;
}
@@ -719,8 +740,10 @@ ccl_device_forceinline void integrate_volume_direct_light(
ccl_private const RNGState *ccl_restrict rng_state,
const float3 P,
ccl_private const ShaderVolumePhases *ccl_restrict phases,
ccl_private const Spectrum throughput,
ccl_private LightSample *ccl_restrict ls)
# ifdef __PATH_GUIDING__
ccl_private const Spectrum unlit_throughput,
# endif
ccl_private const Spectrum throughput)
{
PROFILING_INIT(kg, PROFILING_SHADE_VOLUME_DIRECT_LIGHT);
@@ -728,23 +751,38 @@ ccl_device_forceinline void integrate_volume_direct_light(
return;
}
/* Sample position on the same light again, now from the shading
* point where we scattered.
/* Sample position on the same light again, now from the shading point where we scattered.
*
* TODO: decorrelate random numbers and use light_sample_new_position to
* avoid resampling the CDF. */
* Note that this means we sample the light tree twice when equiangular sampling is used.
* We could consider sampling the light tree just once and use the same light position again.
*
* This would make the PDFs for MIS weights more complicated due to having to account for
* both distance/equiangular and direct/indirect light sampling, but could be more accurate.
* Additionally we could end up behind the light or outside a spot light cone, which might
* waste a sample. Though on the other hand it would be possible to prevent that with
* equiangular sampling restricted to a smaller sub-segment where the light has influence. */
LightSample ls ccl_optional_struct_init;
{
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
const uint bounce = INTEGRATOR_STATE(state, path, bounce);
const float2 rand_light = path_state_rng_2D(kg, rng_state, PRNG_LIGHT);
if (!light_distribution_sample_from_position(
kg, rand_light.x, rand_light.y, sd->time, P, bounce, path_flag, ls)) {
if (!light_sample_from_position(kg,
rng_state,
rand_light.x,
rand_light.y,
sd->time,
P,
zero_float3(),
SD_BSDF_HAS_TRANSMISSION,
bounce,
path_flag,
&ls)) {
return;
}
}
if (ls->shader & SHADER_EXCLUDE_SCATTER) {
if (ls.shader & SHADER_EXCLUDE_SCATTER) {
return;
}
@@ -756,32 +794,32 @@ ccl_device_forceinline void integrate_volume_direct_light(
* non-constant light sources. */
ShaderDataTinyStorage emission_sd_storage;
ccl_private ShaderData *emission_sd = AS_SHADER_DATA(&emission_sd_storage);
const Spectrum light_eval = light_sample_shader_eval(kg, state, emission_sd, ls, sd->time);
const Spectrum light_eval = light_sample_shader_eval(kg, state, emission_sd, &ls, sd->time);
if (is_zero(light_eval)) {
return;
}
/* Evaluate BSDF. */
BsdfEval phase_eval ccl_optional_struct_init;
float phase_pdf = volume_shader_phase_eval(kg, state, sd, phases, ls->D, &phase_eval);
float phase_pdf = volume_shader_phase_eval(kg, state, sd, phases, ls.D, &phase_eval);
if (ls->shader & SHADER_USE_MIS) {
float mis_weight = light_sample_mis_weight_nee(kg, ls->pdf, phase_pdf);
if (ls.shader & SHADER_USE_MIS) {
float mis_weight = light_sample_mis_weight_nee(kg, ls.pdf, phase_pdf);
bsdf_eval_mul(&phase_eval, mis_weight);
}
bsdf_eval_mul(&phase_eval, light_eval / ls->pdf);
bsdf_eval_mul(&phase_eval, light_eval / ls.pdf);
/* Path termination. */
const float terminate = path_state_rng_light_termination(kg, rng_state);
if (light_sample_terminate(kg, ls, &phase_eval, terminate)) {
if (light_sample_terminate(kg, &ls, &phase_eval, terminate)) {
return;
}
/* Create shadow ray. */
Ray ray ccl_optional_struct_init;
light_sample_to_volume_shadow_ray(kg, sd, ls, P, &ray);
const bool is_light = light_sample_is_light(ls);
light_sample_to_volume_shadow_ray(kg, sd, &ls, P, &ray);
const bool is_light = light_sample_is_light(&ls);
/* Branch off shadow kernel. */
IntegratorShadowState shadow_state = integrator_shadow_path_init(
@@ -840,18 +878,14 @@ ccl_device_forceinline void integrate_volume_direct_light(
state, path, transmission_bounce);
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, throughput) = throughput_phase;
if (kernel_data.kernel_features & KERNEL_FEATURE_SHADOW_PASS) {
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, unshadowed_throughput) = throughput;
}
/* Write Lightgroup, +1 as lightgroup is int but we need to encode into a uint8_t. */
INTEGRATOR_STATE_WRITE(
shadow_state, shadow_path, lightgroup) = (ls->type != LIGHT_BACKGROUND) ?
ls->group + 1 :
shadow_state, shadow_path, lightgroup) = (ls.type != LIGHT_BACKGROUND) ?
ls.group + 1 :
kernel_data.background.lightgroup + 1;
# ifdef __PATH_GUIDING__
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, unlit_throughput) = throughput;
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, unlit_throughput) = unlit_throughput;
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, path_segment) = INTEGRATOR_STATE(
state, guiding, path_segment);
# endif
@@ -949,10 +983,11 @@ ccl_device_forceinline bool integrate_volume_phase_scatter(
/* Update path state */
INTEGRATOR_STATE_WRITE(state, path, mis_ray_pdf) = phase_pdf;
INTEGRATOR_STATE_WRITE(state, path, mis_origin_n) = zero_float3();
INTEGRATOR_STATE_WRITE(state, path, min_ray_pdf) = fminf(
unguided_phase_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf));
path_state_next(kg, state, label);
path_state_next(kg, state, label, sd->flag);
return true;
}
@@ -974,12 +1009,11 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
/* Sample light ahead of volume stepping, for equiangular sampling. */
/* TODO: distant lights are ignored now, but could instead use even distribution. */
LightSample ls ccl_optional_struct_init;
const bool need_light_sample = !(INTEGRATOR_STATE(state, path, flag) & PATH_RAY_TERMINATE);
float3 equiangular_P = zero_float3();
const bool have_equiangular_sample = need_light_sample &&
integrate_volume_sample_light(
kg, state, &sd, &rng_state, &ls) &&
(ls.t != FLT_MAX);
integrate_volume_equiangular_sample_light(
kg, state, ray, &sd, &rng_state, &equiangular_P);
VolumeSampleMethod direct_sample_method = (have_equiangular_sample) ?
volume_stack_sample_method(kg, state) :
@@ -990,7 +1024,13 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
const float step_size = volume_stack_step_size(kg, volume_read_lambda_pass);
# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1
/* The current path throughput which is used later to calculate per-segment throughput.*/
const float3 initial_throughput = INTEGRATOR_STATE(state, path, throughput);
/* The path throughput used to calculate the throughput for direct light. */
float3 unlit_throughput = initial_throughput;
/* If a new path segment is generated at the direct scatter position.*/
bool guiding_generated_new_segment = false;
float rand_phase_guiding = 0.5f;
# endif
/* TODO: expensive to zero closures? */
@@ -1003,7 +1043,7 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
render_buffer,
step_size,
direct_sample_method,
ls.P,
equiangular_P,
result);
/* Perform path termination. The intersect_closest will have already marked this path
@@ -1018,41 +1058,48 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
return VOLUME_PATH_MISSED;
}
# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1
bool guiding_generated_new_segment = false;
if (kernel_data.integrator.use_guiding) {
/* Record transmittance using change in throughput. */
float3 transmittance_weight = spectrum_to_rgb(
safe_divide_color(result.indirect_throughput, initial_throughput));
guiding_record_volume_transmission(kg, state, transmittance_weight);
if (result.indirect_scatter) {
const float3 P = ray->P + result.indirect_t * ray->D;
/* Record volume segment up to direct scatter position.
* TODO: volume segment is wrong when direct_t and indirect_t. */
if (result.direct_scatter && (result.direct_t == result.indirect_t)) {
guiding_record_volume_segment(kg, state, P, sd.I);
guiding_generated_new_segment = true;
}
# if PATH_GUIDING_LEVEL >= 4
/* TODO: this position will be wrong for direct light pdf computation,
* since the direct light position may be different? */
volume_shader_prepare_guiding(
kg, state, &sd, &rng_state, P, ray->D, &result.direct_phases, direct_sample_method);
# endif
}
else {
/* No guiding if we don't scatter. */
state->guiding.use_volume_guiding = false;
}
}
# endif
/* Direct light. */
if (result.direct_scatter) {
const float3 direct_P = ray->P + result.direct_t * ray->D;
# ifdef __PATH_GUIDING__
if (kernel_data.integrator.use_guiding) {
# if PATH_GUIDING_LEVEL >= 1
if (result.direct_sample_method == VOLUME_SAMPLE_DISTANCE) {
/* If the direct scatter event is generated using VOLUME_SAMPLE_DISTANCE the direct event
* will happen at the same position as the indirect event and the direct light contribution
* will contribute to the position of the next path segment.*/
float3 transmittance_weight = spectrum_to_rgb(
safe_divide_color(result.indirect_throughput, initial_throughput));
guiding_record_volume_transmission(kg, state, transmittance_weight);
guiding_record_volume_segment(kg, state, direct_P, sd.I);
guiding_generated_new_segment = true;
unlit_throughput = result.indirect_throughput / continuation_probability;
rand_phase_guiding = path_state_rng_1D(kg, &rng_state, PRNG_VOLUME_PHASE_GUIDING_DISTANCE);
}
else {
/* If the direct scatter event is generated using VOLUME_SAMPLE_EQUIANGULAR the direct
* event will happen at a separate position as the indirect event and the direct light
* contribution will contribute to the position of the current/previous path segment. The
* unlit_throughput has to be adjusted to include the scattering at the previous segment.*/
float3 scatterEval = one_float3();
if (state->guiding.path_segment) {
pgl_vec3f scatteringWeight = state->guiding.path_segment->scatteringWeight;
scatterEval = make_float3(scatteringWeight.x, scatteringWeight.y, scatteringWeight.z);
}
unlit_throughput /= scatterEval;
unlit_throughput *= continuation_probability;
rand_phase_guiding = path_state_rng_1D(
kg, &rng_state, PRNG_VOLUME_PHASE_GUIDING_EQUIANGULAR);
}
# endif
# if PATH_GUIDING_LEVEL >= 4
volume_shader_prepare_guiding(
kg, state, &sd, rand_phase_guiding, direct_P, ray->D, &result.direct_phases);
# endif
}
# endif
result.direct_throughput /= continuation_probability;
integrate_volume_direct_light(kg,
state,
@@ -1060,8 +1107,10 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
&rng_state,
direct_P,
&result.direct_phases,
result.direct_throughput,
&ls);
# ifdef __PATH_GUIDING__
unlit_throughput,
# endif
result.direct_throughput);
}
/* Indirect light.
@@ -1069,6 +1118,13 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
* Only divide throughput by continuation_probability if we scatter. For the attenuation
* case the next surface will already do this division. */
if (result.indirect_scatter) {
# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1
if (!guiding_generated_new_segment) {
float3 transmittance_weight = spectrum_to_rgb(
safe_divide_color(result.indirect_throughput, initial_throughput));
guiding_record_volume_transmission(kg, state, transmittance_weight);
}
# endif
result.indirect_throughput /= continuation_probability;
}
INTEGRATOR_STATE_WRITE(state, path, throughput) = result.indirect_throughput;
@@ -1076,10 +1132,21 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
if (result.indirect_scatter) {
sd.P = ray->P + result.indirect_t * ray->D;
# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1
# if defined(__PATH_GUIDING__)
# if PATH_GUIDING_LEVEL >= 1
if (!guiding_generated_new_segment) {
guiding_record_volume_segment(kg, state, sd.P, sd.I);
}
# endif
# if PATH_GUIDING_LEVEL >= 4
/* If the direct scatter event was generated using VOLUME_SAMPLE_EQUIANGULAR we need to
* initialize the guiding distribution at the indirect scatter position. */
if (result.direct_sample_method == VOLUME_SAMPLE_EQUIANGULAR) {
rand_phase_guiding = path_state_rng_1D(kg, &rng_state, PRNG_VOLUME_PHASE_GUIDING_DISTANCE);
volume_shader_prepare_guiding(
kg, state, &sd, rand_phase_guiding, sd.P, ray->D, &result.indirect_phases);
}
# endif
# endif
if (integrate_volume_phase_scatter(kg, state, &sd, &rng_state, &result.indirect_phases)) {
@@ -1090,6 +1157,10 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
}
}
else {
# if defined(__PATH_GUIDING__)
/* No guiding if we don't scatter. */
state->guiding.use_volume_guiding = false;
# endif
return VOLUME_PATH_ATTENUATED;
}
}

View File

@@ -32,7 +32,7 @@ KERNEL_STRUCT_MEMBER(shadow_path, PackedSpectrum, throughput, KERNEL_FEATURE_PAT
KERNEL_STRUCT_MEMBER(shadow_path,
PackedSpectrum,
unshadowed_throughput,
KERNEL_FEATURE_SHADOW_PASS | KERNEL_FEATURE_AO_ADDITIVE)
KERNEL_FEATURE_AO_ADDITIVE)
/* Ratio of throughput to distinguish diffuse / glossy / transmission render passes. */
KERNEL_STRUCT_MEMBER(shadow_path, PackedSpectrum, pass_diffuse_weight, KERNEL_FEATURE_LIGHT_PASSES)
KERNEL_STRUCT_MEMBER(shadow_path, PackedSpectrum, pass_glossy_weight, KERNEL_FEATURE_LIGHT_PASSES)

View File

@@ -41,6 +41,7 @@ KERNEL_STRUCT_MEMBER(path, uint8_t, mnee, KERNEL_FEATURE_PATH_TRACING)
* zero and distance. Note that transparency and volume attenuation increase
* the ray tmin but keep P unmodified so that this works. */
KERNEL_STRUCT_MEMBER(path, float, mis_ray_pdf, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_MEMBER(path, packed_float3, mis_origin_n, KERNEL_FEATURE_PATH_TRACING)
/* Filter glossy. */
KERNEL_STRUCT_MEMBER(path, float, min_ray_pdf, KERNEL_FEATURE_PATH_TRACING)
/* Continuation probability for path termination. */

View File

@@ -95,11 +95,10 @@ ccl_device_inline void volume_shader_copy_phases(ccl_private ShaderVolumePhases
ccl_device_inline void volume_shader_prepare_guiding(KernelGlobals kg,
IntegratorState state,
ccl_private ShaderData *sd,
ccl_private const RNGState *rng_state,
float rand_phase_guiding,
const float3 P,
const float3 D,
ccl_private ShaderVolumePhases *phases,
const VolumeSampleMethod direct_sample_method)
ccl_private ShaderVolumePhases *phases)
{
/* Have any phase functions to guide? */
const int num_phases = phases->num_closure;
@@ -109,7 +108,6 @@ ccl_device_inline void volume_shader_prepare_guiding(KernelGlobals kg,
}
const float volume_guiding_probability = kernel_data.integrator.volume_guiding_probability;
float rand_phase_guiding = path_state_rng_1D(kg, rng_state, PRNG_VOLUME_PHASE_GUIDING);
/* If we have more than one phase function we select one random based on its
* sample weight to calculate the product distribution for guiding. */

View File

@@ -0,0 +1,387 @@
/* SPDX-License-Identifier: Apache-2.0
* Copyright 2011-2022 Blender Foundation */
#pragma once
#include "kernel/light/common.h"
CCL_NAMESPACE_BEGIN
/* Importance sampling.
*
* An Area-Preserving Parametrization for Spherical Rectangles.
* Carlos Urena et al.
*
* NOTE: light_p is modified when sample_coord is true. */
ccl_device_inline float area_light_rect_sample(float3 P,
ccl_private float3 *light_p,
const float3 axis_u,
const float len_u,
const float3 axis_v,
const float len_v,
float randu,
float randv,
bool sample_coord)
{
/* In our name system we're using P for the center, which is o in the paper. */
float3 corner = *light_p - axis_u * len_u * 0.5f - axis_v * len_v * 0.5f;
/* Compute local reference system R. */
float3 x = axis_u;
float3 y = axis_v;
float3 z = cross(x, y);
/* Compute rectangle coords in local reference system. */
float3 dir = corner - P;
float z0 = dot(dir, z);
/* Flip 'z' to make it point against Q. */
if (z0 > 0.0f) {
z *= -1.0f;
z0 *= -1.0f;
}
float x0 = dot(dir, x);
float y0 = dot(dir, y);
float x1 = x0 + len_u;
float y1 = y0 + len_v;
/* Compute internal angles (gamma_i). */
float4 diff = make_float4(x0, y1, x1, y0) - make_float4(x1, y0, x0, y1);
float4 nz = make_float4(y0, x1, y1, x0) * diff;
nz = nz / sqrt(z0 * z0 * diff * diff + nz * nz);
float g0 = safe_acosf(-nz.x * nz.y);
float g1 = safe_acosf(-nz.y * nz.z);
float g2 = safe_acosf(-nz.z * nz.w);
float g3 = safe_acosf(-nz.w * nz.x);
/* Compute predefined constants. */
float b0 = nz.x;
float b1 = nz.z;
float b0sq = b0 * b0;
float k = M_2PI_F - g2 - g3;
/* Compute solid angle from internal angles. */
float S = g0 + g1 - k;
if (sample_coord) {
/* Compute cu. */
float au = randu * S + k;
float fu = (cosf(au) * b0 - b1) / sinf(au);
float cu = 1.0f / sqrtf(fu * fu + b0sq) * (fu > 0.0f ? 1.0f : -1.0f);
cu = clamp(cu, -1.0f, 1.0f);
/* Compute xu. */
float xu = -(cu * z0) / max(sqrtf(1.0f - cu * cu), 1e-7f);
xu = clamp(xu, x0, x1);
/* Compute yv. */
float z0sq = z0 * z0;
float y0sq = y0 * y0;
float y1sq = y1 * y1;
float d = sqrtf(xu * xu + z0sq);
float h0 = y0 / sqrtf(d * d + y0sq);
float h1 = y1 / sqrtf(d * d + y1sq);
float hv = h0 + randv * (h1 - h0), hv2 = hv * hv;
float yv = (hv2 < 1.0f - 1e-6f) ? (hv * d) / sqrtf(1.0f - hv2) : y1;
/* Transform (xu, yv, z0) to world coords. */
*light_p = P + xu * x + yv * y + z0 * z;
}
/* return pdf */
if (S != 0.0f)
return 1.0f / S;
else
return 0.0f;
}
/* Light spread. */
ccl_device float area_light_spread_attenuation(const float3 D,
const float3 lightNg,
const float cot_half_spread,
const float normalize_spread)
{
/* Model a soft-box grid, computing the ratio of light not hidden by the
* slats of the grid at a given angle. (see D10594). */
const float cos_a = -dot(D, lightNg);
const float sin_a = safe_sqrtf(1.0f - sqr(cos_a));
const float tan_a = sin_a / cos_a;
return max((1.0f - (cot_half_spread * tan_a)) * normalize_spread, 0.0f);
}
/* Compute subset of area light that actually has an influence on the shading point, to
* reduce noise with low spread. */
ccl_device bool area_light_spread_clamp_area_light(const float3 P,
const float3 lightNg,
ccl_private float3 *lightP,
const float3 axis_u,
ccl_private float *len_u,
const float3 axis_v,
ccl_private float *len_v,
const float cot_half_spread)
{
/* Closest point in area light plane and distance to that plane. */
const float3 closest_P = P - dot(lightNg, P - *lightP) * lightNg;
const float t = len(closest_P - P);
/* Radius of circle on area light that actually affects the shading point. */
const float radius = t / cot_half_spread;
/* Local uv coordinates of closest point. */
const float closest_u = dot(axis_u, closest_P - *lightP);
const float closest_v = dot(axis_v, closest_P - *lightP);
/* Compute rectangle encompassing the circle that affects the shading point,
* clamped to the bounds of the area light. */
const float min_u = max(closest_u - radius, -*len_u * 0.5f);
const float max_u = min(closest_u + radius, *len_u * 0.5f);
const float min_v = max(closest_v - radius, -*len_v * 0.5f);
const float max_v = min(closest_v + radius, *len_v * 0.5f);
/* Skip if rectangle is empty. */
if (min_u >= max_u || min_v >= max_v) {
return false;
}
/* Compute new area light center position and axes from rectangle in local
* uv coordinates. */
const float new_center_u = 0.5f * (min_u + max_u);
const float new_center_v = 0.5f * (min_v + max_v);
*len_u = max_u - min_u;
*len_v = max_v - min_v;
*lightP = *lightP + new_center_u * axis_u + new_center_v * axis_v;
return true;
}
/* Common API. */
template<bool in_volume_segment>
ccl_device_inline bool area_light_sample(const ccl_global KernelLight *klight,
const float randu,
const float randv,
const float3 P,
ccl_private LightSample *ls)
{
ls->P = klight->co;
const float3 axis_u = klight->area.axis_u;
const float3 axis_v = klight->area.axis_v;
const float len_u = klight->area.len_u;
const float len_v = klight->area.len_v;
float3 Ng = klight->area.dir;
float invarea = fabsf(klight->area.invarea);
bool is_round = (klight->area.invarea < 0.0f);
if (!in_volume_segment) {
if (dot(ls->P - P, Ng) > 0.0f) {
return false;
}
}
float3 inplane;
if (is_round || in_volume_segment) {
inplane = ellipse_sample(axis_u * len_u * 0.5f, axis_v * len_v * 0.5f, randu, randv);
ls->P += inplane;
ls->pdf = invarea;
}
else {
inplane = ls->P;
float sample_len_u = len_u;
float sample_len_v = len_v;
if (!in_volume_segment && klight->area.cot_half_spread > 0.0f) {
if (!area_light_spread_clamp_area_light(P,
Ng,
&ls->P,
axis_u,
&sample_len_u,
axis_v,
&sample_len_v,
klight->area.cot_half_spread)) {
return false;
}
}
ls->pdf = area_light_rect_sample(
P, &ls->P, axis_u, sample_len_u, axis_v, sample_len_v, randu, randv, true);
inplane = ls->P - inplane;
}
const float light_u = dot(inplane, axis_u) / len_u;
const float light_v = dot(inplane, axis_v) / len_v;
/* NOTE: Return barycentric coordinates in the same notation as Embree and OptiX. */
ls->u = light_v + 0.5f;
ls->v = -light_u - light_v;
ls->Ng = Ng;
ls->D = normalize_len(ls->P - P, &ls->t);
ls->eval_fac = 0.25f * invarea;
if (klight->area.cot_half_spread > 0.0f) {
/* Area Light spread angle attenuation */
ls->eval_fac *= area_light_spread_attenuation(
ls->D, ls->Ng, klight->area.cot_half_spread, klight->area.normalize_spread);
}
if (is_round) {
ls->pdf *= lamp_light_pdf(Ng, -ls->D, ls->t);
}
return true;
}
ccl_device_forceinline void area_light_update_position(const ccl_global KernelLight *klight,
ccl_private LightSample *ls,
const float3 P)
{
const float invarea = fabsf(klight->area.invarea);
ls->D = normalize_len(ls->P - P, &ls->t);
ls->pdf = invarea;
if (klight->area.cot_half_spread > 0.f) {
ls->eval_fac = 0.25f * invarea;
ls->eval_fac *= area_light_spread_attenuation(
ls->D, ls->Ng, klight->area.cot_half_spread, klight->area.normalize_spread);
}
}
ccl_device_inline bool area_light_intersect(const ccl_global KernelLight *klight,
const ccl_private Ray *ccl_restrict ray,
ccl_private float *t,
ccl_private float *u,
ccl_private float *v)
{
/* Area light. */
const float invarea = fabsf(klight->area.invarea);
const bool is_round = (klight->area.invarea < 0.0f);
if (invarea == 0.0f) {
return false;
}
const float3 inv_extent_u = klight->area.axis_u / klight->area.len_u;
const float3 inv_extent_v = klight->area.axis_v / klight->area.len_v;
const float3 Ng = klight->area.dir;
/* One sided. */
if (dot(ray->D, Ng) >= 0.0f) {
return false;
}
const float3 light_P = klight->co;
float3 P;
return ray_quad_intersect(ray->P,
ray->D,
ray->tmin,
ray->tmax,
light_P,
inv_extent_u,
inv_extent_v,
Ng,
&P,
t,
u,
v,
is_round);
}
ccl_device_inline bool area_light_sample_from_intersection(
const ccl_global KernelLight *klight,
ccl_private const Intersection *ccl_restrict isect,
const float3 ray_P,
const float3 ray_D,
ccl_private LightSample *ccl_restrict ls)
{
/* area light */
float invarea = fabsf(klight->area.invarea);
float3 Ng = klight->area.dir;
float3 light_P = klight->co;
ls->u = isect->u;
ls->v = isect->v;
ls->D = ray_D;
ls->Ng = Ng;
const bool is_round = (klight->area.invarea < 0.0f);
if (is_round) {
ls->pdf = invarea * lamp_light_pdf(Ng, -ray_D, ls->t);
}
else {
const float3 axis_u = klight->area.axis_u;
const float3 axis_v = klight->area.axis_v;
float sample_len_u = klight->area.len_u;
float sample_len_v = klight->area.len_v;
if (klight->area.cot_half_spread > 0.0f) {
if (!area_light_spread_clamp_area_light(ray_P,
Ng,
&light_P,
axis_u,
&sample_len_u,
axis_v,
&sample_len_v,
klight->area.cot_half_spread)) {
return false;
}
}
ls->pdf = area_light_rect_sample(
ray_P, &light_P, axis_u, sample_len_u, axis_v, sample_len_v, 0, 0, false);
}
ls->eval_fac = 0.25f * invarea;
if (klight->area.cot_half_spread > 0.0f) {
/* Area Light spread angle attenuation */
ls->eval_fac *= area_light_spread_attenuation(
ls->D, ls->Ng, klight->area.cot_half_spread, klight->area.normalize_spread);
if (ls->eval_fac == 0.0f) {
return false;
}
}
return true;
}
template<bool in_volume_segment>
ccl_device_forceinline bool area_light_tree_parameters(const ccl_global KernelLight *klight,
const float3 centroid,
const float3 P,
const float3 N,
const float3 bcone_axis,
ccl_private float &cos_theta_u,
ccl_private float2 &distance,
ccl_private float3 &point_to_centroid)
{
if (!in_volume_segment) {
/* TODO: a cheap substitute for minimal distance between point and primitive. Does it
* worth the overhead to compute the accurate minimal distance? */
float min_distance;
point_to_centroid = safe_normalize_len(centroid - P, &min_distance);
distance = make_float2(min_distance, min_distance);
}
cos_theta_u = FLT_MAX;
const float3 extentu = klight->area.axis_u * klight->area.len_u;
const float3 extentv = klight->area.axis_v * klight->area.len_v;
for (int i = 0; i < 4; i++) {
const float3 corner = ((i & 1) - 0.5f) * extentu + 0.5f * ((i & 2) - 1) * extentv + centroid;
float distance_point_to_corner;
const float3 point_to_corner = safe_normalize_len(corner - P, &distance_point_to_corner);
cos_theta_u = fminf(cos_theta_u, dot(point_to_centroid, point_to_corner));
if (!in_volume_segment) {
distance.x = fmaxf(distance.x, distance_point_to_corner);
}
}
const bool front_facing = dot(bcone_axis, point_to_centroid) < 0;
const bool shape_above_surface = dot(N, centroid - P) + fabsf(dot(N, extentu)) +
fabsf(dot(N, extentv)) >
0;
const bool in_volume = is_zero(N);
return (front_facing && shape_above_surface) || in_volume;
}
CCL_NAMESPACE_END

View File

@@ -3,6 +3,7 @@
#pragma once
#include "kernel/light/area.h"
#include "kernel/light/common.h"
CCL_NAMESPACE_BEGIN
@@ -130,11 +131,11 @@ ccl_device float background_map_pdf(KernelGlobals kg, float3 direction)
ccl_device_inline bool background_portal_data_fetch_and_check_side(
KernelGlobals kg, float3 P, int index, ccl_private float3 *lightpos, ccl_private float3 *dir)
{
int portal = kernel_data.background.portal_offset + index;
int portal = kernel_data.integrator.portal_offset + index;
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, portal);
*lightpos = make_float3(klight->co[0], klight->co[1], klight->co[2]);
*dir = make_float3(klight->area.dir[0], klight->area.dir[1], klight->area.dir[2]);
*lightpos = klight->co;
*dir = klight->area.dir;
/* Check whether portal is on the right side. */
if (dot(*dir, P - *lightpos) > 1e-4f)
@@ -149,7 +150,7 @@ ccl_device_inline float background_portal_pdf(
float portal_pdf = 0.0f;
int num_possible = 0;
for (int p = 0; p < kernel_data.background.num_portals; p++) {
for (int p = 0; p < kernel_data.integrator.num_portals; p++) {
if (p == ignore_portal)
continue;
@@ -163,12 +164,16 @@ ccl_device_inline float background_portal_pdf(
}
num_possible++;
int portal = kernel_data.background.portal_offset + p;
int portal = kernel_data.integrator.portal_offset + p;
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, portal);
float3 axisu = make_float3(
klight->area.axisu[0], klight->area.axisu[1], klight->area.axisu[2]);
float3 axisv = make_float3(
klight->area.axisv[0], klight->area.axisv[1], klight->area.axisv[2]);
const float3 axis_u = klight->area.axis_u;
const float len_u = klight->area.len_u;
const float3 axis_v = klight->area.axis_v;
const float len_v = klight->area.len_v;
const float3 inv_extent_u = axis_u / len_u;
const float3 inv_extent_v = axis_v / len_v;
bool is_round = (klight->area.invarea < 0.0f);
if (!ray_quad_intersect(P,
@@ -176,8 +181,8 @@ ccl_device_inline float background_portal_pdf(
1e-4f,
FLT_MAX,
lightpos,
axisu,
axisv,
inv_extent_u,
inv_extent_v,
dir,
NULL,
NULL,
@@ -189,10 +194,11 @@ ccl_device_inline float background_portal_pdf(
if (is_round) {
float t;
float3 D = normalize_len(lightpos - P, &t);
portal_pdf += fabsf(klight->area.invarea) * lamp_light_pdf(kg, dir, -D, t);
portal_pdf += fabsf(klight->area.invarea) * lamp_light_pdf(dir, -D, t);
}
else {
portal_pdf += rect_light_sample(P, &lightpos, axisu, axisv, 0.0f, 0.0f, false);
portal_pdf += area_light_rect_sample(
P, &lightpos, axis_u, len_u, axis_v, len_v, 0.0f, 0.0f, false);
}
}
@@ -207,7 +213,7 @@ ccl_device_inline float background_portal_pdf(
ccl_device int background_num_possible_portals(KernelGlobals kg, float3 P)
{
int num_possible_portals = 0;
for (int p = 0; p < kernel_data.background.num_portals; p++) {
for (int p = 0; p < kernel_data.integrator.num_portals; p++) {
float3 lightpos, dir;
if (background_portal_data_fetch_and_check_side(kg, P, p, &lightpos, &dir))
num_possible_portals++;
@@ -231,7 +237,7 @@ ccl_device float3 background_portal_sample(KernelGlobals kg,
/* TODO(sergey): Some smarter way of finding portal to sample
* is welcome.
*/
for (int p = 0; p < kernel_data.background.num_portals; p++) {
for (int p = 0; p < kernel_data.integrator.num_portals; p++) {
/* Search for the sampled portal. */
float3 lightpos, dir;
if (!background_portal_data_fetch_and_check_side(kg, P, p, &lightpos, &dir))
@@ -239,23 +245,24 @@ ccl_device float3 background_portal_sample(KernelGlobals kg,
if (portal == 0) {
/* p is the portal to be sampled. */
int portal = kernel_data.background.portal_offset + p;
int portal = kernel_data.integrator.portal_offset + p;
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, portal);
float3 axisu = make_float3(
klight->area.axisu[0], klight->area.axisu[1], klight->area.axisu[2]);
float3 axisv = make_float3(
klight->area.axisv[0], klight->area.axisv[1], klight->area.axisv[2]);
const float3 axis_u = klight->area.axis_u;
const float3 axis_v = klight->area.axis_v;
const float len_u = klight->area.len_u;
const float len_v = klight->area.len_v;
bool is_round = (klight->area.invarea < 0.0f);
float3 D;
if (is_round) {
lightpos += ellipse_sample(axisu * 0.5f, axisv * 0.5f, randu, randv);
lightpos += ellipse_sample(axis_u * len_u * 0.5f, axis_v * len_v * 0.5f, randu, randv);
float t;
D = normalize_len(lightpos - P, &t);
*pdf = fabsf(klight->area.invarea) * lamp_light_pdf(kg, dir, -D, t);
*pdf = fabsf(klight->area.invarea) * lamp_light_pdf(dir, -D, t);
}
else {
*pdf = rect_light_sample(P, &lightpos, axisu, axisv, randu, randv, true);
*pdf = area_light_rect_sample(
P, &lightpos, axis_u, len_u, axis_v, len_v, randu, randv, true);
D = normalize(lightpos - P);
}
@@ -414,7 +421,7 @@ ccl_device float background_light_pdf(KernelGlobals kg, float3 P, float3 directi
float pdf_fac = (portal_method_pdf + sun_method_pdf + map_method_pdf);
if (pdf_fac == 0.0f) {
/* Use uniform as a fallback if we can't use any strategy. */
return kernel_data.integrator.pdf_lights / M_4PI_F;
return 1.0f / M_4PI_F;
}
pdf_fac = 1.0f / pdf_fac;
@@ -430,7 +437,21 @@ ccl_device float background_light_pdf(KernelGlobals kg, float3 P, float3 directi
pdf += background_map_pdf(kg, direction) * map_method_pdf;
}
return pdf * kernel_data.integrator.pdf_lights;
return pdf;
}
ccl_device_forceinline bool background_light_tree_parameters(const float3 centroid,
ccl_private float &cos_theta_u,
ccl_private float2 &distance,
ccl_private float3 &point_to_centroid)
{
/* Cover the whole sphere */
cos_theta_u = -1.0f;
distance = make_float2(1.0f, 1.0f);
point_to_centroid = -centroid;
return true;
}
CCL_NAMESPACE_END

View File

@@ -7,92 +7,26 @@
CCL_NAMESPACE_BEGIN
/* Area light sampling */
/* Light Sample Result */
/* Uses the following paper:
*
* Carlos Urena et al.
* An Area-Preserving Parametrization for Spherical Rectangles.
*
* https://www.solidangle.com/research/egsr2013_spherical_rectangle.pdf
*
* NOTE: light_p is modified when sample_coord is true.
*/
ccl_device_inline float rect_light_sample(float3 P,
ccl_private float3 *light_p,
float3 axisu,
float3 axisv,
float randu,
float randv,
bool sample_coord)
{
/* In our name system we're using P for the center,
* which is o in the paper.
*/
typedef struct LightSample {
float3 P; /* position on light, or direction for distant light */
float3 Ng; /* normal on light */
float3 D; /* direction from shading point to light */
float t; /* distance to light (FLT_MAX for distant light) */
float u, v; /* parametric coordinate on primitive */
float pdf; /* pdf for selecting light and point on light */
float pdf_selection; /* pdf for selecting light */
float eval_fac; /* intensity multiplier */
int object; /* object id for triangle/curve lights */
int prim; /* primitive id for triangle/curve lights */
int shader; /* shader id */
int lamp; /* lamp id */
int group; /* lightgroup */
LightType type; /* type of light */
} LightSample;
float3 corner = *light_p - axisu * 0.5f - axisv * 0.5f;
float axisu_len, axisv_len;
/* Compute local reference system R. */
float3 x = normalize_len(axisu, &axisu_len);
float3 y = normalize_len(axisv, &axisv_len);
float3 z = cross(x, y);
/* Compute rectangle coords in local reference system. */
float3 dir = corner - P;
float z0 = dot(dir, z);
/* Flip 'z' to make it point against Q. */
if (z0 > 0.0f) {
z *= -1.0f;
z0 *= -1.0f;
}
float x0 = dot(dir, x);
float y0 = dot(dir, y);
float x1 = x0 + axisu_len;
float y1 = y0 + axisv_len;
/* Compute internal angles (gamma_i). */
float4 diff = make_float4(x0, y1, x1, y0) - make_float4(x1, y0, x0, y1);
float4 nz = make_float4(y0, x1, y1, x0) * diff;
nz = nz / sqrt(z0 * z0 * diff * diff + nz * nz);
float g0 = safe_acosf(-nz.x * nz.y);
float g1 = safe_acosf(-nz.y * nz.z);
float g2 = safe_acosf(-nz.z * nz.w);
float g3 = safe_acosf(-nz.w * nz.x);
/* Compute predefined constants. */
float b0 = nz.x;
float b1 = nz.z;
float b0sq = b0 * b0;
float k = M_2PI_F - g2 - g3;
/* Compute solid angle from internal angles. */
float S = g0 + g1 - k;
if (sample_coord) {
/* Compute cu. */
float au = randu * S + k;
float fu = (cosf(au) * b0 - b1) / sinf(au);
float cu = 1.0f / sqrtf(fu * fu + b0sq) * (fu > 0.0f ? 1.0f : -1.0f);
cu = clamp(cu, -1.0f, 1.0f);
/* Compute xu. */
float xu = -(cu * z0) / max(sqrtf(1.0f - cu * cu), 1e-7f);
xu = clamp(xu, x0, x1);
/* Compute yv. */
float z0sq = z0 * z0;
float y0sq = y0 * y0;
float y1sq = y1 * y1;
float d = sqrtf(xu * xu + z0sq);
float h0 = y0 / sqrtf(d * d + y0sq);
float h1 = y1 / sqrtf(d * d + y1sq);
float hv = h0 + randv * (h1 - h0), hv2 = hv * hv;
float yv = (hv2 < 1.0f - 1e-6f) ? (hv * d) / sqrtf(1.0f - hv2) : y1;
/* Transform (xu, yv, z0) to world coords. */
*light_p = P + xu * x + yv * y + z0 * z;
}
/* return pdf */
if (S != 0.0f)
return 1.0f / S;
else
return 0.0f;
}
/* Utilities */
ccl_device_inline float3 ellipse_sample(float3 ru, float3 rv, float randu, float randv)
{
@@ -109,99 +43,7 @@ ccl_device float3 disk_light_sample(float3 v, float randu, float randv)
return ellipse_sample(ru, rv, randu, randv);
}
ccl_device float3 distant_light_sample(float3 D, float radius, float randu, float randv)
{
return normalize(D + disk_light_sample(D, randu, randv) * radius);
}
ccl_device float3
sphere_light_sample(float3 P, float3 center, float radius, float randu, float randv)
{
return disk_light_sample(normalize(P - center), randu, randv) * radius;
}
ccl_device float spot_light_attenuation(float3 dir, float spot_angle, float spot_smooth, float3 N)
{
float attenuation = dot(dir, N);
if (attenuation <= spot_angle) {
attenuation = 0.0f;
}
else {
float t = attenuation - spot_angle;
if (t < spot_smooth && spot_smooth != 0.0f)
attenuation *= smoothstepf(t / spot_smooth);
}
return attenuation;
}
ccl_device float light_spread_attenuation(const float3 D,
const float3 lightNg,
const float tan_spread,
const float normalize_spread)
{
/* Model a soft-box grid, computing the ratio of light not hidden by the
* slats of the grid at a given angle. (see D10594). */
const float cos_a = -dot(D, lightNg);
const float sin_a = safe_sqrtf(1.0f - sqr(cos_a));
const float tan_a = sin_a / cos_a;
return max((1.0f - (tan_spread * tan_a)) * normalize_spread, 0.0f);
}
/* Compute subset of area light that actually has an influence on the shading point, to
* reduce noise with low spread. */
ccl_device bool light_spread_clamp_area_light(const float3 P,
const float3 lightNg,
ccl_private float3 *lightP,
ccl_private float3 *axisu,
ccl_private float3 *axisv,
const float tan_spread)
{
/* Closest point in area light plane and distance to that plane. */
const float3 closest_P = P - dot(lightNg, P - *lightP) * lightNg;
const float t = len(closest_P - P);
/* Radius of circle on area light that actually affects the shading point. */
const float radius = t / tan_spread;
/* TODO: would be faster to store as normalized vector + length, also in rect_light_sample. */
float len_u, len_v;
const float3 u = normalize_len(*axisu, &len_u);
const float3 v = normalize_len(*axisv, &len_v);
/* Local uv coordinates of closest point. */
const float closest_u = dot(u, closest_P - *lightP);
const float closest_v = dot(v, closest_P - *lightP);
/* Compute rectangle encompassing the circle that affects the shading point,
* clamped to the bounds of the area light. */
const float min_u = max(closest_u - radius, -len_u * 0.5f);
const float max_u = min(closest_u + radius, len_u * 0.5f);
const float min_v = max(closest_v - radius, -len_v * 0.5f);
const float max_v = min(closest_v + radius, len_v * 0.5f);
/* Skip if rectangle is empty. */
if (min_u >= max_u || min_v >= max_v) {
return false;
}
/* Compute new area light center position and axes from rectangle in local
* uv coordinates. */
const float new_center_u = 0.5f * (min_u + max_u);
const float new_center_v = 0.5f * (min_v + max_v);
const float new_len_u = max_u - min_u;
const float new_len_v = max_v - min_v;
*lightP = *lightP + new_center_u * u + new_center_v * v;
*axisu = u * new_len_u;
*axisv = v * new_len_v;
return true;
}
ccl_device float lamp_light_pdf(KernelGlobals kg, const float3 Ng, const float3 I, float t)
ccl_device float lamp_light_pdf(const float3 Ng, const float3 I, float t)
{
float cos_pi = dot(Ng, I);

View File

@@ -0,0 +1,127 @@
/* SPDX-License-Identifier: Apache-2.0
* Copyright 2011-2022 Blender Foundation */
#pragma once
#include "kernel/geom/geom.h"
#include "kernel/light/common.h"
CCL_NAMESPACE_BEGIN
ccl_device_inline bool distant_light_sample(const ccl_global KernelLight *klight,
const float randu,
const float randv,
ccl_private LightSample *ls)
{
/* distant light */
float3 lightD = klight->co;
float3 D = lightD;
float radius = klight->distant.radius;
float invarea = klight->distant.invarea;
if (radius > 0.0f) {
D = normalize(D + disk_light_sample(D, randu, randv) * radius);
}
ls->P = D;
ls->Ng = D;
ls->D = -D;
ls->t = FLT_MAX;
float costheta = dot(lightD, D);
ls->pdf = invarea / (costheta * costheta * costheta);
ls->eval_fac = ls->pdf;
return true;
}
ccl_device bool distant_light_sample_from_intersection(KernelGlobals kg,
const float3 ray_D,
const int lamp,
ccl_private LightSample *ccl_restrict ls)
{
ccl_global const KernelLight *klight = &kernel_data_fetch(lights, lamp);
const int shader = klight->shader_id;
const float radius = klight->distant.radius;
const LightType type = (LightType)klight->type;
if (type != LIGHT_DISTANT) {
return false;
}
if (!(shader & SHADER_USE_MIS)) {
return false;
}
if (radius == 0.0f) {
return false;
}
/* a distant light is infinitely far away, but equivalent to a disk
* shaped light exactly 1 unit away from the current shading point.
*
* radius t^2/cos(theta)
* <----------> t = sqrt(1^2 + tan(theta)^2)
* tan(th) area = radius*radius*pi
* <----->
* \ | (1 + tan(theta)^2)/cos(theta)
* \ | (1 + tan(acos(cos(theta)))^2)/cos(theta)
* t \th| 1 simplifies to
* \-| 1/(cos(theta)^3)
* \| magic!
* P
*/
float3 lightD = klight->co;
float costheta = dot(-lightD, ray_D);
float cosangle = klight->distant.cosangle;
/* Workaround to prevent a hang in the classroom scene with AMD HIP drivers 22.10,
* Remove when a compiler fix is available. */
#ifdef __HIP__
ls->shader = klight->shader_id;
#endif
if (costheta < cosangle)
return false;
ls->type = type;
#ifndef __HIP__
ls->shader = klight->shader_id;
#endif
ls->object = PRIM_NONE;
ls->prim = PRIM_NONE;
ls->lamp = lamp;
/* todo: missing texture coordinates */
ls->u = 0.0f;
ls->v = 0.0f;
ls->t = FLT_MAX;
ls->P = -ray_D;
ls->Ng = -ray_D;
ls->D = ray_D;
ls->group = lamp_lightgroup(kg, lamp);
/* compute pdf */
float invarea = klight->distant.invarea;
ls->pdf = invarea / (costheta * costheta * costheta);
ls->eval_fac = ls->pdf;
return true;
}
ccl_device_forceinline bool distant_light_tree_parameters(const float3 centroid,
const float theta_e,
ccl_private float &cos_theta_u,
ccl_private float2 &distance,
ccl_private float3 &point_to_centroid)
{
/* Treating it as a disk light 1 unit away */
cos_theta_u = fast_cosf(theta_e);
distance = make_float2(1.0f / cos_theta_u, 1.0f);
point_to_centroid = -centroid;
return true;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,80 @@
/* SPDX-License-Identifier: Apache-2.0
* Copyright 2011-2022 Blender Foundation */
#pragma once
#include "kernel/light/light.h"
#include "kernel/light/triangle.h"
CCL_NAMESPACE_BEGIN
/* Simple CDF based sampling over all lights in the scene, without taking into
* account shading position or normal. */
ccl_device int light_distribution_sample(KernelGlobals kg, ccl_private float &randu)
{
/* This is basically std::upper_bound as used by PBRT, to find a point light or
* triangle to emit from, proportional to area. a good improvement would be to
* also sample proportional to power, though it's not so well defined with
* arbitrary shaders. */
int first = 0;
int len = kernel_data.integrator.num_distribution + 1;
float r = randu;
do {
int half_len = len >> 1;
int middle = first + half_len;
if (r < kernel_data_fetch(light_distribution, middle).totarea) {
len = half_len;
}
else {
first = middle + 1;
len = len - half_len - 1;
}
} while (len > 0);
/* Clamping should not be needed but float rounding errors seem to
* make this fail on rare occasions. */
int index = clamp(first - 1, 0, kernel_data.integrator.num_distribution - 1);
/* Rescale to reuse random number. this helps the 2D samples within
* each area light be stratified as well. */
float distr_min = kernel_data_fetch(light_distribution, index).totarea;
float distr_max = kernel_data_fetch(light_distribution, index + 1).totarea;
randu = (r - distr_min) / (distr_max - distr_min);
return index;
}
ccl_device_noinline bool light_distribution_sample(KernelGlobals kg,
ccl_private float &randu,
const float randv,
const float time,
const float3 P,
const int bounce,
const uint32_t path_flag,
ccl_private int &emitter_object,
ccl_private int &emitter_prim,
ccl_private int &emitter_shader_flag,
ccl_private float &emitter_pdf_selection)
{
/* Sample light index from distribution. */
const int index = light_distribution_sample(kg, randu);
ccl_global const KernelLightDistribution *kdistribution = &kernel_data_fetch(light_distribution,
index);
emitter_object = kdistribution->mesh_light.object_id;
emitter_prim = kdistribution->prim;
emitter_shader_flag = kdistribution->mesh_light.shader_flag;
emitter_pdf_selection = kernel_data.integrator.distribution_pdf_lights;
return true;
}
ccl_device_inline float light_distribution_pdf_lamp(KernelGlobals kg)
{
return kernel_data.integrator.distribution_pdf_lights;
}
CCL_NAMESPACE_END

View File

@@ -3,31 +3,18 @@
#pragma once
#include "kernel/geom/geom.h"
#include "kernel/light/area.h"
#include "kernel/light/background.h"
#include "kernel/light/distant.h"
#include "kernel/light/point.h"
#include "kernel/light/spot.h"
#include "kernel/light/triangle.h"
#include "kernel/sample/mapping.h"
CCL_NAMESPACE_BEGIN
/* Light Sample result */
typedef struct LightSample {
float3 P; /* position on light, or direction for distant light */
float3 Ng; /* normal on light */
float3 D; /* direction from shading point to light */
float t; /* distance to light (FLT_MAX for distant light) */
float u, v; /* parametric coordinate on primitive */
float pdf; /* light sampling probability density function */
float eval_fac; /* intensity multiplier */
int object; /* object id for triangle/curve lights */
int prim; /* primitive id for triangle/curve lights */
int shader; /* shader id */
int lamp; /* lamp id */
int group; /* lightgroup */
LightType type; /* type of light */
} LightSample;
/* Regular Light */
/* Sample point on an individual light. */
template<bool in_volume_segment>
ccl_device_inline bool light_sample(KernelGlobals kg,
@@ -63,28 +50,15 @@ ccl_device_inline bool light_sample(KernelGlobals kg,
ls->Ng = zero_float3();
ls->D = zero_float3();
ls->pdf = 1.0f;
ls->eval_fac = 0.0f;
ls->t = FLT_MAX;
return true;
}
if (type == LIGHT_DISTANT) {
/* distant light */
float3 lightD = make_float3(klight->co[0], klight->co[1], klight->co[2]);
float3 D = lightD;
float radius = klight->distant.radius;
float invarea = klight->distant.invarea;
if (radius > 0.0f)
D = distant_light_sample(D, radius, randu, randv);
ls->P = D;
ls->Ng = D;
ls->D = -D;
ls->t = FLT_MAX;
float costheta = dot(lightD, D);
ls->pdf = invarea / (costheta * costheta * costheta);
ls->eval_fac = ls->pdf;
if (!distant_light_sample(klight, randu, randv, ls)) {
return false;
}
}
else if (type == LIGHT_BACKGROUND) {
/* infinite area light (e.g. light dome or env light) */
@@ -96,139 +70,28 @@ ccl_device_inline bool light_sample(KernelGlobals kg,
ls->t = FLT_MAX;
ls->eval_fac = 1.0f;
}
else if (type == LIGHT_SPOT) {
if (!spot_light_sample<in_volume_segment>(klight, randu, randv, P, ls)) {
return false;
}
}
else if (type == LIGHT_POINT) {
if (!point_light_sample<in_volume_segment>(klight, randu, randv, P, ls)) {
return false;
}
}
else {
ls->P = make_float3(klight->co[0], klight->co[1], klight->co[2]);
if (type == LIGHT_SPOT) {
const float3 center = make_float3(klight->co[0], klight->co[1], klight->co[2]);
const float radius = klight->spot.radius;
const float3 dir = make_float3(
klight->spot.dir[0], klight->spot.dir[1], klight->spot.dir[2]);
/* disk oriented normal */
const float3 lightN = normalize(P - center);
ls->P = center;
if (radius > 0.0f)
/* disk light */
ls->P += disk_light_sample(lightN, randu, randv) * radius;
const float invarea = klight->spot.invarea;
ls->pdf = invarea;
ls->D = normalize_len(ls->P - P, &ls->t);
/* we set the light normal to the outgoing direction to support texturing */
ls->Ng = -ls->D;
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
/* spot light attenuation */
ls->eval_fac *= spot_light_attenuation(
dir, klight->spot.spot_angle, klight->spot.spot_smooth, -ls->D);
if (!in_volume_segment && ls->eval_fac == 0.0f) {
return false;
}
float2 uv = map_to_sphere(ls->Ng);
ls->u = uv.x;
ls->v = uv.y;
ls->pdf *= lamp_light_pdf(kg, lightN, -ls->D, ls->t);
}
else if (type == LIGHT_POINT) {
float3 center = make_float3(klight->co[0], klight->co[1], klight->co[2]);
float radius = klight->spot.radius;
/* disk oriented normal */
const float3 lightN = normalize(P - center);
ls->P = center;
if (radius > 0.0f) {
ls->P += disk_light_sample(lightN, randu, randv) * radius;
}
ls->pdf = klight->spot.invarea;
ls->D = normalize_len(ls->P - P, &ls->t);
/* we set the light normal to the outgoing direction to support texturing */
ls->Ng = -ls->D;
ls->eval_fac = M_1_PI_F * 0.25f * klight->spot.invarea;
if (!in_volume_segment && ls->eval_fac == 0.0f) {
return false;
}
float2 uv = map_to_sphere(ls->Ng);
ls->u = uv.x;
ls->v = uv.y;
ls->pdf *= lamp_light_pdf(kg, lightN, -ls->D, ls->t);
}
else {
/* area light */
float3 axisu = make_float3(
klight->area.axisu[0], klight->area.axisu[1], klight->area.axisu[2]);
float3 axisv = make_float3(
klight->area.axisv[0], klight->area.axisv[1], klight->area.axisv[2]);
float3 Ng = make_float3(klight->area.dir[0], klight->area.dir[1], klight->area.dir[2]);
float invarea = fabsf(klight->area.invarea);
bool is_round = (klight->area.invarea < 0.0f);
if (!in_volume_segment) {
if (dot(ls->P - P, Ng) > 0.0f) {
return false;
}
}
float3 inplane;
if (is_round || in_volume_segment) {
inplane = ellipse_sample(axisu * 0.5f, axisv * 0.5f, randu, randv);
ls->P += inplane;
ls->pdf = invarea;
}
else {
inplane = ls->P;
float3 sample_axisu = axisu;
float3 sample_axisv = axisv;
if (!in_volume_segment && klight->area.tan_spread > 0.0f) {
if (!light_spread_clamp_area_light(
P, Ng, &ls->P, &sample_axisu, &sample_axisv, klight->area.tan_spread)) {
return false;
}
}
ls->pdf = rect_light_sample(P, &ls->P, sample_axisu, sample_axisv, randu, randv, true);
inplane = ls->P - inplane;
}
const float light_u = dot(inplane, axisu) * (1.0f / dot(axisu, axisu));
const float light_v = dot(inplane, axisv) * (1.0f / dot(axisv, axisv));
/* NOTE: Return barycentric coordinates in the same notation as Embree and OptiX. */
ls->u = light_v + 0.5f;
ls->v = -light_u - light_v;
ls->Ng = Ng;
ls->D = normalize_len(ls->P - P, &ls->t);
ls->eval_fac = 0.25f * invarea;
if (klight->area.tan_spread > 0.0f) {
/* Area Light spread angle attenuation */
ls->eval_fac *= light_spread_attenuation(
ls->D, ls->Ng, klight->area.tan_spread, klight->area.normalize_spread);
}
if (is_round) {
ls->pdf *= lamp_light_pdf(kg, Ng, -ls->D, ls->t);
}
/* area light */
if (!area_light_sample<in_volume_segment>(klight, randu, randv, P, ls)) {
return false;
}
}
ls->pdf *= kernel_data.integrator.pdf_lights;
return in_volume_segment || (ls->pdf > 0.0f);
}
/* Intersect ray with individual light. */
ccl_device bool lights_intersect(KernelGlobals kg,
IntegratorState state,
ccl_private const Ray *ccl_restrict ray,
@@ -238,7 +101,7 @@ ccl_device bool lights_intersect(KernelGlobals kg,
const int last_type,
const uint32_t path_flag)
{
for (int lamp = 0; lamp < kernel_data.integrator.num_all_lights; lamp++) {
for (int lamp = 0; lamp < kernel_data.integrator.num_lights; lamp++) {
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, lamp);
if (path_flag & PATH_RAY_CAMERA) {
@@ -271,76 +134,17 @@ ccl_device bool lights_intersect(KernelGlobals kg,
float t = 0.0f, u = 0.0f, v = 0.0f;
if (type == LIGHT_SPOT) {
/* Spot/Disk light. */
const float3 lightP = make_float3(klight->co[0], klight->co[1], klight->co[2]);
const float radius = klight->spot.radius;
if (radius == 0.0f) {
continue;
}
/* disk oriented normal */
const float3 lightN = normalize(ray->P - lightP);
/* One sided. */
if (dot(ray->D, lightN) >= 0.0f) {
continue;
}
float3 P;
if (!ray_disk_intersect(
ray->P, ray->D, ray->tmin, ray->tmax, lightP, lightN, radius, &P, &t)) {
if (!spot_light_intersect(klight, ray, &t)) {
continue;
}
}
else if (type == LIGHT_POINT) {
/* Sphere light (aka, aligned disk light). */
const float3 lightP = make_float3(klight->co[0], klight->co[1], klight->co[2]);
const float radius = klight->spot.radius;
if (radius == 0.0f) {
continue;
}
/* disk oriented normal */
const float3 lightN = normalize(ray->P - lightP);
float3 P;
if (!ray_disk_intersect(
ray->P, ray->D, ray->tmin, ray->tmax, lightP, lightN, radius, &P, &t)) {
if (!point_light_intersect(klight, ray, &t)) {
continue;
}
}
else if (type == LIGHT_AREA) {
/* Area light. */
const float invarea = fabsf(klight->area.invarea);
const bool is_round = (klight->area.invarea < 0.0f);
if (invarea == 0.0f) {
continue;
}
const float3 axisu = make_float3(
klight->area.axisu[0], klight->area.axisu[1], klight->area.axisu[2]);
const float3 axisv = make_float3(
klight->area.axisv[0], klight->area.axisv[1], klight->area.axisv[2]);
const float3 Ng = make_float3(klight->area.dir[0], klight->area.dir[1], klight->area.dir[2]);
/* One sided. */
if (dot(ray->D, Ng) >= 0.0f) {
continue;
}
const float3 light_P = make_float3(klight->co[0], klight->co[1], klight->co[2]);
float3 P;
if (!ray_quad_intersect(ray->P,
ray->D,
ray->tmin,
ray->tmax,
light_P,
axisu,
axisv,
Ng,
&P,
&t,
&u,
&v,
is_round)) {
if (!area_light_intersect(klight, ray, &t, &u, &v)) {
continue;
}
}
@@ -362,78 +166,7 @@ ccl_device bool lights_intersect(KernelGlobals kg,
return isect->prim != PRIM_NONE;
}
ccl_device bool light_sample_from_distant_ray(KernelGlobals kg,
const float3 ray_D,
const int lamp,
ccl_private LightSample *ccl_restrict ls)
{
ccl_global const KernelLight *klight = &kernel_data_fetch(lights, lamp);
const int shader = klight->shader_id;
const float radius = klight->distant.radius;
const LightType type = (LightType)klight->type;
if (type != LIGHT_DISTANT) {
return false;
}
if (!(shader & SHADER_USE_MIS)) {
return false;
}
if (radius == 0.0f) {
return false;
}
/* a distant light is infinitely far away, but equivalent to a disk
* shaped light exactly 1 unit away from the current shading point.
*
* radius t^2/cos(theta)
* <----------> t = sqrt(1^2 + tan(theta)^2)
* tan(th) area = radius*radius*pi
* <----->
* \ | (1 + tan(theta)^2)/cos(theta)
* \ | (1 + tan(acos(cos(theta)))^2)/cos(theta)
* t \th| 1 simplifies to
* \-| 1/(cos(theta)^3)
* \| magic!
* P
*/
float3 lightD = make_float3(klight->co[0], klight->co[1], klight->co[2]);
float costheta = dot(-lightD, ray_D);
float cosangle = klight->distant.cosangle;
/* Workaround to prevent a hang in the classroom scene with AMD HIP drivers 22.10,
* Remove when a compiler fix is available. */
#ifdef __HIP__
ls->shader = klight->shader_id;
#endif
if (costheta < cosangle)
return false;
ls->type = type;
#ifndef __HIP__
ls->shader = klight->shader_id;
#endif
ls->object = PRIM_NONE;
ls->prim = PRIM_NONE;
ls->lamp = lamp;
/* todo: missing texture coordinates */
ls->u = 0.0f;
ls->v = 0.0f;
ls->t = FLT_MAX;
ls->P = -ray_D;
ls->Ng = -ray_D;
ls->D = ray_D;
ls->group = lamp_lightgroup(kg, lamp);
/* compute pdf */
float invarea = klight->distant.invarea;
ls->pdf = invarea / (costheta * costheta * costheta);
ls->eval_fac = ls->pdf;
ls->pdf *= kernel_data.integrator.pdf_lights;
return true;
}
/* Setup light sample from intersection. */
ccl_device bool light_sample_from_intersection(KernelGlobals kg,
ccl_private const Intersection *ccl_restrict isect,
@@ -456,102 +189,18 @@ ccl_device bool light_sample_from_intersection(KernelGlobals kg,
ls->group = lamp_lightgroup(kg, lamp);
if (type == LIGHT_SPOT) {
const float3 center = make_float3(klight->co[0], klight->co[1], klight->co[2]);
const float3 dir = make_float3(klight->spot.dir[0], klight->spot.dir[1], klight->spot.dir[2]);
/* the normal of the oriented disk */
const float3 lightN = normalize(ray_P - center);
/* We set the light normal to the outgoing direction to support texturing. */
ls->Ng = -ls->D;
float invarea = klight->spot.invarea;
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
ls->pdf = invarea;
/* spot light attenuation */
ls->eval_fac *= spot_light_attenuation(
dir, klight->spot.spot_angle, klight->spot.spot_smooth, -ls->D);
if (ls->eval_fac == 0.0f) {
if (!spot_light_sample_from_intersection(klight, isect, ray_P, ray_D, ls)) {
return false;
}
float2 uv = map_to_sphere(ls->Ng);
ls->u = uv.x;
ls->v = uv.y;
/* compute pdf */
if (ls->t != FLT_MAX)
ls->pdf *= lamp_light_pdf(kg, lightN, -ls->D, ls->t);
else
ls->pdf = 0.f;
}
else if (type == LIGHT_POINT) {
const float3 center = make_float3(klight->co[0], klight->co[1], klight->co[2]);
const float3 lighN = normalize(ray_P - center);
/* We set the light normal to the outgoing direction to support texturing. */
ls->Ng = -ls->D;
float invarea = klight->spot.invarea;
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
ls->pdf = invarea;
if (ls->eval_fac == 0.0f) {
if (!point_light_sample_from_intersection(klight, isect, ray_P, ray_D, ls)) {
return false;
}
float2 uv = map_to_sphere(ls->Ng);
ls->u = uv.x;
ls->v = uv.y;
/* compute pdf */
if (ls->t != FLT_MAX)
ls->pdf *= lamp_light_pdf(kg, lighN, -ls->D, ls->t);
else
ls->pdf = 0.f;
}
else if (type == LIGHT_AREA) {
/* area light */
float invarea = fabsf(klight->area.invarea);
float3 axisu = make_float3(
klight->area.axisu[0], klight->area.axisu[1], klight->area.axisu[2]);
float3 axisv = make_float3(
klight->area.axisv[0], klight->area.axisv[1], klight->area.axisv[2]);
float3 Ng = make_float3(klight->area.dir[0], klight->area.dir[1], klight->area.dir[2]);
float3 light_P = make_float3(klight->co[0], klight->co[1], klight->co[2]);
ls->u = isect->u;
ls->v = isect->v;
ls->D = ray_D;
ls->Ng = Ng;
const bool is_round = (klight->area.invarea < 0.0f);
if (is_round) {
ls->pdf = invarea * lamp_light_pdf(kg, Ng, -ray_D, ls->t);
}
else {
float3 sample_axisu = axisu;
float3 sample_axisv = axisv;
if (klight->area.tan_spread > 0.0f) {
if (!light_spread_clamp_area_light(
ray_P, Ng, &light_P, &sample_axisu, &sample_axisv, klight->area.tan_spread)) {
return false;
}
}
ls->pdf = rect_light_sample(ray_P, &light_P, sample_axisu, sample_axisv, 0, 0, false);
}
ls->eval_fac = 0.25f * invarea;
if (klight->area.tan_spread > 0.0f) {
/* Area Light spread angle attenuation */
ls->eval_fac *= light_spread_attenuation(
ls->D, ls->Ng, klight->area.tan_spread, klight->area.normalize_spread);
if (ls->eval_fac == 0.0f) {
return false;
}
if (!area_light_sample_from_intersection(klight, isect, ray_P, ray_D, ls)) {
return false;
}
}
else {
@@ -559,411 +208,33 @@ ccl_device bool light_sample_from_intersection(KernelGlobals kg,
return false;
}
ls->pdf *= kernel_data.integrator.pdf_lights;
return true;
}
/* Triangle Light */
/* Update light sample for changed new position, for MNEE. */
/* returns true if the triangle is has motion blur or an instancing transform applied */
ccl_device_inline bool triangle_world_space_vertices(
KernelGlobals kg, int object, int prim, float time, float3 V[3])
{
bool has_motion = false;
const int object_flag = kernel_data_fetch(object_flag, object);
if (object_flag & SD_OBJECT_HAS_VERTEX_MOTION && time >= 0.0f) {
motion_triangle_vertices(kg, object, prim, time, V);
has_motion = true;
}
else {
triangle_vertices(kg, prim, V);
}
if (!(object_flag & SD_OBJECT_TRANSFORM_APPLIED)) {
#ifdef __OBJECT_MOTION__
float object_time = (time >= 0.0f) ? time : 0.5f;
Transform tfm = object_fetch_transform_motion_test(kg, object, object_time, NULL);
#else
Transform tfm = object_fetch_transform(kg, object, OBJECT_TRANSFORM);
#endif
V[0] = transform_point(&tfm, V[0]);
V[1] = transform_point(&tfm, V[1]);
V[2] = transform_point(&tfm, V[2]);
has_motion = true;
}
return has_motion;
}
ccl_device_inline float triangle_light_pdf_area(KernelGlobals kg,
const float3 Ng,
const float3 I,
float t)
{
float pdf = kernel_data.integrator.pdf_triangles;
float cos_pi = fabsf(dot(Ng, I));
if (cos_pi == 0.0f)
return 0.0f;
return t * t * pdf / cos_pi;
}
ccl_device_forceinline float triangle_light_pdf(KernelGlobals kg,
ccl_private const ShaderData *sd,
float t)
{
/* A naive heuristic to decide between costly solid angle sampling
* and simple area sampling, comparing the distance to the triangle plane
* to the length of the edges of the triangle. */
float3 V[3];
bool has_motion = triangle_world_space_vertices(kg, sd->object, sd->prim, sd->time, V);
const float3 e0 = V[1] - V[0];
const float3 e1 = V[2] - V[0];
const float3 e2 = V[2] - V[1];
const float longest_edge_squared = max(len_squared(e0), max(len_squared(e1), len_squared(e2)));
const float3 N = cross(e0, e1);
const float distance_to_plane = fabsf(dot(N, sd->I * t)) / dot(N, N);
if (longest_edge_squared > distance_to_plane * distance_to_plane) {
/* sd contains the point on the light source
* calculate Px, the point that we're shading */
const float3 Px = sd->P + sd->I * t;
const float3 v0_p = V[0] - Px;
const float3 v1_p = V[1] - Px;
const float3 v2_p = V[2] - Px;
const float3 u01 = safe_normalize(cross(v0_p, v1_p));
const float3 u02 = safe_normalize(cross(v0_p, v2_p));
const float3 u12 = safe_normalize(cross(v1_p, v2_p));
const float alpha = fast_acosf(dot(u02, u01));
const float beta = fast_acosf(-dot(u01, u12));
const float gamma = fast_acosf(dot(u02, u12));
const float solid_angle = alpha + beta + gamma - M_PI_F;
/* pdf_triangles is calculated over triangle area, but we're not sampling over its area */
if (UNLIKELY(solid_angle == 0.0f)) {
return 0.0f;
}
else {
float area = 1.0f;
if (has_motion) {
/* get the center frame vertices, this is what the PDF was calculated from */
triangle_world_space_vertices(kg, sd->object, sd->prim, -1.0f, V);
area = triangle_area(V[0], V[1], V[2]);
}
else {
area = 0.5f * len(N);
}
const float pdf = area * kernel_data.integrator.pdf_triangles;
return pdf / solid_angle;
}
}
else {
float pdf = triangle_light_pdf_area(kg, sd->Ng, sd->I, t);
if (has_motion) {
const float area = 0.5f * len(N);
if (UNLIKELY(area == 0.0f)) {
return 0.0f;
}
/* scale the PDF.
* area = the area the sample was taken from
* area_pre = the are from which pdf_triangles was calculated from */
triangle_world_space_vertices(kg, sd->object, sd->prim, -1.0f, V);
const float area_pre = triangle_area(V[0], V[1], V[2]);
pdf = pdf * area_pre / area;
}
return pdf;
}
}
template<bool in_volume_segment>
ccl_device_forceinline void triangle_light_sample(KernelGlobals kg,
int prim,
int object,
float randu,
float randv,
float time,
ccl_device_forceinline void light_update_position(KernelGlobals kg,
ccl_private LightSample *ls,
const float3 P)
{
/* A naive heuristic to decide between costly solid angle sampling
* and simple area sampling, comparing the distance to the triangle plane
* to the length of the edges of the triangle. */
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, ls->lamp);
float3 V[3];
bool has_motion = triangle_world_space_vertices(kg, object, prim, time, V);
const float3 e0 = V[1] - V[0];
const float3 e1 = V[2] - V[0];
const float3 e2 = V[2] - V[1];
const float longest_edge_squared = max(len_squared(e0), max(len_squared(e1), len_squared(e2)));
const float3 N0 = cross(e0, e1);
float Nl = 0.0f;
ls->Ng = safe_normalize_len(N0, &Nl);
float area = 0.5f * Nl;
/* flip normal if necessary */
const int object_flag = kernel_data_fetch(object_flag, object);
if (object_flag & SD_OBJECT_NEGATIVE_SCALE_APPLIED) {
ls->Ng = -ls->Ng;
if (ls->type == LIGHT_POINT) {
point_light_update_position(klight, ls, P);
}
ls->eval_fac = 1.0f;
ls->shader = kernel_data_fetch(tri_shader, prim);
ls->object = object;
ls->prim = prim;
ls->lamp = LAMP_NONE;
ls->shader |= SHADER_USE_MIS;
ls->type = LIGHT_TRIANGLE;
ls->group = object_lightgroup(kg, object);
float distance_to_plane = fabsf(dot(N0, V[0] - P) / dot(N0, N0));
if (!in_volume_segment && (longest_edge_squared > distance_to_plane * distance_to_plane)) {
/* see James Arvo, "Stratified Sampling of Spherical Triangles"
* http://www.graphics.cornell.edu/pubs/1995/Arv95c.pdf */
/* project the triangle to the unit sphere
* and calculate its edges and angles */
const float3 v0_p = V[0] - P;
const float3 v1_p = V[1] - P;
const float3 v2_p = V[2] - P;
const float3 u01 = safe_normalize(cross(v0_p, v1_p));
const float3 u02 = safe_normalize(cross(v0_p, v2_p));
const float3 u12 = safe_normalize(cross(v1_p, v2_p));
const float3 A = safe_normalize(v0_p);
const float3 B = safe_normalize(v1_p);
const float3 C = safe_normalize(v2_p);
const float cos_alpha = dot(u02, u01);
const float cos_beta = -dot(u01, u12);
const float cos_gamma = dot(u02, u12);
/* calculate dihedral angles */
const float alpha = fast_acosf(cos_alpha);
const float beta = fast_acosf(cos_beta);
const float gamma = fast_acosf(cos_gamma);
/* the area of the unit spherical triangle = solid angle */
const float solid_angle = alpha + beta + gamma - M_PI_F;
/* precompute a few things
* these could be re-used to take several samples
* as they are independent of randu/randv */
const float cos_c = dot(A, B);
const float sin_alpha = fast_sinf(alpha);
const float product = sin_alpha * cos_c;
/* Select a random sub-area of the spherical triangle
* and calculate the third vertex C_ of that new triangle */
const float phi = randu * solid_angle - alpha;
float s, t;
fast_sincosf(phi, &s, &t);
const float u = t - cos_alpha;
const float v = s + product;
const float3 U = safe_normalize(C - dot(C, A) * A);
float q = 1.0f;
const float det = ((v * s + u * t) * sin_alpha);
if (det != 0.0f) {
q = ((v * t - u * s) * cos_alpha - v) / det;
}
const float temp = max(1.0f - q * q, 0.0f);
const float3 C_ = safe_normalize(q * A + sqrtf(temp) * U);
/* Finally, select a random point along the edge of the new triangle
* That point on the spherical triangle is the sampled ray direction */
const float z = 1.0f - randv * (1.0f - dot(C_, B));
ls->D = z * B + safe_sqrtf(1.0f - z * z) * safe_normalize(C_ - dot(C_, B) * B);
/* calculate intersection with the planar triangle */
if (!ray_triangle_intersect(
P, ls->D, 0.0f, FLT_MAX, V[0], V[1], V[2], &ls->u, &ls->v, &ls->t)) {
ls->pdf = 0.0f;
return;
}
ls->P = P + ls->D * ls->t;
/* pdf_triangles is calculated over triangle area, but we're sampling over solid angle */
if (UNLIKELY(solid_angle == 0.0f)) {
ls->pdf = 0.0f;
return;
}
else {
if (has_motion) {
/* get the center frame vertices, this is what the PDF was calculated from */
triangle_world_space_vertices(kg, object, prim, -1.0f, V);
area = triangle_area(V[0], V[1], V[2]);
}
const float pdf = area * kernel_data.integrator.pdf_triangles;
ls->pdf = pdf / solid_angle;
}
else if (ls->type == LIGHT_SPOT) {
spot_light_update_position(klight, ls, P);
}
else {
/* compute random point in triangle. From Eric Heitz's "A Low-Distortion Map Between Triangle
* and Square" */
float u = randu;
float v = randv;
if (v > u) {
u *= 0.5f;
v -= u;
}
else {
v *= 0.5f;
u -= v;
}
const float t = 1.0f - u - v;
ls->P = u * V[0] + v * V[1] + t * V[2];
/* compute incoming direction, distance and pdf */
ls->D = normalize_len(ls->P - P, &ls->t);
ls->pdf = triangle_light_pdf_area(kg, ls->Ng, -ls->D, ls->t);
if (has_motion && area != 0.0f) {
/* scale the PDF.
* area = the area the sample was taken from
* area_pre = the are from which pdf_triangles was calculated from */
triangle_world_space_vertices(kg, object, prim, -1.0f, V);
const float area_pre = triangle_area(V[0], V[1], V[2]);
ls->pdf = ls->pdf * area_pre / area;
}
ls->u = u;
ls->v = v;
else if (ls->type == LIGHT_AREA) {
area_light_update_position(klight, ls, P);
}
}
/* Light Distribution */
ccl_device int light_distribution_sample(KernelGlobals kg, ccl_private float *randu)
{
/* This is basically std::upper_bound as used by PBRT, to find a point light or
* triangle to emit from, proportional to area. a good improvement would be to
* also sample proportional to power, though it's not so well defined with
* arbitrary shaders. */
int first = 0;
int len = kernel_data.integrator.num_distribution + 1;
float r = *randu;
do {
int half_len = len >> 1;
int middle = first + half_len;
if (r < kernel_data_fetch(light_distribution, middle).totarea) {
len = half_len;
}
else {
first = middle + 1;
len = len - half_len - 1;
}
} while (len > 0);
/* Clamping should not be needed but float rounding errors seem to
* make this fail on rare occasions. */
int index = clamp(first - 1, 0, kernel_data.integrator.num_distribution - 1);
/* Rescale to reuse random number. this helps the 2D samples within
* each area light be stratified as well. */
float distr_min = kernel_data_fetch(light_distribution, index).totarea;
float distr_max = kernel_data_fetch(light_distribution, index + 1).totarea;
*randu = (r - distr_min) / (distr_max - distr_min);
return index;
}
/* Generic Light */
/* Light info. */
ccl_device_inline bool light_select_reached_max_bounces(KernelGlobals kg, int index, int bounce)
{
return (bounce > kernel_data_fetch(lights, index).max_bounces);
}
template<bool in_volume_segment>
ccl_device_noinline bool light_distribution_sample(KernelGlobals kg,
float randu,
const float randv,
const float time,
const float3 P,
const int bounce,
const uint32_t path_flag,
ccl_private LightSample *ls)
{
/* Sample light index from distribution. */
const int index = light_distribution_sample(kg, &randu);
ccl_global const KernelLightDistribution *kdistribution = &kernel_data_fetch(light_distribution,
index);
const int prim = kdistribution->prim;
if (prim >= 0) {
/* Mesh light. */
const int object = kdistribution->mesh_light.object_id;
/* Exclude synthetic meshes from shadow catcher pass. */
if ((path_flag & PATH_RAY_SHADOW_CATCHER_PASS) &&
!(kernel_data_fetch(object_flag, object) & SD_OBJECT_SHADOW_CATCHER)) {
return false;
}
const int shader_flag = kdistribution->mesh_light.shader_flag;
triangle_light_sample<in_volume_segment>(kg, prim, object, randu, randv, time, ls, P);
ls->shader |= shader_flag;
return (ls->pdf > 0.0f);
}
const int lamp = -prim - 1;
if (UNLIKELY(light_select_reached_max_bounces(kg, lamp, bounce))) {
return false;
}
return light_sample<in_volume_segment>(kg, lamp, randu, randv, P, path_flag, ls);
}
ccl_device_inline bool light_distribution_sample_from_volume_segment(KernelGlobals kg,
float randu,
const float randv,
const float time,
const float3 P,
const int bounce,
const uint32_t path_flag,
ccl_private LightSample *ls)
{
return light_distribution_sample<true>(kg, randu, randv, time, P, bounce, path_flag, ls);
}
ccl_device_inline bool light_distribution_sample_from_position(KernelGlobals kg,
float randu,
const float randv,
const float time,
const float3 P,
const int bounce,
const uint32_t path_flag,
ccl_private LightSample *ls)
{
return light_distribution_sample<false>(kg, randu, randv, time, P, bounce, path_flag, ls);
}
ccl_device_inline bool light_distribution_sample_new_position(KernelGlobals kg,
const float randu,
const float randv,
const float time,
const float3 P,
ccl_private LightSample *ls)
{
/* Sample a new position on the same light, for volume sampling. */
if (ls->type == LIGHT_TRIANGLE) {
triangle_light_sample<false>(kg, ls->prim, ls->object, randu, randv, time, ls, P);
return (ls->pdf > 0.0f);
}
else {
return light_sample<false>(kg, ls->lamp, randu, randv, P, 0, ls);
}
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,136 @@
/* SPDX-License-Identifier: Apache-2.0
* Copyright 2011-2022 Blender Foundation */
#pragma once
#include "kernel/light/common.h"
CCL_NAMESPACE_BEGIN
template<bool in_volume_segment>
ccl_device_inline bool point_light_sample(const ccl_global KernelLight *klight,
const float randu,
const float randv,
const float3 P,
ccl_private LightSample *ls)
{
float3 center = klight->co;
float radius = klight->spot.radius;
/* disk oriented normal */
const float3 lightN = normalize(P - center);
ls->P = center;
if (radius > 0.0f) {
ls->P += disk_light_sample(lightN, randu, randv) * radius;
}
ls->pdf = klight->spot.invarea;
ls->D = normalize_len(ls->P - P, &ls->t);
/* we set the light normal to the outgoing direction to support texturing */
ls->Ng = -ls->D;
ls->eval_fac = M_1_PI_F * 0.25f * klight->spot.invarea;
if (!in_volume_segment && ls->eval_fac == 0.0f) {
return false;
}
float2 uv = map_to_sphere(ls->Ng);
ls->u = uv.x;
ls->v = uv.y;
ls->pdf *= lamp_light_pdf(lightN, -ls->D, ls->t);
return true;
}
ccl_device_forceinline void point_light_update_position(const ccl_global KernelLight *klight,
ccl_private LightSample *ls,
const float3 P)
{
ls->D = normalize_len(ls->P - P, &ls->t);
ls->Ng = -ls->D;
float2 uv = map_to_sphere(ls->Ng);
ls->u = uv.x;
ls->v = uv.y;
float invarea = klight->spot.invarea;
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
ls->pdf = invarea;
}
ccl_device_inline bool point_light_intersect(const ccl_global KernelLight *klight,
const ccl_private Ray *ccl_restrict ray,
ccl_private float *t)
{
/* Sphere light (aka, aligned disk light). */
const float3 lightP = klight->co;
const float radius = klight->spot.radius;
if (radius == 0.0f) {
return false;
}
/* disk oriented normal */
const float3 lightN = normalize(ray->P - lightP);
float3 P;
return ray_disk_intersect(ray->P, ray->D, ray->tmin, ray->tmax, lightP, lightN, radius, &P, t);
}
ccl_device_inline bool point_light_sample_from_intersection(
const ccl_global KernelLight *klight,
ccl_private const Intersection *ccl_restrict isect,
const float3 ray_P,
const float3 ray_D,
ccl_private LightSample *ccl_restrict ls)
{
const float3 lighN = normalize(ray_P - klight->co);
/* We set the light normal to the outgoing direction to support texturing. */
ls->Ng = -ls->D;
float invarea = klight->spot.invarea;
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
ls->pdf = invarea;
if (ls->eval_fac == 0.0f) {
return false;
}
float2 uv = map_to_sphere(ls->Ng);
ls->u = uv.x;
ls->v = uv.y;
/* compute pdf */
if (ls->t != FLT_MAX) {
ls->pdf *= lamp_light_pdf(lighN, -ls->D, ls->t);
}
else {
ls->pdf = 0.f;
}
return true;
}
template<bool in_volume_segment>
ccl_device_forceinline bool point_light_tree_parameters(const ccl_global KernelLight *klight,
const float3 centroid,
const float3 P,
ccl_private float &cos_theta_u,
ccl_private float2 &distance,
ccl_private float3 &point_to_centroid)
{
if (in_volume_segment) {
cos_theta_u = 1.0f; /* Any value in [-1, 1], irrelevant since theta = 0 */
return true;
}
float min_distance;
point_to_centroid = safe_normalize_len(centroid - P, &min_distance);
const float radius = klight->spot.radius;
const float hypotenus = sqrtf(sqr(radius) + sqr(min_distance));
cos_theta_u = min_distance / hypotenus;
distance = make_float2(hypotenus, min_distance);
return true;
}
CCL_NAMESPACE_END

View File

@@ -6,8 +6,13 @@
#include "kernel/integrator/path_state.h"
#include "kernel/integrator/surface_shader.h"
#include "kernel/light/distribution.h"
#include "kernel/light/light.h"
#ifdef __LIGHT_TREE__
# include "kernel/light/tree.h"
#endif
#include "kernel/sample/mapping.h"
#include "kernel/sample/mis.h"
@@ -277,6 +282,8 @@ ccl_device_inline void light_sample_to_volume_shadow_ray(
shadow_ray_setup(sd, ls, P, ray, false);
}
/* Multiple importance sampling weights. */
ccl_device_inline float light_sample_mis_weight_forward(KernelGlobals kg,
const float forward_pdf,
const float nee_pdf)
@@ -309,4 +316,333 @@ ccl_device_inline float light_sample_mis_weight_nee(KernelGlobals kg,
return power_heuristic(nee_pdf, forward_pdf);
}
/* Next event estimation sampling.
*
* Sample a position on a light in the scene, from a position on a surface or
* from a volume segment.
*
* Uses either a flat distribution or light tree. */
ccl_device_inline bool light_sample_from_volume_segment(KernelGlobals kg,
float randu,
float randv,
const float time,
const float3 P,
const float3 D,
const float t,
const int bounce,
const uint32_t path_flag,
ccl_private LightSample *ls)
{
/* Select an emitter. */
int emitter_object = 0;
int emitter_prim = 0;
int emitter_shader_flag = 0;
float emitter_pdf_selection = 0.0f;
#ifdef __LIGHT_TREE__
if (kernel_data.integrator.use_light_tree) {
if (!light_tree_sample<true>(kg,
randu,
randv,
time,
P,
D,
t,
SD_BSDF_HAS_TRANSMISSION,
bounce,
path_flag,
emitter_object,
emitter_prim,
emitter_shader_flag,
emitter_pdf_selection)) {
return false;
}
}
else
#endif
{
if (!light_distribution_sample(kg,
randu,
randv,
time,
P,
bounce,
path_flag,
emitter_object,
emitter_prim,
emitter_shader_flag,
emitter_pdf_selection)) {
return false;
}
}
/* Set first, triangle light sampling from flat distribution will override. */
ls->pdf_selection = emitter_pdf_selection;
/* Sample a point on the chosen emitter. */
if (emitter_prim >= 0) {
/* Mesh light. */
/* Exclude synthetic meshes from shadow catcher pass. */
if ((path_flag & PATH_RAY_SHADOW_CATCHER_PASS) &&
!(kernel_data_fetch(object_flag, emitter_object) & SD_OBJECT_SHADOW_CATCHER)) {
return false;
}
if (!triangle_light_sample<true>(
kg, emitter_prim, emitter_object, randu, randv, time, ls, P)) {
return false;
}
}
else {
/* Light object. */
const int lamp = ~emitter_prim;
if (UNLIKELY(light_select_reached_max_bounces(kg, lamp, bounce))) {
return false;
}
if (!light_sample<true>(kg, lamp, randu, randv, P, path_flag, ls)) {
return false;
}
}
ls->pdf *= ls->pdf_selection;
ls->shader |= emitter_shader_flag;
return (ls->pdf > 0);
}
ccl_device bool light_sample_from_position(KernelGlobals kg,
ccl_private const RNGState *rng_state,
float randu,
float randv,
const float time,
const float3 P,
const float3 N,
const int shader_flags,
const int bounce,
const uint32_t path_flag,
ccl_private LightSample *ls)
{
/* Select an emitter. */
int emitter_object = 0;
int emitter_prim = 0;
int emitter_shader_flag = 0;
float emitter_pdf_selection = 0.0f;
#ifdef __LIGHT_TREE__
if (kernel_data.integrator.use_light_tree) {
if (!light_tree_sample<false>(kg,
randu,
randv,
time,
P,
N,
0,
shader_flags,
bounce,
path_flag,
emitter_object,
emitter_prim,
emitter_shader_flag,
emitter_pdf_selection)) {
return false;
}
}
else
#endif
{
if (!light_distribution_sample(kg,
randu,
randv,
time,
P,
bounce,
path_flag,
emitter_object,
emitter_prim,
emitter_shader_flag,
emitter_pdf_selection)) {
return false;
}
}
/* Set first, triangle light sampling from flat distribution will override. */
ls->pdf_selection = emitter_pdf_selection;
/* Sample a point on the chosen emitter.
* TODO: deduplicate code with light_sample_from_volume_segment? */
if (emitter_prim >= 0) {
/* Mesh light. */
/* Exclude synthetic meshes from shadow catcher pass. */
if ((path_flag & PATH_RAY_SHADOW_CATCHER_PASS) &&
!(kernel_data_fetch(object_flag, emitter_object) & SD_OBJECT_SHADOW_CATCHER)) {
return false;
}
if (!triangle_light_sample<false>(
kg, emitter_prim, emitter_object, randu, randv, time, ls, P)) {
return false;
}
}
else {
/* Light object. */
const int lamp = ~emitter_prim;
if (UNLIKELY(light_select_reached_max_bounces(kg, lamp, bounce))) {
return false;
}
if (!light_sample<false>(kg, lamp, randu, randv, P, path_flag, ls)) {
return false;
}
}
ls->pdf *= ls->pdf_selection;
ls->shader |= emitter_shader_flag;
return (ls->pdf > 0);
}
ccl_device_inline bool light_sample_new_position(KernelGlobals kg,
const float randu,
const float randv,
const float time,
const float3 P,
ccl_private LightSample *ls)
{
/* Sample a new position on the same light, for volume sampling. */
if (ls->type == LIGHT_TRIANGLE) {
if (!triangle_light_sample<false>(kg, ls->prim, ls->object, randu, randv, time, ls, P)) {
return false;
}
#ifdef __LIGHT_TREE__
if (kernel_data.integrator.use_light_tree) {
ls->pdf *= ls->pdf_selection;
}
else
#endif
{
/* Handled in triangle_light_sample for efficiency. */
}
return true;
}
else {
if (!light_sample<false>(kg, ls->lamp, randu, randv, P, 0, ls)) {
return false;
}
ls->pdf *= ls->pdf_selection;
return true;
}
}
ccl_device_forceinline void light_sample_update_position(KernelGlobals kg,
ccl_private LightSample *ls,
const float3 P)
{
/* Update light sample for new shading point position, while keeping
* position on the light fixed. */
/* NOTE : preserve pdf in area measure. */
light_update_position(kg, ls, P);
/* Re-apply already computed selection pdf. */
ls->pdf *= ls->pdf_selection;
}
/* Forward sampling.
*
* Multiple importance sampling weights for hitting surface, light or background
* through indirect light ray.
*
* The BSDF or phase pdf from the previous bounce was stored in mis_ray_pdf and
* is used for balancing with the light sampling pdf. */
ccl_device_inline float light_sample_mis_weight_forward_surface(KernelGlobals kg,
IntegratorState state,
const uint32_t path_flag,
const ccl_private ShaderData *sd)
{
const float bsdf_pdf = INTEGRATOR_STATE(state, path, mis_ray_pdf);
const float t = sd->ray_length;
float pdf = triangle_light_pdf(kg, sd, t);
/* Light selection pdf. */
#ifdef __LIGHT_TREE__
if (kernel_data.integrator.use_light_tree) {
float3 ray_P = INTEGRATOR_STATE(state, ray, P);
const float3 N = INTEGRATOR_STATE(state, path, mis_origin_n);
uint lookup_offset = kernel_data_fetch(object_lookup_offset, sd->object);
uint prim_offset = kernel_data_fetch(object_prim_offset, sd->object);
pdf *= light_tree_pdf(kg, ray_P, N, path_flag, sd->prim - prim_offset + lookup_offset);
}
else
#endif
{
/* Handled in triangle_light_pdf for efficiency. */
}
return light_sample_mis_weight_forward(kg, bsdf_pdf, pdf);
}
ccl_device_inline float light_sample_mis_weight_forward_lamp(KernelGlobals kg,
IntegratorState state,
const uint32_t path_flag,
const ccl_private LightSample *ls,
const float3 P)
{
const float mis_ray_pdf = INTEGRATOR_STATE(state, path, mis_ray_pdf);
float pdf = ls->pdf;
/* Light selection pdf. */
#ifdef __LIGHT_TREE__
if (kernel_data.integrator.use_light_tree) {
const float3 N = INTEGRATOR_STATE(state, path, mis_origin_n);
pdf *= light_tree_pdf(kg, P, N, path_flag, ~ls->lamp);
}
else
#endif
{
pdf *= light_distribution_pdf_lamp(kg);
}
return light_sample_mis_weight_forward(kg, mis_ray_pdf, pdf);
}
ccl_device_inline float light_sample_mis_weight_forward_distant(KernelGlobals kg,
IntegratorState state,
const uint32_t path_flag,
const ccl_private LightSample *ls)
{
const float3 ray_P = INTEGRATOR_STATE(state, ray, P);
return light_sample_mis_weight_forward_lamp(kg, state, path_flag, ls, ray_P);
}
ccl_device_inline float light_sample_mis_weight_forward_background(KernelGlobals kg,
IntegratorState state,
const uint32_t path_flag)
{
const float3 ray_P = INTEGRATOR_STATE(state, ray, P);
const float3 ray_D = INTEGRATOR_STATE(state, ray, D);
const float mis_ray_pdf = INTEGRATOR_STATE(state, path, mis_ray_pdf);
float pdf = background_light_pdf(kg, ray_P, ray_D);
/* Light selection pdf. */
#ifdef __LIGHT_TREE__
if (kernel_data.integrator.use_light_tree) {
const float3 N = INTEGRATOR_STATE(state, path, mis_origin_n);
pdf *= light_tree_pdf(kg, ray_P, N, path_flag, ~kernel_data.background.light_index);
}
else
#endif
{
pdf *= light_distribution_pdf_lamp(kg);
}
return light_sample_mis_weight_forward(kg, mis_ray_pdf, pdf);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,179 @@
/* SPDX-License-Identifier: Apache-2.0
* Copyright 2011-2022 Blender Foundation */
#pragma once
#include "kernel/light/common.h"
CCL_NAMESPACE_BEGIN
ccl_device float spot_light_attenuation(float3 dir,
float cos_half_spot_angle,
float spot_smooth,
float3 N)
{
float attenuation = dot(dir, N);
if (attenuation <= cos_half_spot_angle) {
attenuation = 0.0f;
}
else {
float t = attenuation - cos_half_spot_angle;
if (t < spot_smooth && spot_smooth != 0.0f)
attenuation *= smoothstepf(t / spot_smooth);
}
return attenuation;
}
template<bool in_volume_segment>
ccl_device_inline bool spot_light_sample(const ccl_global KernelLight *klight,
const float randu,
const float randv,
const float3 P,
ccl_private LightSample *ls)
{
ls->P = klight->co;
const float3 center = klight->co;
const float radius = klight->spot.radius;
/* disk oriented normal */
const float3 lightN = normalize(P - center);
ls->P = center;
if (radius > 0.0f) {
/* disk light */
ls->P += disk_light_sample(lightN, randu, randv) * radius;
}
const float invarea = klight->spot.invarea;
ls->pdf = invarea;
ls->D = normalize_len(ls->P - P, &ls->t);
/* we set the light normal to the outgoing direction to support texturing */
ls->Ng = -ls->D;
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
/* spot light attenuation */
ls->eval_fac *= spot_light_attenuation(
klight->spot.dir, klight->spot.cos_half_spot_angle, klight->spot.spot_smooth, -ls->D);
if (!in_volume_segment && ls->eval_fac == 0.0f) {
return false;
}
float2 uv = map_to_sphere(ls->Ng);
ls->u = uv.x;
ls->v = uv.y;
ls->pdf *= lamp_light_pdf(lightN, -ls->D, ls->t);
return true;
}
ccl_device_forceinline void spot_light_update_position(const ccl_global KernelLight *klight,
ccl_private LightSample *ls,
const float3 P)
{
ls->D = normalize_len(ls->P - P, &ls->t);
ls->Ng = -ls->D;
float2 uv = map_to_sphere(ls->Ng);
ls->u = uv.x;
ls->v = uv.y;
float invarea = klight->spot.invarea;
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
ls->pdf = invarea;
/* spot light attenuation */
ls->eval_fac *= spot_light_attenuation(
klight->spot.dir, klight->spot.cos_half_spot_angle, klight->spot.spot_smooth, ls->Ng);
}
ccl_device_inline bool spot_light_intersect(const ccl_global KernelLight *klight,
const ccl_private Ray *ccl_restrict ray,
ccl_private float *t)
{
/* Spot/Disk light. */
const float3 lightP = klight->co;
const float radius = klight->spot.radius;
if (radius == 0.0f) {
return false;
}
/* disk oriented normal */
const float3 lightN = normalize(ray->P - lightP);
/* One sided. */
if (dot(ray->D, lightN) >= 0.0f) {
return false;
}
float3 P;
return ray_disk_intersect(ray->P, ray->D, ray->tmin, ray->tmax, lightP, lightN, radius, &P, t);
}
ccl_device_inline bool spot_light_sample_from_intersection(
const ccl_global KernelLight *klight,
ccl_private const Intersection *ccl_restrict isect,
const float3 ray_P,
const float3 ray_D,
ccl_private LightSample *ccl_restrict ls)
{
/* the normal of the oriented disk */
const float3 lightN = normalize(ray_P - klight->co);
/* We set the light normal to the outgoing direction to support texturing. */
ls->Ng = -ls->D;
float invarea = klight->spot.invarea;
ls->eval_fac = (0.25f * M_1_PI_F) * invarea;
ls->pdf = invarea;
/* spot light attenuation */
ls->eval_fac *= spot_light_attenuation(
klight->spot.dir, klight->spot.cos_half_spot_angle, klight->spot.spot_smooth, -ls->D);
if (ls->eval_fac == 0.0f) {
return false;
}
float2 uv = map_to_sphere(ls->Ng);
ls->u = uv.x;
ls->v = uv.y;
/* compute pdf */
if (ls->t != FLT_MAX) {
ls->pdf *= lamp_light_pdf(lightN, -ls->D, ls->t);
}
else {
ls->pdf = 0.f;
}
return true;
}
template<bool in_volume_segment>
ccl_device_forceinline bool spot_light_tree_parameters(const ccl_global KernelLight *klight,
const float3 centroid,
const float3 P,
ccl_private float &cos_theta_u,
ccl_private float2 &distance,
ccl_private float3 &point_to_centroid)
{
float min_distance;
const float3 point_to_centroid_ = safe_normalize_len(centroid - P, &min_distance);
const float radius = klight->spot.radius;
const float hypotenus = sqrtf(sqr(radius) + sqr(min_distance));
cos_theta_u = min_distance / hypotenus;
if (in_volume_segment) {
return true;
}
distance = make_float2(hypotenus, min_distance);
point_to_centroid = point_to_centroid_;
return true;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,691 @@
/* SPDX-License-Identifier: Apache-2.0
* Copyright 2011-2022 Blender Foundation */
#pragma once
#include "kernel/light/area.h"
#include "kernel/light/common.h"
#include "kernel/light/light.h"
#include "kernel/light/spot.h"
#include "kernel/light/triangle.h"
CCL_NAMESPACE_BEGIN
/* TODO: this seems like a relative expensive computation, and we can make it a lot cheaper
* by using a bounding sphere instead of a bounding box. This will be more inaccurate, but it
* might be fine when used along with the adaptive splitting. */
ccl_device float light_tree_cos_bounding_box_angle(const BoundingBox bbox,
const float3 P,
const float3 point_to_centroid)
{
if (P.x > bbox.min.x && P.y > bbox.min.y && P.z > bbox.min.z && P.x < bbox.max.x &&
P.y < bbox.max.y && P.z < bbox.max.z) {
/* If P is inside the bbox, `theta_u` covers the whole sphere */
return -1.0f;
}
float cos_theta_u = 1.0f;
/* Iterate through all 8 possible points of the bounding box. */
for (int i = 0; i < 8; ++i) {
const float3 corner = make_float3((i & 1) ? bbox.max.x : bbox.min.x,
(i & 2) ? bbox.max.y : bbox.min.y,
(i & 4) ? bbox.max.z : bbox.min.z);
/* Caculate the bounding box angle. */
float3 point_to_corner = normalize(corner - P);
cos_theta_u = fminf(cos_theta_u, dot(point_to_centroid, point_to_corner));
}
return cos_theta_u;
}
ccl_device_forceinline float sin_from_cos(const float c)
{
return safe_sqrtf(1.0f - sqr(c));
}
/* Compute vector v as in Fig .8. P_v is the corresponding point along the ray ccl_device float3 */
ccl_device float3 compute_v(
const float3 centroid, const float3 P, const float3 D, const float3 bcone_axis, const float t)
{
const float3 unnormalized_v0 = P - centroid;
float len_v0;
const float3 unnormalized_v1 = unnormalized_v0 + D * fminf(t, 1e12f);
const float3 v0 = normalize_len(unnormalized_v0, &len_v0);
const float3 v1 = normalize(unnormalized_v1);
const float3 o0 = v0;
float3 o1, o2;
make_orthonormals_tangent(o0, v1, &o1, &o2);
const float dot_o0_a = dot(o0, bcone_axis);
const float dot_o1_a = dot(o1, bcone_axis);
const float cos_phi0 = dot_o0_a / sqrtf(sqr(dot_o0_a) + sqr(dot_o1_a));
return (dot_o1_a < 0 || dot(v0, v1) > cos_phi0) ? (dot_o0_a > dot(v1, bcone_axis) ? v0 : v1) :
cos_phi0 * o0 + sin_from_cos(cos_phi0) * o1;
}
/* This is the general function for calculating the importance of either a cluster or an emitter.
* Both of the specialized functions obtain the necessary data before calling this function. */
template<bool in_volume_segment>
ccl_device void light_tree_importance(const float3 N_or_D,
const bool has_transmission,
const float3 point_to_centroid,
const float cos_theta_u,
const BoundingCone bcone,
const float max_distance,
const float min_distance,
const float t,
const float energy,
ccl_private float &max_importance,
ccl_private float &min_importance)
{
max_importance = 0.0f;
min_importance = 0.0f;
const float sin_theta_u = sin_from_cos(cos_theta_u);
/* cos(theta_i') in the paper, omitted for volume */
float cos_min_incidence_angle = 1.0f;
float cos_max_incidence_angle = 1.0f;
/* when sampling the light tree for the second time in `shade_volume.h` and when query the pdf in
* `sample.h` */
const bool in_volume = is_zero(N_or_D);
if (!in_volume_segment && !in_volume) {
const float3 N = N_or_D;
const float cos_theta_i = has_transmission ? fabsf(dot(point_to_centroid, N)) :
dot(point_to_centroid, N);
const float sin_theta_i = sin_from_cos(cos_theta_i);
/* cos_min_incidence_angle = cos(max{theta_i - theta_u, 0}) = cos(theta_i') in the paper */
cos_min_incidence_angle = cos_theta_i >= cos_theta_u ?
1.0f :
cos_theta_i * cos_theta_u + sin_theta_i * sin_theta_u;
/* If the node is guaranteed to be behind the surface we're sampling, and the surface is
* opaque, then we can give the node an importance of 0 as it contributes nothing to the
* surface. This is more accurate than the bbox test if we are calculating the importance of
* an emitter with radius */
if (!has_transmission && cos_min_incidence_angle < 0) {
return;
}
/* cos_max_incidence_angle = cos(min{theta_i + theta_u, pi}) */
cos_max_incidence_angle = fmaxf(cos_theta_i * cos_theta_u - sin_theta_i * sin_theta_u, 0.0f);
}
/* cos(theta - theta_u) */
const float cos_theta = dot(bcone.axis, -point_to_centroid);
const float sin_theta = sin_from_cos(cos_theta);
const float cos_theta_minus_theta_u = cos_theta * cos_theta_u + sin_theta * sin_theta_u;
float cos_theta_o, sin_theta_o;
fast_sincosf(bcone.theta_o, &sin_theta_o, &cos_theta_o);
/* minimum angle an emitters axis would form with the direction to the shading point,
* cos(theta') in the paper */
float cos_min_outgoing_angle;
if ((cos_theta >= cos_theta_u) || (cos_theta_minus_theta_u >= cos_theta_o)) {
/* theta - theta_o - theta_u <= 0 */
kernel_assert((fast_acosf(cos_theta) - bcone.theta_o - fast_acosf(cos_theta_u)) < 5e-4f);
cos_min_outgoing_angle = 1.0f;
}
else if ((bcone.theta_o + bcone.theta_e > M_PI_F) ||
(cos_theta_minus_theta_u > cos(bcone.theta_o + bcone.theta_e))) {
/* theta' = theta - theta_o - theta_u < theta_e */
kernel_assert(
(fast_acosf(cos_theta) - bcone.theta_o - fast_acosf(cos_theta_u) - bcone.theta_e) < 5e-4f);
const float sin_theta_minus_theta_u = sin_from_cos(cos_theta_minus_theta_u);
cos_min_outgoing_angle = cos_theta_minus_theta_u * cos_theta_o +
sin_theta_minus_theta_u * sin_theta_o;
}
else {
/* cluster invisible */
return;
}
/* TODO: find a good approximation for f_a. */
const float f_a = 1.0f;
/* TODO: also consider t (or theta_a, theta_b) for volume */
max_importance = fabsf(f_a * cos_min_incidence_angle * energy * cos_min_outgoing_angle /
(in_volume_segment ? min_distance : sqr(min_distance)));
/* TODO: also min importance for volume? */
if (in_volume_segment) {
min_importance = max_importance;
return;
}
/* cos(theta + theta_o + theta_u) if theta + theta_o + theta_u < theta_e, 0 otherwise */
float cos_max_outgoing_angle;
const float cos_theta_plus_theta_u = cos_theta * cos_theta_u - sin_theta * sin_theta_u;
if (bcone.theta_e - bcone.theta_o < 0 || cos_theta < 0 || cos_theta_u < 0 ||
cos_theta_plus_theta_u < cos(bcone.theta_e - bcone.theta_o)) {
min_importance = 0.0f;
}
else {
const float sin_theta_plus_theta_u = sin_from_cos(cos_theta_plus_theta_u);
cos_max_outgoing_angle = cos_theta_plus_theta_u * cos_theta_o -
sin_theta_plus_theta_u * sin_theta_o;
min_importance = fabsf(f_a * cos_max_incidence_angle * energy * cos_max_outgoing_angle /
sqr(max_distance));
}
}
template<bool in_volume_segment>
ccl_device bool compute_emitter_centroid_and_dir(KernelGlobals kg,
ccl_global const KernelLightTreeEmitter *kemitter,
const float3 P,
ccl_private float3 &centroid,
ccl_private packed_float3 &dir)
{
const int prim_id = kemitter->prim_id;
if (prim_id < 0) {
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, ~prim_id);
centroid = klight->co;
switch (klight->type) {
case LIGHT_SPOT:
dir = klight->spot.dir;
break;
case LIGHT_POINT:
/* Disk-oriented normal */
dir = safe_normalize(P - centroid);
break;
case LIGHT_AREA:
dir = klight->area.dir;
break;
case LIGHT_BACKGROUND:
/* Aarbitrary centroid and direction */
centroid = make_float3(0.0f, 0.0f, 1.0f);
dir = make_float3(0.0f, 0.0f, -1.0f);
return !in_volume_segment;
case LIGHT_DISTANT:
dir = centroid;
return !in_volume_segment;
default:
return false;
}
}
else {
const int object = kemitter->mesh_light.object_id;
float3 vertices[3];
triangle_world_space_vertices(kg, object, prim_id, -1.0f, vertices);
centroid = (vertices[0] + vertices[1] + vertices[2]) / 3.0f;
if (kemitter->mesh_light.emission_sampling == EMISSION_SAMPLING_FRONT) {
dir = safe_normalize(cross(vertices[1] - vertices[0], vertices[2] - vertices[0]));
}
else if (kemitter->mesh_light.emission_sampling == EMISSION_SAMPLING_BACK) {
dir = -safe_normalize(cross(vertices[1] - vertices[0], vertices[2] - vertices[0]));
}
else {
/* Double sided: any vector in the plane. */
dir = safe_normalize(vertices[0] - vertices[1]);
}
}
return true;
}
template<bool in_volume_segment>
ccl_device void light_tree_emitter_importance(KernelGlobals kg,
const float3 P,
const float3 N_or_D,
const float t,
const bool has_transmission,
int emitter_index,
ccl_private float &max_importance,
ccl_private float &min_importance)
{
const ccl_global KernelLightTreeEmitter *kemitter = &kernel_data_fetch(light_tree_emitters,
emitter_index);
max_importance = 0.0f;
min_importance = 0.0f;
BoundingCone bcone;
bcone.theta_o = kemitter->theta_o;
bcone.theta_e = kemitter->theta_e;
float cos_theta_u;
float2 distance; /* distance.x = max_distance, distance.y = mix_distance */
float3 centroid, point_to_centroid, P_c;
if (!compute_emitter_centroid_and_dir<in_volume_segment>(
kg, kemitter, P, centroid, bcone.axis)) {
return;
}
const int prim_id = kemitter->prim_id;
if (in_volume_segment) {
const float3 D = N_or_D;
/* Closest point */
P_c = P + dot(centroid - P, D) * D;
/* minimal distance of the ray to the cluster */
distance.x = len(centroid - P_c);
distance.y = distance.x;
point_to_centroid = -compute_v(centroid, P, D, bcone.axis, t);
}
else {
P_c = P;
}
bool is_visible;
if (prim_id < 0) {
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, ~prim_id);
switch (klight->type) {
/* Function templates only modifies cos_theta_u when in_volume_segment = true */
case LIGHT_SPOT:
is_visible = spot_light_tree_parameters<in_volume_segment>(
klight, centroid, P_c, cos_theta_u, distance, point_to_centroid);
break;
case LIGHT_POINT:
is_visible = point_light_tree_parameters<in_volume_segment>(
klight, centroid, P_c, cos_theta_u, distance, point_to_centroid);
bcone.theta_o = 0.0f;
break;
case LIGHT_AREA:
is_visible = area_light_tree_parameters<in_volume_segment>(
klight, centroid, P_c, N_or_D, bcone.axis, cos_theta_u, distance, point_to_centroid);
break;
case LIGHT_BACKGROUND:
is_visible = background_light_tree_parameters(
centroid, cos_theta_u, distance, point_to_centroid);
break;
case LIGHT_DISTANT:
is_visible = distant_light_tree_parameters(
centroid, bcone.theta_e, cos_theta_u, distance, point_to_centroid);
break;
default:
return;
}
}
else { /* mesh light */
is_visible = triangle_light_tree_parameters<in_volume_segment>(
kg, kemitter, centroid, P_c, N_or_D, bcone, cos_theta_u, distance, point_to_centroid);
}
is_visible |= has_transmission;
if (!is_visible) {
return;
}
light_tree_importance<in_volume_segment>(N_or_D,
has_transmission,
point_to_centroid,
cos_theta_u,
bcone,
distance.x,
distance.y,
t,
kemitter->energy,
max_importance,
min_importance);
}
template<bool in_volume_segment>
ccl_device void light_tree_node_importance(KernelGlobals kg,
const float3 P,
const float3 N_or_D,
const float t,
const bool has_transmission,
const ccl_global KernelLightTreeNode *knode,
ccl_private float &max_importance,
ccl_private float &min_importance)
{
max_importance = 0.0f;
min_importance = 0.0f;
if (knode->num_prims == 1) {
/* At a leaf node with only one emitter */
light_tree_emitter_importance<in_volume_segment>(
kg, P, N_or_D, t, has_transmission, -knode->child_index, max_importance, min_importance);
}
else if (knode->num_prims != 0) {
const BoundingCone bcone = knode->bcone;
const BoundingBox bbox = knode->bbox;
float3 point_to_centroid;
float cos_theta_u;
float distance;
if (knode->bit_trail == 1) {
/* distant light node */
if (in_volume_segment) {
return;
}
point_to_centroid = -bcone.axis;
cos_theta_u = fast_cosf(bcone.theta_o);
distance = 1.0f;
}
else {
const float3 centroid = 0.5f * (bbox.min + bbox.max);
if (in_volume_segment) {
const float3 D = N_or_D;
const float3 closest_point = P + dot(centroid - P, D) * D;
/* minimal distance of the ray to the cluster */
distance = len(centroid - closest_point);
point_to_centroid = -compute_v(centroid, P, D, bcone.axis, t);
cos_theta_u = light_tree_cos_bounding_box_angle(bbox, closest_point, point_to_centroid);
}
else {
const float3 N = N_or_D;
const float3 bbox_extent = bbox.max - centroid;
const bool bbox_is_visible = has_transmission |
(dot(N, centroid - P) + dot(fabs(N), fabs(bbox_extent)) > 0);
/* If the node is guaranteed to be behind the surface we're sampling, and the surface is
* opaque, then we can give the node an importance of 0 as it contributes nothing to the
* surface. */
if (!bbox_is_visible) {
return;
}
point_to_centroid = normalize_len(centroid - P, &distance);
cos_theta_u = light_tree_cos_bounding_box_angle(bbox, P, point_to_centroid);
}
/* clamp distance to half the radius of the cluster when splitting is disabled */
distance = fmaxf(0.5f * len(centroid - bbox.max), distance);
}
/* TODO: currently max_distance = min_distance, max_importance = min_importance for the
* nodes. Do we need better weights for complex scenes? */
light_tree_importance<in_volume_segment>(N_or_D,
has_transmission,
point_to_centroid,
cos_theta_u,
bcone,
distance,
distance,
t,
knode->energy,
max_importance,
min_importance);
}
}
ccl_device void sample_resevoir(const int current_index,
const float current_weight,
ccl_private int &selected_index,
ccl_private float &selected_weight,
ccl_private float &total_weight,
ccl_private float &rand)
{
if (current_weight == 0.0f) {
return;
}
total_weight += current_weight;
float thresh = current_weight / total_weight;
if (rand <= thresh) {
selected_index = current_index;
selected_weight = current_weight;
rand = rand / thresh;
}
else {
rand = (rand - thresh) / (1.0f - thresh);
}
kernel_assert(rand >= 0.0f && rand <= 1.0f);
return;
}
/* pick an emitter from a leaf node using resevoir sampling, keep two reservoirs for upper and
* lower bounds */
template<bool in_volume_segment>
ccl_device int light_tree_cluster_select_emitter(KernelGlobals kg,
ccl_private float &rand,
const float3 P,
const float3 N_or_D,
const float t,
const bool has_transmission,
const ccl_global KernelLightTreeNode *knode,
ccl_private float *pdf_factor)
{
float selected_importance[2] = {0.0f, 0.0f};
float total_importance[2] = {0.0f, 0.0f};
int selected_index = -1;
/* Mark emitters with zero importance. Used for resevoir when total minimum importance = 0 */
kernel_assert(knode->num_prims <= sizeof(uint) * 8);
uint has_importance = 0;
const bool sample_max = (rand > 0.5f); /* sampling using the maximum importance */
rand = rand * 2.0f - float(sample_max);
for (int i = 0; i < knode->num_prims; i++) {
int current_index = -knode->child_index + i;
/* maximum importance = importance[0], mininum importance = importance[1] */
float importance[2];
light_tree_emitter_importance<in_volume_segment>(
kg, P, N_or_D, t, has_transmission, current_index, importance[0], importance[1]);
sample_resevoir(current_index,
importance[!sample_max],
selected_index,
selected_importance[!sample_max],
total_importance[!sample_max],
rand);
if (selected_index == current_index) {
selected_importance[sample_max] = importance[sample_max];
}
total_importance[sample_max] += importance[sample_max];
has_importance |= ((importance[0] > 0) << i);
}
if (total_importance[0] == 0.0f) {
return -1;
}
if (total_importance[1] == 0.0f) {
/* uniformly sample emitters with positive maximum importance */
if (sample_max) {
selected_importance[1] = 1.0f;
total_importance[1] = float(popcount(has_importance));
}
else {
selected_index = -1;
for (int i = 0; i < knode->num_prims; i++) {
int current_index = -knode->child_index + i;
sample_resevoir(current_index,
float(has_importance & 1),
selected_index,
selected_importance[1],
total_importance[1],
rand);
has_importance >>= 1;
}
float discard;
light_tree_emitter_importance<in_volume_segment>(
kg, P, N_or_D, t, has_transmission, selected_index, selected_importance[0], discard);
}
}
*pdf_factor = 0.5f * (selected_importance[0] / total_importance[0] +
selected_importance[1] / total_importance[1]);
return selected_index;
}
template<bool in_volume_segment>
ccl_device bool get_left_probability(KernelGlobals kg,
const float3 P,
const float3 N_or_D,
const float t,
const bool has_transmission,
const int left_index,
const int right_index,
ccl_private float &left_probability)
{
const ccl_global KernelLightTreeNode *left = &kernel_data_fetch(light_tree_nodes, left_index);
const ccl_global KernelLightTreeNode *right = &kernel_data_fetch(light_tree_nodes, right_index);
float min_left_importance, max_left_importance, min_right_importance, max_right_importance;
light_tree_node_importance<in_volume_segment>(
kg, P, N_or_D, t, has_transmission, left, max_left_importance, min_left_importance);
light_tree_node_importance<in_volume_segment>(
kg, P, N_or_D, t, has_transmission, right, max_right_importance, min_right_importance);
const float total_max_importance = max_left_importance + max_right_importance;
if (total_max_importance == 0.0f) {
return false;
}
const float total_min_importance = min_left_importance + min_right_importance;
/* average two probabilities of picking the left child node using lower and upper bounds */
const float probability_max = max_left_importance / total_max_importance;
const float probability_min = total_min_importance > 0 ?
min_left_importance / total_min_importance :
0.5f * (float(max_left_importance > 0) +
float(max_right_importance == 0.0f));
left_probability = 0.5f * (probability_max + probability_min);
return true;
}
template<bool in_volume_segment>
ccl_device_noinline bool light_tree_sample(KernelGlobals kg,
ccl_private float &randu,
ccl_private float &randv,
const float time,
const float3 P,
const float3 N_or_D,
const float t,
const int shader_flags,
const int bounce,
const uint32_t path_flag,
ccl_private int &emitter_object,
ccl_private int &emitter_prim,
ccl_private int &emitter_shader_flag,
ccl_private float &emitter_pdf_selection)
{
if (!kernel_data.integrator.use_direct_light) {
return false;
}
const bool has_transmission = (shader_flags & SD_BSDF_HAS_TRANSMISSION);
float pdf_leaf = 1.0f;
float pdf_emitter_from_leaf = 1.0f;
int selected_light = -1;
int node_index = 0; /* root node */
/* Traverse the light tree until a leaf node is reached. */
while (true) {
const ccl_global KernelLightTreeNode *knode = &kernel_data_fetch(light_tree_nodes, node_index);
if (knode->child_index <= 0) {
/* At a leaf node, we pick an emitter */
selected_light = light_tree_cluster_select_emitter<in_volume_segment>(
kg, randv, P, N_or_D, t, has_transmission, knode, &pdf_emitter_from_leaf);
break;
}
/* At an interior node, the left child is directly after the parent,
* while the right child is stored as the child index. */
const int left_index = node_index + 1;
const int right_index = knode->child_index;
float left_prob;
if (!get_left_probability<in_volume_segment>(
kg, P, N_or_D, t, has_transmission, left_index, right_index, left_prob)) {
return false; /* both child nodes have zero importance */
}
float discard;
float total_prob = left_prob;
node_index = left_index;
sample_resevoir(right_index, 1.0f - left_prob, node_index, discard, total_prob, randu);
pdf_leaf *= (node_index == left_index) ? left_prob : (1.0f - left_prob);
}
if (selected_light < 0) {
return false;
}
/* Return info about chosen emitter. */
ccl_global const KernelLightTreeEmitter *kemitter = &kernel_data_fetch(light_tree_emitters,
selected_light);
emitter_object = kemitter->mesh_light.object_id;
emitter_prim = kemitter->prim_id;
emitter_shader_flag = kemitter->mesh_light.shader_flag;
emitter_pdf_selection = pdf_leaf * pdf_emitter_from_leaf;
return true;
}
/* We need to be able to find the probability of selecting a given light for MIS. */
ccl_device float light_tree_pdf(
KernelGlobals kg, const float3 P, const float3 N, const int path_flag, const int prim)
{
const bool has_transmission = (path_flag & PATH_RAY_MIS_HAD_TRANSMISSION);
/* Target emitter info */
const int target_emitter = (prim >= 0) ? kernel_data_fetch(triangle_to_tree, prim) :
kernel_data_fetch(light_to_tree, ~prim);
ccl_global const KernelLightTreeEmitter *kemitter = &kernel_data_fetch(light_tree_emitters,
target_emitter);
const int target_leaf = kemitter->parent_index;
ccl_global const KernelLightTreeNode *kleaf = &kernel_data_fetch(light_tree_nodes, target_leaf);
uint bit_trail = kleaf->bit_trail;
int node_index = 0; /* root node */
float pdf = 1.0f;
/* Traverse the light tree until we reach the target leaf node */
while (true) {
const ccl_global KernelLightTreeNode *knode = &kernel_data_fetch(light_tree_nodes, node_index);
if (knode->child_index <= 0) {
break;
}
/* Interior node */
const int left_index = node_index + 1;
const int right_index = knode->child_index;
float left_prob;
if (!get_left_probability<false>(
kg, P, N, 0, has_transmission, left_index, right_index, left_prob)) {
return 0.0f;
}
const bool go_left = (bit_trail & 1) == 0;
bit_trail >>= 1;
pdf *= go_left ? left_prob : (1.0f - left_prob);
node_index = go_left ? left_index : right_index;
if (pdf == 0) {
return 0.0f;
}
}
kernel_assert(node_index == target_leaf);
/* Iterate through leaf node to find the probability of sampling the target emitter. */
float target_max_importance = 0.0f;
float target_min_importance = 0.0f;
float total_max_importance = 0.0f;
float total_min_importance = 0.0f;
int num_has_importance = 0;
for (int i = 0; i < kleaf->num_prims; i++) {
const int emitter = -kleaf->child_index + i;
float max_importance, min_importance;
light_tree_emitter_importance<false>(
kg, P, N, 0, has_transmission, emitter, max_importance, min_importance);
num_has_importance += (max_importance > 0);
if (emitter == target_emitter) {
target_max_importance = max_importance;
target_min_importance = min_importance;
}
total_max_importance += max_importance;
total_min_importance += min_importance;
}
if (target_max_importance > 0.0f) {
return pdf * 0.5f *
(target_max_importance / total_max_importance +
(total_min_importance > 0 ? target_min_importance / total_min_importance :
1.0f / num_has_importance));
}
return 0.0f;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,329 @@
/* SPDX-License-Identifier: Apache-2.0
* Copyright 2011-2022 Blender Foundation */
#pragma once
#include "kernel/geom/geom.h"
CCL_NAMESPACE_BEGIN
/* returns true if the triangle is has motion blur or an instancing transform applied */
ccl_device_inline bool triangle_world_space_vertices(
KernelGlobals kg, int object, int prim, float time, float3 V[3])
{
bool has_motion = false;
const int object_flag = kernel_data_fetch(object_flag, object);
if (object_flag & SD_OBJECT_HAS_VERTEX_MOTION && time >= 0.0f) {
motion_triangle_vertices(kg, object, prim, time, V);
has_motion = true;
}
else {
triangle_vertices(kg, prim, V);
}
if (!(object_flag & SD_OBJECT_TRANSFORM_APPLIED)) {
#ifdef __OBJECT_MOTION__
float object_time = (time >= 0.0f) ? time : 0.5f;
Transform tfm = object_fetch_transform_motion_test(kg, object, object_time, NULL);
#else
Transform tfm = object_fetch_transform(kg, object, OBJECT_TRANSFORM);
#endif
V[0] = transform_point(&tfm, V[0]);
V[1] = transform_point(&tfm, V[1]);
V[2] = transform_point(&tfm, V[2]);
has_motion = true;
}
return has_motion;
}
ccl_device_inline float triangle_light_pdf_area_sampling(const float3 Ng, const float3 I, float t)
{
float cos_pi = fabsf(dot(Ng, I));
if (cos_pi == 0.0f)
return 0.0f;
return t * t / cos_pi;
}
ccl_device_forceinline float triangle_light_pdf(KernelGlobals kg,
ccl_private const ShaderData *sd,
float t)
{
/* A naive heuristic to decide between costly solid angle sampling
* and simple area sampling, comparing the distance to the triangle plane
* to the length of the edges of the triangle. */
float3 V[3];
bool has_motion = triangle_world_space_vertices(kg, sd->object, sd->prim, sd->time, V);
const float3 e0 = V[1] - V[0];
const float3 e1 = V[2] - V[0];
const float3 e2 = V[2] - V[1];
const float longest_edge_squared = max(len_squared(e0), max(len_squared(e1), len_squared(e2)));
const float3 N = cross(e0, e1);
const float distance_to_plane = fabsf(dot(N, sd->I * t)) / dot(N, N);
const float area = 0.5f * len(N);
float pdf;
if (longest_edge_squared > distance_to_plane * distance_to_plane) {
/* sd contains the point on the light source
* calculate Px, the point that we're shading */
const float3 Px = sd->P + sd->I * t;
const float3 v0_p = V[0] - Px;
const float3 v1_p = V[1] - Px;
const float3 v2_p = V[2] - Px;
const float3 u01 = safe_normalize(cross(v0_p, v1_p));
const float3 u02 = safe_normalize(cross(v0_p, v2_p));
const float3 u12 = safe_normalize(cross(v1_p, v2_p));
const float alpha = fast_acosf(dot(u02, u01));
const float beta = fast_acosf(-dot(u01, u12));
const float gamma = fast_acosf(dot(u02, u12));
const float solid_angle = alpha + beta + gamma - M_PI_F;
/* distribution_pdf_triangles is calculated over triangle area, but we're not sampling over
* its area */
if (UNLIKELY(solid_angle == 0.0f)) {
return 0.0f;
}
else {
pdf = 1.0f / solid_angle;
}
}
else {
if (UNLIKELY(area == 0.0f)) {
return 0.0f;
}
pdf = triangle_light_pdf_area_sampling(sd->Ng, sd->I, t) / area;
}
/* Belongs in distribution.h but can reuse computations here. */
if (!kernel_data.integrator.use_light_tree) {
float distribution_area = area;
if (has_motion && area != 0.0f) {
/* For motion blur need area of triangle at fixed time as used in the CDF. */
triangle_world_space_vertices(kg, sd->object, sd->prim, -1.0f, V);
distribution_area = triangle_area(V[0], V[1], V[2]);
}
pdf *= distribution_area * kernel_data.integrator.distribution_pdf_triangles;
}
return pdf;
}
template<bool in_volume_segment>
ccl_device_forceinline bool triangle_light_sample(KernelGlobals kg,
int prim,
int object,
float randu,
float randv,
float time,
ccl_private LightSample *ls,
const float3 P)
{
/* A naive heuristic to decide between costly solid angle sampling
* and simple area sampling, comparing the distance to the triangle plane
* to the length of the edges of the triangle. */
float3 V[3];
bool has_motion = triangle_world_space_vertices(kg, object, prim, time, V);
const float3 e0 = V[1] - V[0];
const float3 e1 = V[2] - V[0];
const float3 e2 = V[2] - V[1];
const float longest_edge_squared = max(len_squared(e0), max(len_squared(e1), len_squared(e2)));
const float3 N0 = cross(e0, e1);
float Nl = 0.0f;
ls->Ng = safe_normalize_len(N0, &Nl);
const float area = 0.5f * Nl;
/* flip normal if necessary */
const int object_flag = kernel_data_fetch(object_flag, object);
if (object_flag & SD_OBJECT_NEGATIVE_SCALE_APPLIED) {
ls->Ng = -ls->Ng;
}
ls->eval_fac = 1.0f;
ls->shader = kernel_data_fetch(tri_shader, prim);
ls->object = object;
ls->prim = prim;
ls->lamp = LAMP_NONE;
ls->shader |= SHADER_USE_MIS;
ls->type = LIGHT_TRIANGLE;
ls->group = object_lightgroup(kg, object);
float distance_to_plane = fabsf(dot(N0, V[0] - P) / dot(N0, N0));
if (!in_volume_segment && (longest_edge_squared > distance_to_plane * distance_to_plane)) {
/* see James Arvo, "Stratified Sampling of Spherical Triangles"
* http://www.graphics.cornell.edu/pubs/1995/Arv95c.pdf */
/* project the triangle to the unit sphere
* and calculate its edges and angles */
const float3 v0_p = V[0] - P;
const float3 v1_p = V[1] - P;
const float3 v2_p = V[2] - P;
const float3 u01 = safe_normalize(cross(v0_p, v1_p));
const float3 u02 = safe_normalize(cross(v0_p, v2_p));
const float3 u12 = safe_normalize(cross(v1_p, v2_p));
const float3 A = safe_normalize(v0_p);
const float3 B = safe_normalize(v1_p);
const float3 C = safe_normalize(v2_p);
const float cos_alpha = dot(u02, u01);
const float cos_beta = -dot(u01, u12);
const float cos_gamma = dot(u02, u12);
/* calculate dihedral angles */
const float alpha = fast_acosf(cos_alpha);
const float beta = fast_acosf(cos_beta);
const float gamma = fast_acosf(cos_gamma);
/* the area of the unit spherical triangle = solid angle */
const float solid_angle = alpha + beta + gamma - M_PI_F;
/* precompute a few things
* these could be re-used to take several samples
* as they are independent of randu/randv */
const float cos_c = dot(A, B);
const float sin_alpha = fast_sinf(alpha);
const float product = sin_alpha * cos_c;
/* Select a random sub-area of the spherical triangle
* and calculate the third vertex C_ of that new triangle */
const float phi = randu * solid_angle - alpha;
float s, t;
fast_sincosf(phi, &s, &t);
const float u = t - cos_alpha;
const float v = s + product;
const float3 U = safe_normalize(C - dot(C, A) * A);
float q = 1.0f;
const float det = ((v * s + u * t) * sin_alpha);
if (det != 0.0f) {
q = ((v * t - u * s) * cos_alpha - v) / det;
}
const float temp = max(1.0f - q * q, 0.0f);
const float3 C_ = safe_normalize(q * A + sqrtf(temp) * U);
/* Finally, select a random point along the edge of the new triangle
* That point on the spherical triangle is the sampled ray direction */
const float z = 1.0f - randv * (1.0f - dot(C_, B));
ls->D = z * B + safe_sqrtf(1.0f - z * z) * safe_normalize(C_ - dot(C_, B) * B);
/* calculate intersection with the planar triangle */
if (!ray_triangle_intersect(
P, ls->D, 0.0f, FLT_MAX, V[0], V[1], V[2], &ls->u, &ls->v, &ls->t)) {
ls->pdf = 0.0f;
return false;
}
ls->P = P + ls->D * ls->t;
/* distribution_pdf_triangles is calculated over triangle area, but we're sampling over solid
* angle */
if (UNLIKELY(solid_angle == 0.0f)) {
ls->pdf = 0.0f;
return false;
}
else {
ls->pdf = 1.0f / solid_angle;
}
}
else {
if (UNLIKELY(area == 0.0f)) {
return 0.0f;
}
/* compute random point in triangle. From Eric Heitz's "A Low-Distortion Map Between Triangle
* and Square" */
float u = randu;
float v = randv;
if (v > u) {
u *= 0.5f;
v -= u;
}
else {
v *= 0.5f;
u -= v;
}
const float t = 1.0f - u - v;
ls->P = u * V[0] + v * V[1] + t * V[2];
/* compute incoming direction, distance and pdf */
ls->D = normalize_len(ls->P - P, &ls->t);
ls->pdf = triangle_light_pdf_area_sampling(ls->Ng, -ls->D, ls->t) / area;
ls->u = u;
ls->v = v;
}
/* Belongs in distribution.h but can reuse computations here. */
if (!kernel_data.integrator.use_light_tree) {
float distribution_area = area;
if (has_motion && area != 0.0f) {
/* For motion blur need area of triangle at fixed time as used in the CDF. */
triangle_world_space_vertices(kg, object, prim, -1.0f, V);
distribution_area = triangle_area(V[0], V[1], V[2]);
}
ls->pdf_selection = distribution_area * kernel_data.integrator.distribution_pdf_triangles;
}
return (ls->pdf > 0.0f);
}
template<bool in_volume_segment>
ccl_device_forceinline bool triangle_light_tree_parameters(
KernelGlobals kg,
const ccl_global KernelLightTreeEmitter *kemitter,
const float3 centroid,
const float3 P,
const float3 N,
const BoundingCone bcone,
ccl_private float &cos_theta_u,
ccl_private float2 &distance,
ccl_private float3 &point_to_centroid)
{
if (!in_volume_segment) {
/* TODO: a cheap substitute for minimal distance between point and primitive. Does it
* worth the overhead to compute the accurate minimal distance? */
float min_distance;
point_to_centroid = safe_normalize_len(centroid - P, &min_distance);
distance = make_float2(min_distance, min_distance);
}
cos_theta_u = FLT_MAX;
const int object = kemitter->mesh_light.object_id;
float3 vertices[3];
triangle_world_space_vertices(kg, object, kemitter->prim_id, -1.0f, vertices);
bool shape_above_surface = false;
for (int i = 0; i < 3; i++) {
const float3 corner = vertices[i];
float distance_point_to_corner;
const float3 point_to_corner = safe_normalize_len(corner - P, &distance_point_to_corner);
cos_theta_u = fminf(cos_theta_u, dot(point_to_centroid, point_to_corner));
shape_above_surface |= dot(point_to_corner, N) > 0;
if (!in_volume_segment) {
distance.x = fmaxf(distance.x, distance_point_to_corner);
}
}
const bool front_facing = bcone.theta_o != 0.0f || dot(bcone.axis, point_to_centroid) < 0;
const bool in_volume = is_zero(N);
return (front_facing && shape_above_surface) || in_volume;
}
CCL_NAMESPACE_END

View File

@@ -60,6 +60,7 @@ CCL_NAMESPACE_BEGIN
#define __DENOISING_FEATURES__
#define __DPDU__
#define __HAIR__
#define __LIGHT_TREE__
#define __OBJECT_MOTION__
#define __PASSES__
#define __PATCH_EVAL__
@@ -74,6 +75,11 @@ CCL_NAMESPACE_BEGIN
#define __VISIBILITY_FLAG__
#define __VOLUME__
/* TODO: solve internal compiler errors and enable light tree on HIP. */
#ifdef __KERNEL_HIP__
# undef __LIGHT_TREE__
#endif
/* Device specific features */
#ifdef WITH_OSL
# define __OSL__
@@ -160,7 +166,8 @@ enum PathTraceDimension {
PRNG_VOLUME_SCATTER_DISTANCE = 5,
PRNG_VOLUME_OFFSET = 6,
PRNG_VOLUME_SHADE_OFFSET = 7,
PRNG_VOLUME_PHASE_GUIDING = 8,
PRNG_VOLUME_PHASE_GUIDING_DISTANCE = 8,
PRNG_VOLUME_PHASE_GUIDING_EQUIANGULAR = 9,
/* Subsurface random walk bounces */
PRNG_SUBSURFACE_BSDF = 0,
@@ -208,21 +215,26 @@ enum PathRayFlag : uint32_t {
PATH_RAY_SHADOW_TRANSPARENT = (1U << 9U),
PATH_RAY_SHADOW = (PATH_RAY_SHADOW_OPAQUE | PATH_RAY_SHADOW_TRANSPARENT),
/* Special flag to tag unaligned BVH nodes.
* Only set and used in BVH nodes to distinguish how to interpret bounding box information stored
* in the node (either it should be intersected as AABB or as OBBU). */
PATH_RAY_NODE_UNALIGNED = (1U << 10U),
/* Subset of flags used for ray visibility for intersection.
*
* NOTE: SHADOW_CATCHER macros below assume there are no more than
* 16 visibility bits. */
PATH_RAY_ALL_VISIBILITY = ((1U << 11U) - 1U),
PATH_RAY_ALL_VISIBILITY = ((1U << 10U) - 1U),
/* Special flag to tag unaligned BVH nodes.
* Only set and used in BVH nodes to distinguish how to interpret bounding box information stored
* in the node (either it should be intersected as AABB or as OBBU).
* So this can overlap with path flags. */
PATH_RAY_NODE_UNALIGNED = (1U << 10U),
/* --------------------------------------------------------------------
* Path flags.
*/
/* Surface had transmission component at previous bounce. Used for light tree
* traversal and culling to be consistent with MIS PDF at the next bounce. */
PATH_RAY_MIS_HAD_TRANSMISSION = (1U << 10U),
/* Don't apply multiple importance sampling weights to emission from
* lamp or surface hits, because they were not direct light sampled. */
PATH_RAY_MIS_SKIP = (1U << 11U),
@@ -342,7 +354,6 @@ typedef enum PassType {
PASS_EMISSION,
PASS_BACKGROUND,
PASS_AO,
PASS_SHADOW,
PASS_DIFFUSE,
PASS_DIFFUSE_DIRECT,
PASS_DIFFUSE_INDIRECT,
@@ -461,6 +472,16 @@ typedef enum ShaderFlag {
SHADER_EXCLUDE_ANY)
} ShaderFlag;
enum EmissionSampling {
EMISSION_SAMPLING_NONE = 0,
EMISSION_SAMPLING_AUTO = 1,
EMISSION_SAMPLING_FRONT = 2,
EMISSION_SAMPLING_BACK = 3,
EMISSION_SAMPLING_FRONT_BACK = 4,
EMISSION_SAMPLING_NUM
};
/* Light Type */
typedef enum LightType {
@@ -774,14 +795,16 @@ enum ShaderDataFlag {
SD_TRANSPARENT = (1 << 9),
/* BSDF requires LCG for evaluation. */
SD_BSDF_NEEDS_LCG = (1 << 10),
/* BSDF has a transmissive component. */
SD_BSDF_HAS_TRANSMISSION = (1 << 11),
SD_CLOSURE_FLAGS = (SD_EMISSION | SD_BSDF | SD_BSDF_HAS_EVAL | SD_BSSRDF | SD_HOLDOUT |
SD_EXTINCTION | SD_SCATTER | SD_BSDF_NEEDS_LCG),
SD_EXTINCTION | SD_SCATTER | SD_BSDF_NEEDS_LCG | SD_BSDF_HAS_TRANSMISSION),
/* Shader flags. */
/* direct light sample */
SD_USE_MIS = (1 << 16),
/* Use front side for direct light sampling. */
SD_MIS_FRONT = (1 << 16),
/* Has transparent shadow. */
SD_HAS_TRANSPARENT_SHADOW = (1 << 17),
/* Has volume shader. */
@@ -810,12 +833,14 @@ enum ShaderDataFlag {
SD_HAS_EMISSION = (1 << 29),
/* Shader has raytracing */
SD_HAS_RAYTRACE = (1 << 30),
/* Use back side for direct light sampling. */
SD_MIS_BACK = (1 << 31),
SD_SHADER_FLAGS = (SD_USE_MIS | SD_HAS_TRANSPARENT_SHADOW | SD_HAS_VOLUME | SD_HAS_ONLY_VOLUME |
SD_HETEROGENEOUS_VOLUME | SD_HAS_BSSRDF_BUMP | SD_VOLUME_EQUIANGULAR |
SD_VOLUME_MIS | SD_VOLUME_CUBIC | SD_HAS_BUMP | SD_HAS_DISPLACEMENT |
SD_HAS_CONSTANT_EMISSION | SD_NEED_VOLUME_ATTRIBUTES | SD_HAS_EMISSION |
SD_HAS_RAYTRACE)
SD_SHADER_FLAGS = (SD_MIS_FRONT | SD_HAS_TRANSPARENT_SHADOW | SD_HAS_VOLUME |
SD_HAS_ONLY_VOLUME | SD_HETEROGENEOUS_VOLUME | SD_HAS_BSSRDF_BUMP |
SD_VOLUME_EQUIANGULAR | SD_VOLUME_MIS | SD_VOLUME_CUBIC | SD_HAS_BUMP |
SD_HAS_DISPLACEMENT | SD_HAS_CONSTANT_EMISSION | SD_NEED_VOLUME_ATTRIBUTES |
SD_HAS_EMISSION | SD_HAS_RAYTRACE | SD_MIS_BACK)
};
/* Object flags. */
@@ -1267,21 +1292,24 @@ static_assert_align(KernelCurveSegment, 8);
typedef struct KernelSpotLight {
float radius;
float invarea;
float spot_angle;
float cos_half_spot_angle;
float spot_smooth;
float dir[3];
packed_float3 dir;
float pad;
} KernelSpotLight;
/* PointLight is SpotLight with only radius and invarea being used. */
typedef struct KernelAreaLight {
float axisu[3];
packed_float3 axis_u;
float len_u;
packed_float3 axis_v;
float len_v;
packed_float3 dir;
float invarea;
float axisv[3];
float tan_spread;
float dir[3];
float cot_half_spread;
float normalize_spread;
float pad[2];
} KernelAreaLight;
typedef struct KernelDistantLight {
@@ -1293,7 +1321,7 @@ typedef struct KernelDistantLight {
typedef struct KernelLight {
int type;
float co[3];
packed_float3 co;
int shader_id;
float max_bounces;
float random;
@@ -1313,19 +1341,70 @@ static_assert_align(KernelLight, 16);
typedef struct KernelLightDistribution {
float totarea;
int prim;
union {
struct {
int shader_flag;
int object_id;
} mesh_light;
struct {
float pad;
float size;
} lamp;
};
struct {
int shader_flag;
int object_id;
} mesh_light;
} KernelLightDistribution;
static_assert_align(KernelLightDistribution, 16);
/* Bounding box. */
using BoundingBox = struct BoundingBox {
packed_float3 min;
packed_float3 max;
};
using BoundingCone = struct BoundingCone {
packed_float3 axis;
float theta_o;
float theta_e;
};
typedef struct KernelLightTreeNode {
/* Bounding box. */
BoundingBox bbox;
/* Bounding cone. */
BoundingCone bcone;
/* Energy. */
float energy;
/* If this is 0 or less, we're at a leaf node
* and the negative value indexes into the first child of the light array.
* Otherwise, it's an index to the node's second child. */
int child_index;
int num_prims; /* leaf nodes need to know the number of primitives stored. */
/* Bit trail. */
uint bit_trail;
/* Padding. */
int pad;
} KernelLightTreeNode;
static_assert_align(KernelLightTreeNode, 16);
typedef struct KernelLightTreeEmitter {
/* Bounding cone. */
float theta_o;
float theta_e;
/* Energy. */
float energy;
/* prim_id denotes the location in the lights or triangles array. */
int prim_id;
struct {
int shader_flag;
int object_id;
EmissionSampling emission_sampling;
} mesh_light;
/* Parent. */
int parent_index;
} KernelLightTreeEmitter;
static_assert_align(KernelLightTreeEmitter, 16);
typedef struct KernelParticle {
int index;
float age;
@@ -1525,22 +1604,19 @@ enum KernelFeatureFlag : uint32_t {
/* Light render passes. */
KERNEL_FEATURE_LIGHT_PASSES = (1U << 21U),
/* Shadow render pass. */
KERNEL_FEATURE_SHADOW_PASS = (1U << 22U),
/* AO. */
KERNEL_FEATURE_AO_PASS = (1U << 23U),
KERNEL_FEATURE_AO_ADDITIVE = (1U << 24U),
KERNEL_FEATURE_AO_PASS = (1U << 22U),
KERNEL_FEATURE_AO_ADDITIVE = (1U << 23U),
KERNEL_FEATURE_AO = (KERNEL_FEATURE_AO_PASS | KERNEL_FEATURE_AO_ADDITIVE),
/* MNEE. */
KERNEL_FEATURE_MNEE = (1U << 25U),
KERNEL_FEATURE_MNEE = (1U << 24U),
/* Path guiding. */
KERNEL_FEATURE_PATH_GUIDING = (1U << 26U),
KERNEL_FEATURE_PATH_GUIDING = (1U << 25U),
/* OSL. */
KERNEL_FEATURE_OSL = (1U << 27U),
KERNEL_FEATURE_OSL = (1U << 26U),
};
/* Shader node feature mask, to specialize shader evaluation for kernels. */

View File

@@ -25,6 +25,7 @@ set(SRC
integrator.cpp
jitter.cpp
light.cpp
light_tree.cpp
mesh.cpp
mesh_displace.cpp
mesh_subdivision.cpp
@@ -63,6 +64,7 @@ set(SRC_HEADERS
image_vdb.h
integrator.h
light.h
light_tree.h
jitter.h
mesh.h
object.h

View File

@@ -4,6 +4,7 @@
#include "scene/background.h"
#include "device/device.h"
#include "scene/integrator.h"
#include "scene/light.h"
#include "scene/scene.h"
#include "scene/shader.h"
#include "scene/shader_graph.h"

View File

@@ -187,7 +187,6 @@ void Film::device_update(Device *device, DeviceScene *dscene, Scene *scene)
kfilm->pass_transmission_indirect = PASS_UNUSED;
kfilm->pass_volume_direct = PASS_UNUSED;
kfilm->pass_volume_indirect = PASS_UNUSED;
kfilm->pass_shadow = PASS_UNUSED;
kfilm->pass_lightgroup = PASS_UNUSED;
/* Mark passes as unused so that the kernel knows the pass is inaccessible. */
@@ -295,9 +294,6 @@ void Film::device_update(Device *device, DeviceScene *dscene, Scene *scene)
case PASS_AO:
kfilm->pass_ao = kfilm->pass_stride;
break;
case PASS_SHADOW:
kfilm->pass_shadow = kfilm->pass_stride;
break;
case PASS_DIFFUSE_COLOR:
kfilm->pass_diffuse_color = kfilm->pass_stride;
@@ -727,10 +723,6 @@ uint Film::get_kernel_features(const Scene *scene) const
kernel_features |= KERNEL_FEATURE_LIGHT_PASSES;
}
if (pass_type == PASS_SHADOW) {
kernel_features |= KERNEL_FEATURE_SHADOW_PASS;
}
if (pass_type == PASS_AO) {
kernel_features |= KERNEL_FEATURE_AO_PASS;
}

View File

@@ -271,7 +271,7 @@ void Geometry::tag_update(Scene *scene, bool rebuild)
else {
foreach (Node *node, used_shaders) {
Shader *shader = static_cast<Shader *>(node);
if (shader->has_surface_emission) {
if (shader->emission_sampling != EMISSION_SAMPLING_NONE) {
scene->light_manager->tag_update(scene, LightManager::EMISSIVE_MESH_MODIFIED);
break;
}

View File

@@ -102,7 +102,8 @@ NODE_DEFINE(Integrator)
SOCKET_FLOAT(adaptive_threshold, "Adaptive Threshold", 0.01f);
SOCKET_INT(adaptive_min_samples, "Adaptive Min Samples", 0);
SOCKET_FLOAT(light_sampling_threshold, "Light Sampling Threshold", 0.01f);
SOCKET_BOOLEAN(use_light_tree, "Use light tree to optimize many light sampling", true);
SOCKET_FLOAT(light_sampling_threshold, "Light Sampling Threshold", 0.0f);
static NodeEnum sampling_pattern_enum;
sampling_pattern_enum.insert("sobol_burley", SAMPLING_PATTERN_SOBOL_BURLEY);
@@ -250,6 +251,7 @@ void Integrator::device_update(Device *device, DeviceScene *dscene, Scene *scene
kintegrator->sampling_pattern = sampling_pattern;
kintegrator->scrambling_distance = scrambling_distance;
kintegrator->use_light_tree = scene->integrator->use_light_tree;
if (light_sampling_threshold > 0.0f) {
kintegrator->light_inv_rr_threshold = 1.0f / light_sampling_threshold;
}

View File

@@ -79,6 +79,7 @@ class Integrator : public Node {
NODE_SOCKET_API(int, aa_samples)
NODE_SOCKET_API(int, start_sample)
NODE_SOCKET_API(bool, use_light_tree)
NODE_SOCKET_API(float, light_sampling_threshold)
NODE_SOCKET_API(bool, use_adaptive_sampling)

View File

@@ -110,6 +110,7 @@ NODE_DEFINE(Light)
SOCKET_FLOAT(spread, "Spread", M_PI_F);
SOCKET_INT(map_resolution, "Map Resolution", 0);
SOCKET_FLOAT(average_radiance, "Average Radiance", 0.0f);
SOCKET_FLOAT(spot_angle, "Spot Angle", M_PI_4_F);
SOCKET_FLOAT(spot_smooth, "Spot Smooth", 0.0f);
@@ -162,7 +163,9 @@ bool Light::has_contribution(Scene *scene)
if (light_type == LIGHT_BACKGROUND) {
return true;
}
return (shader) ? shader->has_surface_emission : scene->default_light->has_surface_emission;
const Shader *effective_shader = (shader) ? shader : scene->default_light;
return !is_zero(effective_shader->emission_estimate);
}
/* Light Manager */
@@ -256,7 +259,7 @@ bool LightManager::object_usable_as_light(Object *object)
*/
foreach (Node *node, geom->get_used_shaders()) {
Shader *shader = static_cast<Shader *>(node);
if (shader->get_use_mis() && shader->has_surface_emission) {
if (shader->emission_sampling != EMISSION_SAMPLING_NONE) {
return true;
}
}
@@ -268,25 +271,14 @@ void LightManager::device_update_distribution(Device *,
Scene *scene,
Progress &progress)
{
KernelIntegrator *kintegrator = &dscene->data.integrator;
/* Update CDF over lights. */
progress.set_status("Updating Lights", "Computing distribution");
/* count */
size_t num_lights = 0;
size_t num_portals = 0;
size_t num_background_lights = 0;
/* Counts emissive triangles in the scene. */
size_t num_triangles = 0;
bool background_mis = false;
foreach (Light *light, scene->lights) {
if (light->is_enabled) {
num_lights++;
}
if (light->is_portal) {
num_portals++;
}
}
foreach (Object *object, scene->objects) {
if (progress.get_cancel())
return;
@@ -295,29 +287,40 @@ void LightManager::device_update_distribution(Device *,
continue;
}
/* Count triangles. */
/* Count emissive triangles. */
Mesh *mesh = static_cast<Mesh *>(object->get_geometry());
size_t mesh_num_triangles = mesh->num_triangles();
for (size_t i = 0; i < mesh_num_triangles; i++) {
int shader_index = mesh->get_shader()[i];
Shader *shader = (shader_index < mesh->get_used_shaders().size()) ?
static_cast<Shader *>(mesh->get_used_shaders()[shader_index]) :
scene->default_surface;
if (shader->get_use_mis() && shader->has_surface_emission) {
if (shader->emission_sampling != EMISSION_SAMPLING_NONE) {
num_triangles++;
}
}
}
size_t num_distribution = num_triangles + num_lights;
const size_t num_lights = kintegrator->num_lights;
const size_t num_distribution = num_triangles + num_lights;
/* Distribution size. */
kintegrator->num_distribution = num_distribution;
VLOG_INFO << "Total " << num_distribution << " of light distribution primitives.";
/* emission area */
if (kintegrator->use_light_tree) {
dscene->light_distribution.free();
return;
}
/* Emission area. */
KernelLightDistribution *distribution = dscene->light_distribution.alloc(num_distribution + 1);
float totarea = 0.0f;
/* triangles */
/* Triangles. */
size_t offset = 0;
int j = 0;
@@ -362,7 +365,7 @@ void LightManager::device_update_distribution(Device *,
static_cast<Shader *>(mesh->get_used_shaders()[shader_index]) :
scene->default_surface;
if (shader->get_use_mis() && shader->has_surface_emission) {
if (shader->emission_sampling != EMISSION_SAMPLING_NONE) {
distribution[offset].totarea = totarea;
distribution[offset].prim = i + mesh->prim_offset;
distribution[offset].mesh_light.shader_flag = shader_flag;
@@ -390,9 +393,9 @@ void LightManager::device_update_distribution(Device *,
j++;
}
float trianglearea = totarea;
/* point lights */
bool use_lamp_mis = false;
const float trianglearea = totarea;
/* Lights. */
int light_index = 0;
if (num_lights > 0) {
@@ -403,24 +406,10 @@ void LightManager::device_update_distribution(Device *,
distribution[offset].totarea = totarea;
distribution[offset].prim = ~light_index;
distribution[offset].lamp.pad = 1.0f;
distribution[offset].lamp.size = light->size;
distribution[offset].mesh_light.object_id = OBJECT_NONE;
distribution[offset].mesh_light.shader_flag = 0;
totarea += lightarea;
if (light->light_type == LIGHT_DISTANT) {
use_lamp_mis |= (light->angle > 0.0f && light->use_mis);
}
else if (light->light_type == LIGHT_POINT || light->light_type == LIGHT_SPOT) {
use_lamp_mis |= (light->size > 0.0f && light->use_mis);
}
else if (light->light_type == LIGHT_AREA) {
use_lamp_mis |= light->use_mis;
}
else if (light->light_type == LIGHT_BACKGROUND) {
num_background_lights++;
background_mis |= light->use_mis;
}
light_index++;
offset++;
}
@@ -428,9 +417,9 @@ void LightManager::device_update_distribution(Device *,
/* normalize cumulative distribution functions */
distribution[num_distribution].totarea = totarea;
distribution[num_distribution].prim = 0.0f;
distribution[num_distribution].lamp.pad = 0.0f;
distribution[num_distribution].lamp.size = 0.0f;
distribution[num_distribution].prim = 0;
distribution[num_distribution].mesh_light.object_id = OBJECT_NONE;
distribution[num_distribution].mesh_light.shader_flag = 0;
if (totarea > 0.0f) {
for (size_t i = 0; i < num_distribution; i++)
@@ -441,82 +430,221 @@ void LightManager::device_update_distribution(Device *,
if (progress.get_cancel())
return;
/* update device */
KernelIntegrator *kintegrator = &dscene->data.integrator;
KernelBackground *kbackground = &dscene->data.background;
KernelFilm *kfilm = &dscene->data.film;
/* Update integrator state. */
kintegrator->use_direct_light = (totarea > 0.0f);
if (kintegrator->use_direct_light) {
/* number of emissives */
kintegrator->num_distribution = num_distribution;
/* precompute pdfs */
kintegrator->pdf_triangles = 0.0f;
kintegrator->pdf_lights = 0.0f;
/* sample one, with 0.5 probability of light or triangle */
kintegrator->num_all_lights = num_lights;
if (trianglearea > 0.0f) {
kintegrator->pdf_triangles = 1.0f / trianglearea;
if (num_lights)
kintegrator->pdf_triangles *= 0.5f;
}
/* Precompute pdfs for distribution sampling.
* Sample one, with 0.5 probability of light or triangle. */
kintegrator->distribution_pdf_triangles = 0.0f;
kintegrator->distribution_pdf_lights = 0.0f;
if (trianglearea > 0.0f) {
kintegrator->distribution_pdf_triangles = 1.0f / trianglearea;
if (num_lights) {
kintegrator->pdf_lights = 1.0f / num_lights;
if (trianglearea > 0.0f)
kintegrator->pdf_lights *= 0.5f;
kintegrator->distribution_pdf_triangles *= 0.5f;
}
}
if (num_lights) {
kintegrator->distribution_pdf_lights = 1.0f / num_lights;
if (trianglearea > 0.0f) {
kintegrator->distribution_pdf_lights *= 0.5f;
}
}
/* Copy distribution to device. */
dscene->light_distribution.copy_to_device();
}
void LightManager::device_update_tree(Device *,
DeviceScene *dscene,
Scene *scene,
Progress &progress)
{
KernelIntegrator *kintegrator = &dscene->data.integrator;
if (!kintegrator->use_light_tree) {
dscene->light_tree_nodes.free();
dscene->light_tree_emitters.free();
dscene->light_to_tree.free();
dscene->object_lookup_offset.free();
dscene->triangle_to_tree.free();
return;
}
/* Update light tree. */
progress.set_status("Updating Lights", "Computing tree");
/* Add both lights and emissive triangles to this vector for light tree construction. */
vector<LightTreePrimitive> light_prims;
light_prims.reserve(kintegrator->num_distribution);
vector<LightTreePrimitive> distant_lights;
distant_lights.reserve(kintegrator->num_distant_lights);
vector<uint> object_lookup_offsets(scene->objects.size());
/* When we keep track of the light index, only contributing lights will be added to the device.
* Therefore, we want to keep track of the light's index on the device.
* However, we also need the light's index in the scene when we're constructing the tree. */
int device_light_index = 0;
int scene_light_index = 0;
foreach (Light *light, scene->lights) {
if (light->is_enabled) {
if (light->light_type == LIGHT_BACKGROUND || light->light_type == LIGHT_DISTANT) {
distant_lights.emplace_back(scene, ~device_light_index, scene_light_index);
}
else {
light_prims.emplace_back(scene, ~device_light_index, scene_light_index);
}
device_light_index++;
}
kintegrator->use_lamp_mis = use_lamp_mis;
scene_light_index++;
}
/* bit of an ugly hack to compensate for emitting triangles influencing
* amount of samples we get for this pass */
kfilm->pass_shadow_scale = 1.0f;
/* Similarly, we also want to keep track of the index of triangles that are emissive. */
size_t total_triangles = 0;
int object_id = 0;
foreach (Object *object, scene->objects) {
if (progress.get_cancel())
return;
if (kintegrator->pdf_triangles != 0.0f)
kfilm->pass_shadow_scale /= 0.5f;
if (!object_usable_as_light(object)) {
object_id++;
continue;
}
if (num_background_lights < num_lights)
kfilm->pass_shadow_scale /= (float)(num_lights - num_background_lights) / (float)num_lights;
object_lookup_offsets[object_id] = total_triangles;
/* CDF */
dscene->light_distribution.copy_to_device();
/* Count emissive triangles. */
Mesh *mesh = static_cast<Mesh *>(object->get_geometry());
size_t mesh_num_triangles = mesh->num_triangles();
/* Portals */
if (num_portals > 0) {
kbackground->portal_offset = light_index;
kbackground->num_portals = num_portals;
kbackground->portal_weight = 1.0f;
for (size_t i = 0; i < mesh_num_triangles; i++) {
int shader_index = mesh->get_shader()[i];
Shader *shader = (shader_index < mesh->get_used_shaders().size()) ?
static_cast<Shader *>(mesh->get_used_shaders()[shader_index]) :
scene->default_surface;
if (shader->emission_sampling != EMISSION_SAMPLING_NONE) {
light_prims.emplace_back(scene, i, object_id);
}
}
total_triangles += mesh_num_triangles;
object_id++;
}
/* Append distant lights to the end of `light_prims` */
std::move(distant_lights.begin(), distant_lights.end(), std::back_inserter(light_prims));
/* Update integrator state. */
kintegrator->use_direct_light = !light_prims.empty();
/* TODO: For now, we'll start with a smaller number of max lights in a node.
* More benchmarking is needed to determine what number works best. */
LightTree light_tree(light_prims, kintegrator->num_distant_lights, 8);
/* We want to create separate arrays corresponding to triangles and lights,
* which will be used to index back into the light tree for PDF calculations. */
const size_t num_lights = kintegrator->num_lights;
uint *light_array = dscene->light_to_tree.alloc(num_lights);
uint *object_offsets = dscene->object_lookup_offset.alloc(object_lookup_offsets.size());
uint *triangle_array = dscene->triangle_to_tree.alloc(total_triangles);
for (int i = 0; i < object_lookup_offsets.size(); i++) {
object_offsets[i] = object_lookup_offsets[i];
}
/* First initialize the light tree's nodes. */
const vector<LightTreeNode> &linearized_bvh = light_tree.get_nodes();
KernelLightTreeNode *light_tree_nodes = dscene->light_tree_nodes.alloc(linearized_bvh.size());
KernelLightTreeEmitter *light_tree_emitters = dscene->light_tree_emitters.alloc(
light_prims.size());
for (int index = 0; index < linearized_bvh.size(); index++) {
const LightTreeNode &node = linearized_bvh[index];
light_tree_nodes[index].energy = node.energy;
light_tree_nodes[index].bbox.min = node.bbox.min;
light_tree_nodes[index].bbox.max = node.bbox.max;
light_tree_nodes[index].bcone.axis = node.bcone.axis;
light_tree_nodes[index].bcone.theta_o = node.bcone.theta_o;
light_tree_nodes[index].bcone.theta_e = node.bcone.theta_e;
light_tree_nodes[index].bit_trail = node.bit_trail;
light_tree_nodes[index].num_prims = node.num_prims;
/* Here we need to make a distinction between interior and leaf nodes. */
if (node.is_leaf()) {
light_tree_nodes[index].child_index = -node.first_prim_index;
for (int i = 0; i < node.num_prims; i++) {
int emitter_index = i + node.first_prim_index;
LightTreePrimitive &prim = light_prims[emitter_index];
light_tree_emitters[emitter_index].energy = prim.energy;
light_tree_emitters[emitter_index].theta_o = prim.bcone.theta_o;
light_tree_emitters[emitter_index].theta_e = prim.bcone.theta_e;
if (prim.is_triangle()) {
light_tree_emitters[emitter_index].mesh_light.object_id = prim.object_id;
int shader_flag = 0;
Object *object = scene->objects[prim.object_id];
Mesh *mesh = static_cast<Mesh *>(object->get_geometry());
Shader *shader = static_cast<Shader *>(
mesh->get_used_shaders()[mesh->get_shader()[prim.prim_id]]);
if (!(object->get_visibility() & PATH_RAY_CAMERA)) {
shader_flag |= SHADER_EXCLUDE_CAMERA;
}
if (!(object->get_visibility() & PATH_RAY_DIFFUSE)) {
shader_flag |= SHADER_EXCLUDE_DIFFUSE;
}
if (!(object->get_visibility() & PATH_RAY_GLOSSY)) {
shader_flag |= SHADER_EXCLUDE_GLOSSY;
}
if (!(object->get_visibility() & PATH_RAY_TRANSMIT)) {
shader_flag |= SHADER_EXCLUDE_TRANSMIT;
}
if (!(object->get_visibility() & PATH_RAY_VOLUME_SCATTER)) {
shader_flag |= SHADER_EXCLUDE_SCATTER;
}
if (!(object->get_is_shadow_catcher())) {
shader_flag |= SHADER_EXCLUDE_SHADOW_CATCHER;
}
light_tree_emitters[emitter_index].prim_id = prim.prim_id + mesh->prim_offset;
light_tree_emitters[emitter_index].mesh_light.shader_flag = shader_flag;
light_tree_emitters[emitter_index].mesh_light.emission_sampling =
shader->emission_sampling;
triangle_array[prim.prim_id + object_lookup_offsets[prim.object_id]] = emitter_index;
}
else {
light_tree_emitters[emitter_index].prim_id = prim.prim_id;
light_tree_emitters[emitter_index].mesh_light.shader_flag = 0;
light_tree_emitters[emitter_index].mesh_light.object_id = OBJECT_NONE;
light_tree_emitters[emitter_index].mesh_light.emission_sampling =
EMISSION_SAMPLING_FRONT_BACK;
light_array[~prim.prim_id] = emitter_index;
}
light_tree_emitters[emitter_index].parent_index = index;
}
}
else {
kbackground->num_portals = 0;
kbackground->portal_offset = 0;
kbackground->portal_weight = 0.0f;
light_tree_nodes[index].child_index = node.right_child_index;
}
/* Map */
kbackground->map_weight = background_mis ? 1.0f : 0.0f;
}
else {
dscene->light_distribution.free();
kintegrator->num_distribution = 0;
kintegrator->num_all_lights = 0;
kintegrator->pdf_triangles = 0.0f;
kintegrator->pdf_lights = 0.0f;
kintegrator->use_lamp_mis = false;
kbackground->num_portals = 0;
kbackground->portal_offset = 0;
kbackground->portal_weight = 0.0f;
kbackground->sun_weight = 0.0f;
kbackground->map_weight = 0.0f;
kfilm->pass_shadow_scale = 1.0f;
}
/* Copy arrays to device. */
dscene->light_tree_nodes.copy_to_device();
dscene->light_tree_emitters.copy_to_device();
dscene->light_to_tree.copy_to_device();
dscene->object_lookup_offset.copy_to_device();
dscene->triangle_to_tree.copy_to_device();
}
static void background_cdf(
@@ -564,31 +692,34 @@ void LightManager::device_update_background(Device *device,
Scene *scene,
Progress &progress)
{
KernelIntegrator *kintegrator = &dscene->data.integrator;
KernelBackground *kbackground = &dscene->data.background;
Light *background_light = NULL;
bool background_mis = false;
/* find background light */
foreach (Light *light, scene->lights) {
if (light->light_type == LIGHT_BACKGROUND) {
if (light->light_type == LIGHT_BACKGROUND && light->is_enabled) {
background_light = light;
break;
background_mis |= light->use_mis;
}
}
kbackground->portal_weight = kintegrator->num_portals > 0 ? 1.0f : 0.0f;
kbackground->map_weight = background_mis ? 1.0f : 0.0f;
kbackground->sun_weight = 0.0f;
/* no background light found, signal renderer to skip sampling */
if (!background_light || !background_light->is_enabled) {
kbackground->map_res_x = 0;
kbackground->map_res_y = 0;
kbackground->map_weight = 0.0f;
kbackground->sun_weight = 0.0f;
kbackground->use_mis = (kbackground->portal_weight > 0.0f);
return;
}
progress.set_status("Updating Lights", "Importance map");
assert(dscene->data.integrator.use_direct_light);
int2 environment_res = make_int2(0, 0);
Shader *shader = scene->background->get_shader(scene);
int num_suns = 0;
@@ -632,6 +763,7 @@ void LightManager::device_update_background(Device *device,
kbackground->sun = make_float4(
sun_direction.x, sun_direction.y, sun_direction.z, half_angle);
/* empirical value */
kbackground->sun_weight = 4.0f;
environment_res.x = max(environment_res.x, 512);
environment_res.y = max(environment_res.y, 256);
@@ -701,6 +833,8 @@ void LightManager::device_update_background(Device *device,
float cdf_total = marg_cdf[res.y - 1].y + marg_cdf[res.y - 1].x / res.y;
marg_cdf[res.y].x = cdf_total;
background_light->set_average_radiance(cdf_total * M_PI_2_F);
if (cdf_total > 0.0f)
for (int i = 1; i < res.y; i++)
marg_cdf[i].y /= cdf_total;
@@ -714,27 +848,91 @@ void LightManager::device_update_background(Device *device,
dscene->light_background_conditional_cdf.copy_to_device();
}
void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *scene)
void LightManager::device_update_lights(Device *device, DeviceScene *dscene, Scene *scene)
{
int num_scene_lights = scene->lights.size();
/* Counts lights in the scene. */
size_t num_lights = 0;
size_t num_portals = 0;
size_t num_background_lights = 0;
size_t num_distant_lights = 0;
bool use_light_mis = false;
int num_lights = 0;
foreach (Light *light, scene->lights) {
if (light->is_enabled || light->is_portal) {
if (light->is_enabled) {
num_lights++;
if (light->light_type == LIGHT_DISTANT) {
num_distant_lights++;
}
else if (light->light_type == LIGHT_POINT || light->light_type == LIGHT_SPOT) {
use_light_mis |= (light->size > 0.0f && light->use_mis);
}
else if (light->light_type == LIGHT_AREA) {
use_light_mis |= light->use_mis;
}
else if (light->light_type == LIGHT_BACKGROUND) {
num_distant_lights++;
num_background_lights++;
}
}
if (light->is_portal) {
num_portals++;
}
}
KernelLight *klights = dscene->lights.alloc(num_lights);
/* Update integrator settings. */
KernelIntegrator *kintegrator = &dscene->data.integrator;
kintegrator->use_light_tree = scene->integrator->get_use_light_tree() &&
device->info.has_light_tree;
kintegrator->num_lights = num_lights;
kintegrator->num_distant_lights = num_distant_lights;
kintegrator->num_background_lights = num_background_lights;
kintegrator->use_light_mis = use_light_mis;
if (num_lights == 0) {
VLOG_WORK << "No effective light, ignoring points update.";
return;
}
kintegrator->num_portals = num_portals;
kintegrator->portal_offset = num_lights;
/* Create KernelLight for every portal and enabled light in the scene. */
KernelLight *klights = dscene->lights.alloc(num_lights + num_portals);
int light_index = 0;
int portal_index = num_lights;
foreach (Light *light, scene->lights) {
/* Consider moving portals update to their own function
* keeping this one more manageable. */
if (light->is_portal) {
assert(light->light_type == LIGHT_AREA);
float3 extentu = light->axisu * (light->sizeu * light->size);
float3 extentv = light->axisv * (light->sizev * light->size);
float len_u, len_v;
float3 axis_u = normalize_len(extentu, &len_u);
float3 axis_v = normalize_len(extentv, &len_v);
float area = len_u * len_v;
if (light->round) {
area *= -M_PI_4_F;
}
float invarea = (area != 0.0f) ? 1.0f / area : 1.0f;
float3 dir = light->dir;
dir = safe_normalize(dir);
klights[portal_index].co = light->co;
klights[portal_index].area.axis_u = axis_u;
klights[portal_index].area.len_u = len_u;
klights[portal_index].area.axis_v = axis_v;
klights[portal_index].area.len_v = len_v;
klights[portal_index].area.invarea = invarea;
klights[portal_index].area.dir = dir;
klights[portal_index].tfm = light->tfm;
klights[portal_index].itfm = transform_inverse(light->tfm);
portal_index++;
continue;
}
if (!light->is_enabled) {
continue;
}
@@ -781,10 +979,7 @@ void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *sc
if (light->use_mis && radius > 0.0f)
shader_id |= SHADER_USE_MIS;
klights[light_index].co[0] = co.x;
klights[light_index].co[1] = co.y;
klights[light_index].co[2] = co.z;
klights[light_index].co = co;
klights[light_index].spot.radius = radius;
klights[light_index].spot.invarea = invarea;
}
@@ -803,10 +998,7 @@ void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *sc
if (light->use_mis && area > 0.0f)
shader_id |= SHADER_USE_MIS;
klights[light_index].co[0] = dir.x;
klights[light_index].co[1] = dir.y;
klights[light_index].co[2] = dir.z;
klights[light_index].co = dir;
klights[light_index].distant.invarea = invarea;
klights[light_index].distant.radius = radius;
klights[light_index].distant.cosangle = cosangle;
@@ -814,6 +1006,8 @@ void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *sc
else if (light->light_type == LIGHT_BACKGROUND) {
uint visibility = scene->background->get_visibility();
dscene->data.background.light_index = light_index;
shader_id &= ~SHADER_AREA_LIGHT;
shader_id |= SHADER_USE_MIS;
@@ -831,44 +1025,41 @@ void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *sc
}
}
else if (light->light_type == LIGHT_AREA) {
float3 axisu = light->axisu * (light->sizeu * light->size);
float3 axisv = light->axisv * (light->sizev * light->size);
float area = len(axisu) * len(axisv);
float3 extentu = light->axisu * (light->sizeu * light->size);
float3 extentv = light->axisv * (light->sizev * light->size);
float len_u, len_v;
float3 axis_u = normalize_len(extentu, &len_u);
float3 axis_v = normalize_len(extentv, &len_v);
float area = len_u * len_v;
if (light->round) {
area *= -M_PI_4_F;
}
float invarea = (area != 0.0f) ? 1.0f / area : 1.0f;
float3 dir = light->dir;
/* Convert from spread angle 0..180 to 90..0, clamping to a minimum
* angle to avoid excessive noise. */
const float min_spread_angle = 1.0f * M_PI_F / 180.0f;
const float spread_angle = 0.5f * (M_PI_F - max(light->spread, min_spread_angle));
/* Clamping to a minimum angle to avoid excessive noise. */
const float min_spread = 1.0f * M_PI_F / 180.0f;
const float half_spread = 0.5f * max(light->spread, min_spread);
/* cot_half_spread is h in D10594#269626 */
const float cot_half_spread = tanf(M_PI_2_F - half_spread);
/* Normalization computed using:
* integrate cos(x) * (1 - tan(x) * tan(a)) * sin(x) from x = 0 to pi/2 - a. */
const float tan_spread = tanf(spread_angle);
const float normalize_spread = 2.0f / (2.0f + (2.0f * spread_angle - M_PI_F) * tan_spread);
* integrate cos(x) * (1 - tan(x) / tan(a)) * sin(x) from x = 0 to a, a being half_spread */
const float normalize_spread = 1.0f / (1.0f - half_spread * cot_half_spread);
dir = safe_normalize(dir);
if (light->use_mis && area != 0.0f)
shader_id |= SHADER_USE_MIS;
klights[light_index].co[0] = co.x;
klights[light_index].co[1] = co.y;
klights[light_index].co[2] = co.z;
klights[light_index].area.axisu[0] = axisu.x;
klights[light_index].area.axisu[1] = axisu.y;
klights[light_index].area.axisu[2] = axisu.z;
klights[light_index].area.axisv[0] = axisv.x;
klights[light_index].area.axisv[1] = axisv.y;
klights[light_index].area.axisv[2] = axisv.z;
klights[light_index].co = co;
klights[light_index].area.axis_u = axis_u;
klights[light_index].area.len_u = len_u;
klights[light_index].area.axis_v = axis_v;
klights[light_index].area.len_v = len_v;
klights[light_index].area.invarea = invarea;
klights[light_index].area.dir[0] = dir.x;
klights[light_index].area.dir[1] = dir.y;
klights[light_index].area.dir[2] = dir.z;
klights[light_index].area.tan_spread = tan_spread;
klights[light_index].area.dir = dir;
klights[light_index].area.cot_half_spread = cot_half_spread;
klights[light_index].area.normalize_spread = normalize_spread;
}
else if (light->light_type == LIGHT_SPOT) {
@@ -876,8 +1067,8 @@ void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *sc
float radius = light->size;
float invarea = (radius > 0.0f) ? 1.0f / (M_PI_F * radius * radius) : 1.0f;
float spot_angle = cosf(light->spot_angle * 0.5f);
float spot_smooth = (1.0f - spot_angle) * light->spot_smooth;
float cos_half_spot_angle = cosf(light->spot_angle * 0.5f);
float spot_smooth = (1.0f - cos_half_spot_angle) * light->spot_smooth;
float3 dir = light->dir;
dir = safe_normalize(dir);
@@ -885,17 +1076,12 @@ void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *sc
if (light->use_mis && radius > 0.0f)
shader_id |= SHADER_USE_MIS;
klights[light_index].co[0] = co.x;
klights[light_index].co[1] = co.y;
klights[light_index].co[2] = co.z;
klights[light_index].co = co;
klights[light_index].spot.radius = radius;
klights[light_index].spot.invarea = invarea;
klights[light_index].spot.spot_angle = spot_angle;
klights[light_index].spot.cos_half_spot_angle = cos_half_spot_angle;
klights[light_index].spot.spot_smooth = spot_smooth;
klights[light_index].spot.dir[0] = dir.x;
klights[light_index].spot.dir[1] = dir.y;
klights[light_index].spot.dir[2] = dir.z;
klights[light_index].spot.dir = dir;
}
klights[light_index].shader_id = shader_id;
@@ -918,49 +1104,7 @@ void LightManager::device_update_points(Device *, DeviceScene *dscene, Scene *sc
light_index++;
}
/* TODO(sergey): Consider moving portals update to their own function
* keeping this one more manageable.
*/
foreach (Light *light, scene->lights) {
if (!light->is_portal)
continue;
assert(light->light_type == LIGHT_AREA);
float3 co = light->co;
float3 axisu = light->axisu * (light->sizeu * light->size);
float3 axisv = light->axisv * (light->sizev * light->size);
float area = len(axisu) * len(axisv);
if (light->round) {
area *= -M_PI_4_F;
}
float invarea = (area != 0.0f) ? 1.0f / area : 1.0f;
float3 dir = light->dir;
dir = safe_normalize(dir);
klights[light_index].co[0] = co.x;
klights[light_index].co[1] = co.y;
klights[light_index].co[2] = co.z;
klights[light_index].area.axisu[0] = axisu.x;
klights[light_index].area.axisu[1] = axisu.y;
klights[light_index].area.axisu[2] = axisu.z;
klights[light_index].area.axisv[0] = axisv.x;
klights[light_index].area.axisv[1] = axisv.y;
klights[light_index].area.axisv[2] = axisv.z;
klights[light_index].area.invarea = invarea;
klights[light_index].area.dir[0] = dir.x;
klights[light_index].area.dir[1] = dir.y;
klights[light_index].area.dir[2] = dir.z;
klights[light_index].tfm = light->tfm;
klights[light_index].itfm = transform_inverse(light->tfm);
light_index++;
}
VLOG_INFO << "Number of lights sent to the device: " << light_index;
VLOG_INFO << "Number of lights without contribution: " << num_scene_lights - light_index;
VLOG_INFO << "Number of lights sent to the device: " << num_lights;
dscene->lights.copy_to_device();
}
@@ -986,11 +1130,7 @@ void LightManager::device_update(Device *device,
device_free(device, dscene, need_update_background);
device_update_points(device, dscene, scene);
if (progress.get_cancel())
return;
device_update_distribution(device, dscene, scene, progress);
device_update_lights(device, dscene, scene);
if (progress.get_cancel())
return;
@@ -1000,6 +1140,14 @@ void LightManager::device_update(Device *device,
return;
}
device_update_distribution(device, dscene, scene, progress);
if (progress.get_cancel())
return;
device_update_tree(device, dscene, scene, progress);
if (progress.get_cancel())
return;
device_update_ies(dscene);
if (progress.get_cancel())
return;
@@ -1010,6 +1158,12 @@ void LightManager::device_update(Device *device,
void LightManager::device_free(Device *, DeviceScene *dscene, const bool free_background)
{
/* to-do: check if the light tree member variables need to be wrapped in a conditional too*/
dscene->light_tree_nodes.free();
dscene->light_tree_emitters.free();
dscene->light_to_tree.free();
dscene->triangle_to_tree.free();
dscene->light_distribution.free();
dscene->lights.free();
if (free_background) {

View File

@@ -10,6 +10,7 @@
/* included as Light::set_shader defined through NODE_SOCKET_API does not select
* the right Node::set overload as it does not know that Shader is a Node */
#include "scene/light_tree.h"
#include "scene/shader.h"
#include "util/ies.h"
@@ -50,6 +51,7 @@ class Light : public Node {
NODE_SOCKET_API(Transform, tfm)
NODE_SOCKET_API(int, map_resolution)
NODE_SOCKET_API(float, average_radiance)
NODE_SOCKET_API(float, spot_angle)
NODE_SOCKET_API(float, spot_smooth)
@@ -127,11 +129,12 @@ class LightManager {
*/
void test_enabled_lights(Scene *scene);
void device_update_points(Device *device, DeviceScene *dscene, Scene *scene);
void device_update_lights(Device *device, DeviceScene *dscene, Scene *scene);
void device_update_distribution(Device *device,
DeviceScene *dscene,
Scene *scene,
Progress &progress);
void device_update_tree(Device *device, DeviceScene *dscene, Scene *scene, Progress &progress);
void device_update_background(Device *device,
DeviceScene *dscene,
Scene *scene,

View File

@@ -0,0 +1,390 @@
/* SPDX-License-Identifier: Apache-2.0
* Copyright 2011-2022 Blender Foundation */
#include "scene/light_tree.h"
#include "scene/mesh.h"
#include "scene/object.h"
CCL_NAMESPACE_BEGIN
float OrientationBounds::calculate_measure() const
{
float theta_w = fminf(M_PI_F, theta_o + theta_e);
float cos_theta_o = cosf(theta_o);
float sin_theta_o = sinf(theta_o);
return M_2PI_F * (1 - cos_theta_o) +
M_PI_2_F * (2 * theta_w * sin_theta_o - cosf(theta_o - 2 * theta_w) -
2 * theta_o * sin_theta_o + cos_theta_o);
}
OrientationBounds merge(const OrientationBounds &cone_a, const OrientationBounds &cone_b)
{
if (is_zero(cone_a.axis)) {
return cone_b;
}
if (is_zero(cone_b.axis)) {
return cone_a;
}
/* Set cone a to always have the greater theta_o. */
const OrientationBounds *a = &cone_a;
const OrientationBounds *b = &cone_b;
if (cone_b.theta_o > cone_a.theta_o) {
a = &cone_b;
b = &cone_a;
}
float theta_d = safe_acosf(dot(a->axis, b->axis));
float theta_e = fmaxf(a->theta_e, b->theta_e);
/* Return axis and theta_o of a if it already contains b. */
/* This should also be called when b is empty. */
if (a->theta_o >= fminf(M_PI_F, theta_d + b->theta_o)) {
return OrientationBounds({a->axis, a->theta_o, theta_e});
}
/* Compute new theta_o that contains both a and b. */
float theta_o = (theta_d + a->theta_o + b->theta_o) * 0.5f;
if (theta_o >= M_PI_F) {
return OrientationBounds({a->axis, M_PI_F, theta_e});
}
/* Rotate new axis to be between a and b. */
float theta_r = theta_o - a->theta_o;
float3 new_axis = rotate_around_axis(a->axis, cross(a->axis, b->axis), theta_r);
new_axis = normalize(new_axis);
return OrientationBounds({new_axis, theta_o, theta_e});
}
LightTreePrimitive::LightTreePrimitive(Scene *scene, int prim_id, int object_id)
: prim_id(prim_id), object_id(object_id)
{
bcone = OrientationBounds::empty;
bbox = BoundBox::empty;
if (is_triangle()) {
float3 vertices[3];
Object *object = scene->objects[object_id];
Mesh *mesh = static_cast<Mesh *>(object->get_geometry());
Mesh::Triangle triangle = mesh->get_triangle(prim_id);
Shader *shader = static_cast<Shader *>(mesh->get_used_shaders()[mesh->get_shader()[prim_id]]);
for (int i = 0; i < 3; i++) {
vertices[i] = mesh->get_verts()[triangle.v[i]];
}
/* instanced mesh lights have not applied their transform at this point.
* in this case, these points have to be transformed to get the proper
* spatial bound. */
if (!mesh->transform_applied) {
const Transform &tfm = object->get_tfm();
for (int i = 0; i < 3; i++) {
vertices[i] = transform_point(&tfm, vertices[i]);
}
}
/* TODO: need a better way to handle this when textures are used. */
float area = triangle_area(vertices[0], vertices[1], vertices[2]);
energy = area * average(shader->emission_estimate);
/* NOTE: the original implementation used the bounding box centroid, but primitive centroid
* seems to work fine */
centroid = (vertices[0] + vertices[1] + vertices[2]) / 3.0f;
if (shader->emission_sampling == EMISSION_SAMPLING_FRONT) {
/* Front only. */
bcone.axis = safe_normalize(cross(vertices[1] - vertices[0], vertices[2] - vertices[0]));
bcone.theta_o = 0;
}
else if (shader->emission_sampling == EMISSION_SAMPLING_BACK) {
/* Back only. */
bcone.axis = -safe_normalize(cross(vertices[1] - vertices[0], vertices[2] - vertices[0]));
bcone.theta_o = 0;
}
else {
/* Double sided: any vector in the plane. */
bcone.axis = safe_normalize(vertices[0] - vertices[1]);
bcone.theta_o = M_PI_2_F;
}
bcone.theta_e = M_PI_2_F;
for (int i = 0; i < 3; i++) {
bbox.grow(vertices[i]);
}
}
else {
Light *lamp = scene->lights[object_id];
LightType type = lamp->get_light_type();
const float size = lamp->get_size();
float3 strength = lamp->get_strength();
centroid = scene->lights[object_id]->get_co();
bcone.axis = normalize(lamp->get_dir());
if (type == LIGHT_AREA) {
bcone.theta_o = 0;
bcone.theta_e = lamp->get_spread() * 0.5f;
/* For an area light, sizeu and sizev determine the 2 dimensions of the area light,
* while axisu and axisv determine the orientation of the 2 dimensions.
* We want to add all 4 corners to our bounding box. */
const float3 half_extentu = 0.5f * lamp->get_sizeu() * lamp->get_axisu() * size;
const float3 half_extentv = 0.5f * lamp->get_sizev() * lamp->get_axisv() * size;
bbox.grow(centroid + half_extentu + half_extentv);
bbox.grow(centroid + half_extentu - half_extentv);
bbox.grow(centroid - half_extentu + half_extentv);
bbox.grow(centroid - half_extentu - half_extentv);
strength *= 0.25f; /* eval_fac scaling in `area.h` */
}
else if (type == LIGHT_POINT) {
bcone.theta_o = M_PI_F;
bcone.theta_e = M_PI_2_F;
/* Point and spot lights can emit light from any point within its radius. */
const float3 radius = make_float3(size);
bbox.grow(centroid - radius);
bbox.grow(centroid + radius);
strength *= 0.25f * M_1_PI_F; /* eval_fac scaling in `spot.h` and `point.h` */
}
else if (type == LIGHT_SPOT) {
bcone.theta_o = 0;
bcone.theta_e = lamp->get_spot_angle() * 0.5f;
/* Point and spot lights can emit light from any point within its radius. */
const float3 radius = make_float3(size);
bbox.grow(centroid - radius);
bbox.grow(centroid + radius);
strength *= 0.25f * M_1_PI_F; /* eval_fac scaling in `spot.h` and `point.h` */
}
else if (type == LIGHT_BACKGROUND) {
/* Set an arbitrary direction for the background light. */
bcone.axis = make_float3(0.0f, 0.0f, 1.0f);
/* TODO: this may depend on portal lights as well. */
bcone.theta_o = M_PI_F;
bcone.theta_e = 0;
/* integrate over cosine-weighted hemisphere */
strength *= lamp->get_average_radiance() * M_PI_F;
}
else if (type == LIGHT_DISTANT) {
bcone.theta_o = 0;
bcone.theta_e = 0.5f * lamp->get_angle();
}
if (lamp->get_shader()) {
strength *= lamp->get_shader()->emission_estimate;
}
energy = average(strength);
}
}
LightTree::LightTree(vector<LightTreePrimitive> &prims,
const int &num_distant_lights,
uint max_lights_in_leaf)
{
if (prims.empty()) {
return;
}
max_lights_in_leaf_ = max_lights_in_leaf;
int num_prims = prims.size();
int num_local_lights = num_prims - num_distant_lights;
/* The amount of nodes is estimated to be twice the amount of primitives */
nodes_.reserve(2 * num_prims);
nodes_.emplace_back(); /* root node */
recursive_build(0, num_local_lights, prims, 0, 1); /* build tree */
nodes_[0].make_interior(nodes_.size());
/* All distant lights are grouped to one node (right child of the root node) */
OrientationBounds bcone = OrientationBounds::empty;
float energy_total = 0.0;
for (int i = num_local_lights; i < num_prims; i++) {
const LightTreePrimitive &prim = prims.at(i);
bcone = merge(bcone, prim.bcone);
energy_total += prim.energy;
}
nodes_.emplace_back(BoundBox::empty, bcone, energy_total, 1);
nodes_.back().make_leaf(num_local_lights, num_distant_lights);
nodes_.shrink_to_fit();
}
const vector<LightTreeNode> &LightTree::get_nodes() const
{
return nodes_;
}
int LightTree::recursive_build(
int start, int end, vector<LightTreePrimitive> &prims, uint bit_trail, int depth)
{
BoundBox bbox = BoundBox::empty;
OrientationBounds bcone = OrientationBounds::empty;
BoundBox centroid_bounds = BoundBox::empty;
float energy_total = 0.0;
int num_prims = end - start;
int current_index = nodes_.size();
for (int i = start; i < end; i++) {
const LightTreePrimitive &prim = prims.at(i);
bbox.grow(prim.bbox);
bcone = merge(bcone, prim.bcone);
centroid_bounds.grow(prim.centroid);
energy_total += prim.energy;
}
nodes_.emplace_back(bbox, bcone, energy_total, bit_trail);
bool try_splitting = num_prims > 1 && len(centroid_bounds.size()) > 0.0f;
int split_dim = -1, split_bucket = 0, num_left_prims = 0;
bool should_split = false;
if (try_splitting) {
/* Find the best place to split the primitives into 2 nodes.
* If the best split cost is no better than making a leaf node, make a leaf instead.*/
float min_cost = min_split_saoh(
centroid_bounds, start, end, bbox, bcone, split_dim, split_bucket, num_left_prims, prims);
should_split = num_prims > max_lights_in_leaf_ || min_cost < energy_total;
}
if (should_split) {
int middle;
if (split_dim != -1) {
/* Partition the primitives between start and end based on the split dimension and bucket
* calculated by `split_saoh` */
middle = start + num_left_prims;
std::nth_element(prims.begin() + start,
prims.begin() + middle,
prims.begin() + end,
[split_dim](const LightTreePrimitive &l, const LightTreePrimitive &r) {
return l.centroid[split_dim] < r.centroid[split_dim];
});
}
else {
/* Degenerate case with many lights in the same place. */
middle = (start + end) / 2;
}
[[maybe_unused]] int left_index = recursive_build(start, middle, prims, bit_trail, depth + 1);
int right_index = recursive_build(middle, end, prims, bit_trail | (1u << depth), depth + 1);
assert(left_index == current_index + 1);
nodes_[current_index].make_interior(right_index);
}
else {
nodes_[current_index].make_leaf(start, num_prims);
}
return current_index;
}
float LightTree::min_split_saoh(const BoundBox &centroid_bbox,
int start,
int end,
const BoundBox &bbox,
const OrientationBounds &bcone,
int &split_dim,
int &split_bucket,
int &num_left_prims,
const vector<LightTreePrimitive> &prims)
{
/* Even though this factor is used for every bucket, we use it to compare
* the min_cost and total_energy (when deciding between creating a leaf or interior node. */
const float bbox_area = bbox.area();
const bool has_area = bbox_area != 0.0f;
const float total_area = has_area ? bbox_area : len(bbox.size());
const float total_cost = total_area * bcone.calculate_measure();
if (total_cost == 0.0f) {
return FLT_MAX;
}
const float inv_total_cost = 1.0f / total_cost;
const float3 extent = centroid_bbox.size();
const float max_extent = max4(extent.x, extent.y, extent.z, 0.0f);
/* Check each dimension to find the minimum splitting cost. */
float min_cost = FLT_MAX;
for (int dim = 0; dim < 3; dim++) {
/* If the centroid bounding box is 0 along a given dimension, skip it. */
if (centroid_bbox.size()[dim] == 0.0f) {
continue;
}
const float inv_extent = 1 / (centroid_bbox.size()[dim]);
/* Fill in buckets with primitives. */
vector<LightTreeBucketInfo> buckets(LightTreeBucketInfo::num_buckets);
for (int i = start; i < end; i++) {
const LightTreePrimitive &prim = prims[i];
/* Place primitive into the appropriate bucket,
* where the centroid box is split into equal partitions. */
int bucket_idx = LightTreeBucketInfo::num_buckets *
(prim.centroid[dim] - centroid_bbox.min[dim]) * inv_extent;
if (bucket_idx == LightTreeBucketInfo::num_buckets) {
bucket_idx = LightTreeBucketInfo::num_buckets - 1;
}
buckets[bucket_idx].count++;
buckets[bucket_idx].energy += prim.energy;
buckets[bucket_idx].bbox.grow(prim.bbox);
buckets[bucket_idx].bcone = merge(buckets[bucket_idx].bcone, prim.bcone);
}
/* Calculate the cost of splitting at each point between partitions. */
vector<float> bucket_costs(LightTreeBucketInfo::num_buckets - 1);
float energy_L, energy_R;
BoundBox bbox_L, bbox_R;
OrientationBounds bcone_L, bcone_R;
for (int split = 0; split < LightTreeBucketInfo::num_buckets - 1; split++) {
energy_L = 0;
energy_R = 0;
bbox_L = BoundBox::empty;
bbox_R = BoundBox::empty;
bcone_L = OrientationBounds::empty;
bcone_R = OrientationBounds::empty;
for (int left = 0; left <= split; left++) {
if (buckets[left].bbox.valid()) {
energy_L += buckets[left].energy;
bbox_L.grow(buckets[left].bbox);
bcone_L = merge(bcone_L, buckets[left].bcone);
}
}
for (int right = split + 1; right < LightTreeBucketInfo::num_buckets; right++) {
if (buckets[right].bbox.valid()) {
energy_R += buckets[right].energy;
bbox_R.grow(buckets[right].bbox);
bcone_R = merge(bcone_R, buckets[right].bcone);
}
}
/* Calculate the cost of splitting using the heuristic as described in the paper. */
const float area_L = has_area ? bbox_L.area() : len(bbox_L.size());
const float area_R = has_area ? bbox_R.area() : len(bbox_R.size());
float left = (bbox_L.valid()) ? energy_L * area_L * bcone_L.calculate_measure() : 0.0f;
float right = (bbox_R.valid()) ? energy_R * area_R * bcone_R.calculate_measure() : 0.0f;
float regularization = max_extent * inv_extent;
bucket_costs[split] = regularization * (left + right) * inv_total_cost;
if (bucket_costs[split] < min_cost) {
min_cost = bucket_costs[split];
split_dim = dim;
split_bucket = split;
num_left_prims = 0;
for (int i = 0; i <= split_bucket; i++) {
num_left_prims += buckets[i].count;
}
}
}
}
return min_cost;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,160 @@
/* SPDX-License-Identifier: Apache-2.0
* Copyright 2011-2022 Blender Foundation */
#ifndef __LIGHT_TREE_H__
#define __LIGHT_TREE_H__
#include "scene/light.h"
#include "scene/scene.h"
#include "util/boundbox.h"
#include "util/types.h"
#include "util/vector.h"
CCL_NAMESPACE_BEGIN
/* Orientation Bounds
*
* Bounds the normal axis of the lights,
* along with their emission profiles */
struct OrientationBounds {
float3 axis; /* normal axis of the light */
float theta_o; /* angle bounding the normals */
float theta_e; /* angle bounding the light emissions */
__forceinline OrientationBounds()
{
}
__forceinline OrientationBounds(const float3 &axis_, float theta_o_, float theta_e_)
: axis(axis_), theta_o(theta_o_), theta_e(theta_e_)
{
}
enum empty_t { empty = 0 };
/* If the orientation bound is set to empty, the values are set to minumums
* so that merging it with another non-empty orientation bound guarantees that
* the return value is equal to non-empty orientation bound. */
__forceinline OrientationBounds(empty_t)
: axis(make_float3(0, 0, 0)), theta_o(FLT_MIN), theta_e(FLT_MIN)
{
}
float calculate_measure() const;
};
OrientationBounds merge(const OrientationBounds &cone_a, const OrientationBounds &cone_b);
/* --------------------------------------------------------------------
* Light Tree Construction
*
* The light tree construction is based on PBRT's BVH construction.
*/
/* Light Tree Primitive
* Struct that indexes into the scene's triangle and light arrays. */
struct LightTreePrimitive {
/* `prim_id >= 0` is an index into an object's local triangle index,
* otherwise `-prim_id-1`(`~prim`) is an index into device lights array. */
int prim_id;
int object_id;
float energy;
float3 centroid;
OrientationBounds bcone;
BoundBox bbox;
LightTreePrimitive(Scene *scene, int prim_id, int object_id);
inline bool is_triangle() const
{
return prim_id >= 0;
};
};
/* Light Tree Bucket Info
* Struct used to determine splitting costs in the light BVH. */
struct LightTreeBucketInfo {
LightTreeBucketInfo()
: energy(0.0f), bbox(BoundBox::empty), bcone(OrientationBounds::empty), count(0)
{
}
float energy; /* Total energy in the partition */
BoundBox bbox;
OrientationBounds bcone;
int count;
static const int num_buckets = 12;
};
/* Light Tree Node */
struct LightTreeNode {
BoundBox bbox;
OrientationBounds bcone;
float energy;
uint bit_trail;
int num_prims = -1;
union {
int first_prim_index; /* leaf nodes contain an index to first primitive. */
int right_child_index; /* interior nodes contain an index to second child. */
};
LightTreeNode() = default;
LightTreeNode(const BoundBox &bbox,
const OrientationBounds &bcone,
const float &energy,
const uint &bit_trial)
: bbox(bbox), bcone(bcone), energy(energy), bit_trail(bit_trial)
{
}
void make_leaf(const uint &first_prim_index, const int &num_prims)
{
this->first_prim_index = first_prim_index;
this->num_prims = num_prims;
}
void make_interior(const int &right_child_index)
{
this->right_child_index = right_child_index;
}
inline bool is_leaf() const
{
return num_prims >= 0;
}
};
/* Light BVH
*
* BVH-like data structure that keeps track of lights
* and considers additional orientation and energy information */
class LightTree {
vector<LightTreeNode> nodes_;
uint max_lights_in_leaf_;
public:
LightTree(vector<LightTreePrimitive> &prims,
const int &num_distant_lights,
uint max_lights_in_leaf);
const vector<LightTreeNode> &get_nodes() const;
private:
int recursive_build(
int start, int end, vector<LightTreePrimitive> &prims, uint bit_trail, int depth);
float min_split_saoh(const BoundBox &centroid_bbox,
int start,
int end,
const BoundBox &bbox,
const OrientationBounds &bcone,
int &split_dim,
int &split_bucket,
int &num_left_prims,
const vector<LightTreePrimitive> &prims);
};
CCL_NAMESPACE_END
#endif /* __LIGHT_TREE_H__ */

View File

@@ -231,7 +231,7 @@ void Object::tag_update(Scene *scene)
foreach (Node *node, geometry->get_used_shaders()) {
Shader *shader = static_cast<Shader *>(node);
if (shader->get_use_mis() && shader->has_surface_emission)
if (shader->emission_sampling != EMISSION_SAMPLING_NONE)
scene->light_manager->tag_update(scene, LightManager::EMISSIVE_MESH_MODIFIED);
}
}
@@ -565,10 +565,12 @@ void ObjectManager::device_update_object_transform(UpdateObjectTransformState *s
void ObjectManager::device_update_prim_offsets(Device *device, DeviceScene *dscene, Scene *scene)
{
BVHLayoutMask layout_mask = device->get_bvh_layout_mask();
if (layout_mask != BVH_LAYOUT_METAL && layout_mask != BVH_LAYOUT_MULTI_METAL &&
layout_mask != BVH_LAYOUT_MULTI_METAL_EMBREE) {
return;
if (!scene->integrator->get_use_light_tree()) {
BVHLayoutMask layout_mask = device->get_bvh_layout_mask();
if (layout_mask != BVH_LAYOUT_METAL && layout_mask != BVH_LAYOUT_MULTI_METAL &&
layout_mask != BVH_LAYOUT_MULTI_METAL_EMBREE) {
return;
}
}
/* On MetalRT, primitive / curve segment offsets can't be baked at BVH build time. Intersection

View File

@@ -137,7 +137,7 @@ void OSLShaderManager::device_update_specific(Device *device,
compiler.compile(og, shader);
});
if (shader->get_use_mis() && shader->has_surface_emission)
if (shader->emission_sampling != EMISSION_SAMPLING_NONE)
scene->light_manager->tag_update(scene, LightManager::SHADER_COMPILED);
}
@@ -552,6 +552,7 @@ OSLNode *OSLShaderManager::osl_node(ShaderGraph *graph,
SocketType::Type socket_type;
/* Read type and default value. */
if (param->isclosure) {
socket_type = SocketType::CLOSURE;
}
@@ -606,7 +607,21 @@ OSLNode *OSLShaderManager::osl_node(ShaderGraph *graph,
node->add_output(param->name, socket_type);
}
else {
node->add_input(param->name, socket_type);
/* Detect if we should leave parameter initialization to OSL, either though
* not constant default or widget metadata. */
int socket_flags = 0;
if (!param->validdefault) {
socket_flags |= SocketType::LINK_OSL_INITIALIZER;
}
for (const OSL::OSLQuery::Parameter &metadata : param->metadata) {
if (metadata.type == TypeDesc::STRING) {
if (metadata.name == "widget" && metadata.sdefault[0] == "null") {
socket_flags |= SocketType::LINK_OSL_INITIALIZER;
}
}
}
node->add_input(param->name, socket_type, socket_flags);
}
}
@@ -731,8 +746,12 @@ void OSLCompiler::add(ShaderNode *node, const char *name, bool isfilepath)
foreach (ShaderInput *input, node->inputs) {
if (!input->link) {
/* checks to untangle graphs */
if (node_skip_input(node, input))
if (node_skip_input(node, input)) {
continue;
}
if ((input->flags() & SocketType::LINK_OSL_INITIALIZER) && !(input->constant_folded_in)) {
continue;
}
string param_name = compatible_name(node, input);
const SocketType &socket = input->socket_type;
@@ -800,8 +819,11 @@ void OSLCompiler::add(ShaderNode *node, const char *name, bool isfilepath)
if (current_type == SHADER_TYPE_SURFACE) {
if (info) {
if (info->has_surface_emission)
current_shader->has_surface_emission = true;
if (info->has_surface_emission && node->special_type == SHADER_SPECIAL_TYPE_OSL) {
/* Will be used by Shader::estimate_emission. */
OSLNode *oslnode = static_cast<OSLNode *>(node);
oslnode->has_emission = true;
}
if (info->has_surface_transparent)
current_shader->has_surface_transparent = true;
if (info->has_surface_bssrdf) {
@@ -1101,8 +1123,6 @@ void OSLCompiler::generate_nodes(const ShaderNodeSet &nodes)
done.insert(node);
if (current_type == SHADER_TYPE_SURFACE) {
if (node->has_surface_emission())
current_shader->has_surface_emission = true;
if (node->has_surface_transparent())
current_shader->has_surface_transparent = true;
if (node->get_feature() & KERNEL_FEATURE_NODE_RAYTRACE)
@@ -1194,7 +1214,6 @@ void OSLCompiler::compile(OSLGlobals *og, Shader *shader)
current_shader = shader;
shader->has_surface = false;
shader->has_surface_emission = false;
shader->has_surface_transparent = false;
shader->has_surface_bssrdf = false;
shader->has_bump = has_bump;
@@ -1237,6 +1256,9 @@ void OSLCompiler::compile(OSLGlobals *og, Shader *shader)
}
else
shader->osl_displacement_ref = OSL::ShaderGroupRef();
/* Estimate emission for MIS. */
shader->estimate_emission();
}
/* push state to array for lookup */

View File

@@ -52,7 +52,6 @@ const NodeEnum *Pass::get_type_enum()
pass_type_enum.insert("emission", PASS_EMISSION);
pass_type_enum.insert("background", PASS_BACKGROUND);
pass_type_enum.insert("ao", PASS_AO);
pass_type_enum.insert("shadow", PASS_SHADOW);
pass_type_enum.insert("diffuse", PASS_DIFFUSE);
pass_type_enum.insert("diffuse_direct", PASS_DIFFUSE_DIRECT);
pass_type_enum.insert("diffuse_indirect", PASS_DIFFUSE_INDIRECT);
@@ -209,10 +208,6 @@ PassInfo Pass::get_info(const PassType type, const bool include_albedo, const bo
case PASS_AO:
pass_info.num_components = 3;
break;
case PASS_SHADOW:
pass_info.num_components = 3;
pass_info.use_exposure = false;
break;
case PASS_DIFFUSE_COLOR:
case PASS_GLOSSY_COLOR:

View File

@@ -71,6 +71,11 @@ DeviceScene::DeviceScene(Device *device)
lights(device, "lights", MEM_GLOBAL),
light_background_marginal_cdf(device, "light_background_marginal_cdf", MEM_GLOBAL),
light_background_conditional_cdf(device, "light_background_conditional_cdf", MEM_GLOBAL),
light_tree_nodes(device, "light_tree_nodes", MEM_GLOBAL),
light_tree_emitters(device, "light_tree_emitters", MEM_GLOBAL),
light_to_tree(device, "light_to_tree", MEM_GLOBAL),
object_lookup_offset(device, "object_lookup_offset", MEM_GLOBAL),
triangle_to_tree(device, "triangle_to_tree", MEM_GLOBAL),
particles(device, "particles", MEM_GLOBAL),
svm_nodes(device, "svm_nodes", MEM_GLOBAL),
shaders(device, "shaders", MEM_GLOBAL),
@@ -485,6 +490,8 @@ void Scene::update_kernel_features()
return;
}
thread_scoped_lock scene_lock(mutex);
/* These features are not being tweaked as often as shaders,
* so could be done selective magic for the viewport as well. */
uint kernel_features = shader_manager->get_kernel_features(this);
@@ -571,9 +578,6 @@ bool Scene::update(Progress &progress)
return false;
}
/* Load render kernels, before device update where we upload data to the GPU. */
load_kernels(progress, false);
/* Upload scene data to the GPU. */
progress.set_status("Updating Scene");
MEM_GUARDED_CALL(&progress, device_update, device, progress);
@@ -613,13 +617,8 @@ static void log_kernel_features(const uint features)
<< "\n";
}
bool Scene::load_kernels(Progress &progress, bool lock_scene)
bool Scene::load_kernels(Progress &progress)
{
thread_scoped_lock scene_lock;
if (lock_scene) {
scene_lock = thread_scoped_lock(mutex);
}
update_kernel_features();
const uint kernel_features = dscene.data.kernel_features;

View File

@@ -111,6 +111,13 @@ class DeviceScene {
device_vector<float2> light_background_marginal_cdf;
device_vector<float2> light_background_conditional_cdf;
/* light tree */
device_vector<KernelLightTreeNode> light_tree_nodes;
device_vector<KernelLightTreeEmitter> light_tree_emitters;
device_vector<uint> light_to_tree;
device_vector<uint> object_lookup_offset;
device_vector<uint> triangle_to_tree;
/* particles */
device_vector<KernelParticle> particles;
@@ -270,6 +277,7 @@ class Scene : public NodeOwner {
void enable_update_stats();
bool load_kernels(Progress &progress);
bool update(Progress &progress);
bool has_shadow_catcher();
@@ -333,7 +341,6 @@ class Scene : public NodeOwner {
uint loaded_kernel_features;
void update_kernel_features();
bool load_kernels(Progress &progress, bool lock_scene = true);
bool has_shadow_catcher_ = false;
bool shadow_catcher_modified_ = true;

View File

@@ -147,7 +147,17 @@ NODE_DEFINE(Shader)
{
NodeType *type = NodeType::add("shader", create);
SOCKET_BOOLEAN(use_mis, "Use MIS", true);
static NodeEnum emission_sampling_method_enum;
emission_sampling_method_enum.insert("none", EMISSION_SAMPLING_NONE);
emission_sampling_method_enum.insert("auto", EMISSION_SAMPLING_AUTO);
emission_sampling_method_enum.insert("front", EMISSION_SAMPLING_FRONT);
emission_sampling_method_enum.insert("back", EMISSION_SAMPLING_BACK);
emission_sampling_method_enum.insert("front_back", EMISSION_SAMPLING_FRONT_BACK);
SOCKET_ENUM(emission_sampling_method,
"Emission Sampling Method",
emission_sampling_method_enum,
EMISSION_SAMPLING_AUTO);
SOCKET_BOOLEAN(use_transparent_shadow, "Use Transparent Shadow", true);
SOCKET_BOOLEAN(heterogeneous_volume, "Heterogeneous Volume", true);
@@ -189,7 +199,6 @@ Shader::Shader() : Node(get_node_type())
has_surface = false;
has_surface_transparent = false;
has_surface_emission = false;
has_surface_raytrace = false;
has_surface_bssrdf = false;
has_volume = false;
@@ -203,6 +212,10 @@ Shader::Shader() : Node(get_node_type())
has_volume_connected = false;
prev_volume_step_rate = 0.0f;
emission_estimate = zero_float3();
emission_sampling = EMISSION_SAMPLING_NONE;
emission_is_constant = true;
displacement_method = DISPLACE_BUMP;
id = -1;
@@ -217,50 +230,141 @@ Shader::~Shader()
delete graph;
}
bool Shader::is_constant_emission(float3 *emission)
static float3 output_estimate_emission(ShaderOutput *output, bool &is_constant)
{
/* Only supports a few nodes for now, not arbitrary shader graphs. */
ShaderNode *node = (output) ? output->parent : nullptr;
if (node == nullptr) {
return zero_float3();
}
else if (node->type == EmissionNode::get_node_type() ||
node->type == BackgroundNode::get_node_type()) {
/* Emission and Background node. */
ShaderInput *color_in = node->input("Color");
ShaderInput *strength_in = node->input("Strength");
float3 estimate = one_float3();
if (color_in->link) {
is_constant = false;
}
else {
estimate *= node->get_float3(color_in->socket_type);
}
if (strength_in->link) {
is_constant = false;
estimate *= output_estimate_emission(strength_in->link, is_constant);
}
else {
estimate *= node->get_float(strength_in->socket_type);
}
return estimate;
}
else if (node->type == LightFalloffNode::get_node_type()) {
/* Light Falloff node. */
ShaderInput *strength_in = node->input("Strength");
is_constant = false;
return (strength_in->link) ? output_estimate_emission(strength_in->link, is_constant) :
make_float3(node->get_float(strength_in->socket_type));
}
else if (node->type == AddClosureNode::get_node_type()) {
/* Add Closure. */
ShaderInput *closure1_in = node->input("Closure1");
ShaderInput *closure2_in = node->input("Closure2");
const float3 estimate1 = (closure1_in->link) ?
output_estimate_emission(closure1_in->link, is_constant) :
zero_float3();
const float3 estimate2 = (closure2_in->link) ?
output_estimate_emission(closure2_in->link, is_constant) :
zero_float3();
return estimate1 + estimate2;
}
else if (node->type == MixClosureNode::get_node_type()) {
/* Mix Closure. */
ShaderInput *fac_in = node->input("Fac");
ShaderInput *closure1_in = node->input("Closure1");
ShaderInput *closure2_in = node->input("Closure2");
const float3 estimate1 = (closure1_in->link) ?
output_estimate_emission(closure1_in->link, is_constant) :
zero_float3();
const float3 estimate2 = (closure2_in->link) ?
output_estimate_emission(closure2_in->link, is_constant) :
zero_float3();
if (fac_in->link) {
is_constant = false;
return estimate1 + estimate2;
}
else {
const float fac = node->get_float(fac_in->socket_type);
return (1.0f - fac) * estimate1 + fac * estimate2;
}
}
else {
/* Other nodes, potentially OSL nodes with arbitrary code for which all we can
* determine is if it has emission or not. */
const bool has_emission = node->has_surface_emission();
float3 estimate;
if (output->type() == SocketType::CLOSURE) {
if (has_emission) {
estimate = one_float3();
is_constant = false;
}
else {
estimate = zero_float3();
}
foreach (const ShaderInput *in, node->inputs) {
if (in->type() == SocketType::CLOSURE && in->link) {
estimate += output_estimate_emission(in->link, is_constant);
}
}
}
else {
estimate = one_float3();
is_constant = false;
}
return estimate;
}
}
void Shader::estimate_emission()
{
/* If the shader has AOVs, they need to be evaluated, so we can't skip the shader. */
emission_is_constant = true;
foreach (ShaderNode *node, graph->nodes) {
if (node->special_type == SHADER_SPECIAL_TYPE_OUTPUT_AOV) {
return false;
emission_is_constant = false;
}
}
ShaderInput *surf = graph->output()->input("Surface");
emission_estimate = output_estimate_emission(surf->link, emission_is_constant);
if (surf->link == NULL) {
return false;
if (is_zero(emission_estimate)) {
emission_sampling = EMISSION_SAMPLING_NONE;
}
if (surf->link->parent->type == EmissionNode::get_node_type()) {
EmissionNode *node = (EmissionNode *)surf->link->parent;
assert(node->input("Color"));
assert(node->input("Strength"));
if (node->input("Color")->link || node->input("Strength")->link) {
return false;
}
*emission = node->get_color() * node->get_strength();
}
else if (surf->link->parent->type == BackgroundNode::get_node_type()) {
BackgroundNode *node = (BackgroundNode *)surf->link->parent;
assert(node->input("Color"));
assert(node->input("Strength"));
if (node->input("Color")->link || node->input("Strength")->link) {
return false;
}
*emission = node->get_color() * node->get_strength();
else if (emission_sampling_method == EMISSION_SAMPLING_AUTO) {
/* Automatically disable MIS when emission is low, to avoid weakly emitting
* using a lot of memory in the light tree and potentially wasting samples
* where indirect light samples are sufficient.
* Possible optimization: estimate front and back emission separately. */
emission_sampling = (reduce_max(emission_estimate) > 0.5f) ? EMISSION_SAMPLING_FRONT_BACK :
EMISSION_SAMPLING_NONE;
}
else {
return false;
emission_sampling = emission_sampling_method;
}
return true;
}
void Shader::set_graph(ShaderGraph *graph_)
@@ -305,7 +409,7 @@ void Shader::tag_update(Scene *scene)
/* if the shader previously was emissive, update light distribution,
* if the new shader is emissive, a light manager update tag will be
* done in the shader manager device update. */
if (use_mis && has_surface_emission)
if (emission_sampling != EMISSION_SAMPLING_NONE)
scene->light_manager->tag_update(scene, LightManager::SHADER_MODIFIED);
/* Special handle of background MIS light for now: for some reason it
@@ -491,9 +595,17 @@ void ShaderManager::device_update_common(Device * /*device*/,
foreach (Shader *shader, scene->shaders) {
uint flag = 0;
if (shader->get_use_mis())
flag |= SD_USE_MIS;
if (shader->has_surface_emission)
if (shader->emission_sampling == EMISSION_SAMPLING_FRONT) {
flag |= SD_MIS_FRONT;
}
else if (shader->emission_sampling == EMISSION_SAMPLING_BACK) {
flag |= SD_MIS_BACK;
}
else if (shader->emission_sampling == EMISSION_SAMPLING_FRONT_BACK) {
flag |= SD_MIS_FRONT | SD_MIS_BACK;
}
if (!is_zero(shader->emission_estimate))
flag |= SD_HAS_EMISSION;
if (shader->has_surface_transparent && shader->get_use_transparent_shadow())
flag |= SD_HAS_TRANSPARENT_SHADOW;
@@ -531,8 +643,7 @@ void ShaderManager::device_update_common(Device * /*device*/,
flag |= SD_HAS_DISPLACEMENT;
/* constant emission check */
float3 constant_emission = zero_float3();
if (shader->is_constant_emission(&constant_emission))
if (shader->emission_is_constant)
flag |= SD_HAS_CONSTANT_EMISSION;
uint32_t cryptomatte_id = util_murmur_hash3(shader->name.c_str(), shader->name.length(), 0);
@@ -540,9 +651,9 @@ void ShaderManager::device_update_common(Device * /*device*/,
/* regular shader */
kshader->flags = flag;
kshader->pass_id = shader->get_pass_id();
kshader->constant_emission[0] = constant_emission.x;
kshader->constant_emission[1] = constant_emission.y;
kshader->constant_emission[2] = constant_emission.z;
kshader->constant_emission[0] = shader->emission_estimate.x;
kshader->constant_emission[1] = shader->emission_estimate.y;
kshader->constant_emission[2] = shader->emission_estimate.z;
kshader->cryptomatte_id = util_hash_to_float(cryptomatte_id);
kshader++;
@@ -627,8 +738,8 @@ void ShaderManager::add_default(Scene *scene)
shader->set_graph(graph);
scene->default_volume = shader;
shader->tag_update(scene);
/* No default reference for the volume to avoid compiling volume kernels if there are no actual
* volumes in the scene */
/* No default reference for the volume to avoid compiling volume kernels if there are no
* actual volumes in the scene */
}
/* default light */

View File

@@ -34,6 +34,7 @@ struct float3;
enum ShadingSystem { SHADINGSYSTEM_OSL, SHADINGSYSTEM_SVM };
/* Keep those in sync with the python-defined enum. */
enum VolumeSampling {
VOLUME_SAMPLING_DISTANCE = 0,
VOLUME_SAMPLING_EQUIANGULAR = 1,
@@ -73,7 +74,7 @@ class Shader : public Node {
NODE_SOCKET_API(int, pass_id)
/* sampling */
NODE_SOCKET_API(bool, use_mis)
NODE_SOCKET_API(EmissionSampling, emission_sampling_method)
NODE_SOCKET_API(bool, use_transparent_shadow)
NODE_SOCKET_API(bool, heterogeneous_volume)
NODE_SOCKET_API(VolumeSampling, volume_sampling_method)
@@ -101,7 +102,6 @@ class Shader : public Node {
/* information about shader after compiling */
bool has_surface;
bool has_surface_emission;
bool has_surface_transparent;
bool has_surface_raytrace;
bool has_volume;
@@ -114,6 +114,10 @@ class Shader : public Node {
bool has_volume_attribute_dependency;
bool has_integrator_dependency;
float3 emission_estimate;
EmissionSampling emission_sampling;
bool emission_is_constant;
/* requested mesh attributes */
AttributeRequestSet attributes;
@@ -131,11 +135,12 @@ class Shader : public Node {
Shader();
~Shader();
/* Checks whether the shader consists of just a emission node with fixed inputs that's connected
* directly to the output.
* If yes, it sets the content of emission to the constant value (color * strength), which is
* then used for speeding up light evaluation. */
bool is_constant_emission(float3 *emission);
/* Estimate emission of this shader based on the shader graph. This works only in very simple
* cases. But it helps improve light importance sampling in common cases.
*
* If the emission is fully constant, returns true, so that shader evaluation can be skipped
* entirely for a light. */
void estimate_emission();
void set_graph(ShaderGraph *graph);
void tag_update(Scene *scene);

View File

@@ -74,15 +74,15 @@ class ShaderInput {
{
}
ustring name()
ustring name() const
{
return socket_type.ui_name;
}
int flags()
int flags() const
{
return socket_type.flags;
}
SocketType::Type type()
SocketType::Type type() const
{
return socket_type.type;
}
@@ -119,11 +119,11 @@ class ShaderOutput {
{
}
ustring name()
ustring name() const
{
return socket_type.ui_name;
}
SocketType::Type type()
SocketType::Type type() const
{
return socket_type.type;
}

View File

@@ -7211,6 +7211,7 @@ void SetNormalNode::compile(OSLCompiler &compiler)
OSLNode::OSLNode() : ShaderNode(new NodeType(NodeType::SHADER))
{
special_type = SHADER_SPECIAL_TYPE_OSL;
has_emission = false;
}
OSLNode::~OSLNode()
@@ -7257,12 +7258,12 @@ char *OSLNode::input_default_value()
return (char *)this + align_up(sizeof(OSLNode), 16) + inputs_size;
}
void OSLNode::add_input(ustring name, SocketType::Type socket_type)
void OSLNode::add_input(ustring name, SocketType::Type socket_type, const int flags)
{
char *memory = input_default_value();
size_t offset = memory - (char *)this;
const_cast<NodeType *>(type)->register_input(
name, name, socket_type, offset, memory, NULL, NULL, SocketType::LINKABLE);
name, name, socket_type, offset, memory, NULL, NULL, flags | SocketType::LINKABLE);
}
void OSLNode::add_output(ustring name, SocketType::Type socket_type)

View File

@@ -1525,11 +1525,16 @@ class OSLNode final : public ShaderNode {
ShaderNode *clone(ShaderGraph *graph) const;
char *input_default_value();
void add_input(ustring name, SocketType::Type type);
void add_input(ustring name, SocketType::Type type, const int flags = 0);
void add_output(ustring name, SocketType::Type type);
SHADER_NODE_NO_CLONE_CLASS(OSLNode)
bool has_surface_emission()
{
return has_emission;
}
/* Ideally we could better detect this, but we can't query this now. */
bool has_spatial_varying()
{
@@ -1551,6 +1556,7 @@ class OSLNode final : public ShaderNode {
string filepath;
string bytecode_hash;
bool has_emission;
};
class NormalMapNode : public ShaderNode {

View File

@@ -109,7 +109,7 @@ void SVMShaderManager::device_update_specific(Device *device,
Shader *shader = scene->shaders[i];
shader->clear_modified();
if (shader->get_use_mis() && shader->has_surface_emission) {
if (shader->emission_sampling != EMISSION_SAMPLING_NONE) {
scene->light_manager->tag_update(scene, LightManager::SHADER_COMPILED);
}
@@ -516,8 +516,6 @@ void SVMCompiler::generate_closure_node(ShaderNode *node, CompilerState *state)
mix_weight_offset = SVM_STACK_INVALID;
if (current_type == SHADER_TYPE_SURFACE) {
if (node->has_surface_emission())
current_shader->has_surface_emission = true;
if (node->has_surface_transparent())
current_shader->has_surface_transparent = true;
if (node->has_surface_bssrdf()) {
@@ -873,7 +871,6 @@ void SVMCompiler::compile(Shader *shader, array<int4> &svm_nodes, int index, Sum
current_shader = shader;
shader->has_surface = false;
shader->has_surface_emission = false;
shader->has_surface_transparent = false;
shader->has_surface_raytrace = false;
shader->has_surface_bssrdf = false;
@@ -928,6 +925,9 @@ void SVMCompiler::compile(Shader *shader, array<int4> &svm_nodes, int index, Sum
summary->peak_stack_usage = max_stack_use;
summary->num_svm_nodes = svm_nodes.size() - start_num_svm_nodes;
}
/* Estimate emission for MIS. */
shader->estimate_emission();
}
/* Compiler summary implementation. */

View File

@@ -85,7 +85,7 @@ class DisplayDriver {
int buffer_height = 0;
/* OpenGL pixel buffer object. */
int opengl_pbo_id = 0;
int64_t opengl_pbo_id = 0;
/* Clear the entire buffer before doing partial write to it. */
bool need_clear = false;

View File

@@ -378,6 +378,18 @@ RenderWork Session::run_update_for_next_iteration()
const int width = max(1, buffer_params_.full_width / resolution);
const int height = max(1, buffer_params_.full_height / resolution);
{
/* Load render kernels, before device update where we upload data to the GPU.
* Do it outside of the scene mutex since the heavy part of the loading (i.e. kernel
* compilation) does not depend on the scene and some other functionality (like display
* driver) might be waiting on the scene mutex to synchronize display pass.
*
* The scene will lock itself for the short period if it needs to update kernel features. */
scene_lock.unlock();
scene->load_kernels(progress);
scene_lock.lock();
}
if (update_scene(width, height)) {
profiler.reset(scene->shaders.size(), scene->objects.size());
}

View File

@@ -342,8 +342,8 @@ void TileManager::reset_scheduling(const BufferParams &params, int2 tile_size)
tile_size_ = tile_size;
tile_state_.num_tiles_x = divide_up(params.width, tile_size_.x);
tile_state_.num_tiles_y = divide_up(params.height, tile_size_.y);
tile_state_.num_tiles_x = tile_size_.x ? divide_up(params.width, tile_size_.x) : 0;
tile_state_.num_tiles_y = tile_size_.y ? divide_up(params.height, tile_size_.y) : 0;
tile_state_.num_tiles = tile_state_.num_tiles_x * tile_state_.num_tiles_y;
tile_state_.next_tile_index = 0;

View File

@@ -74,7 +74,6 @@ set(SRC_HEADERS
md5.h
murmurhash.h
openimagedenoise.h
opengl.h
openvdb.h
optimization.h
param.h

View File

@@ -796,11 +796,11 @@ ccl_device float bits_to_01(uint bits)
ccl_device_inline uint popcount(uint x)
{
/* TODO(Stefan): pop-count intrinsic for Windows with fallback for older CPUs. */
uint i = x & 0xaaaaaaaa;
uint i = x;
i = i - ((i >> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
i = (((i + (i >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24;
return i & 1;
return i;
}
# endif
#elif defined(__KERNEL_ONEAPI__)

View File

@@ -58,7 +58,7 @@ ccl_device_inline float4 madd4(const float4 a, const float4 b, const float4 c)
ccl_device_inline int fast_rint(float x)
{
/* used by sin/cos/tan range reduction. */
#ifdef __KERNEL_SSE4__
#ifdef __KERNEL_SSE41__
/* Single `roundps` instruction on SSE4.1+ (for gcc/clang at least). */
return float_to_int(rintf(x));
#else

View File

@@ -257,8 +257,8 @@ ccl_device bool ray_quad_intersect(float3 ray_P,
float ray_tmin,
float ray_tmax,
float3 quad_P,
float3 quad_u,
float3 quad_v,
float3 inv_quad_u,
float3 inv_quad_v,
float3 quad_n,
ccl_private float3 *isect_P,
ccl_private float *isect_t,
@@ -273,11 +273,11 @@ ccl_device bool ray_quad_intersect(float3 ray_P,
}
const float3 hit = ray_P + t * ray_D;
const float3 inplane = hit - quad_P;
const float u = dot(inplane, quad_u) / dot(quad_u, quad_u);
const float u = dot(inplane, inv_quad_u);
if (u < -0.5f || u > 0.5f) {
return false;
}
const float v = dot(inplane, quad_v) / dot(quad_v, quad_v);
const float v = dot(inplane, inv_quad_v);
if (v < -0.5f || v > 0.5f) {
return false;
}

View File

@@ -1,12 +0,0 @@
/* SPDX-License-Identifier: Apache-2.0
* Copyright 2011-2022 Blender Foundation */
#ifndef __UTIL_OPENGL_H__
#define __UTIL_OPENGL_H__
/* OpenGL header includes, used everywhere we use OpenGL, to deal with
* platform differences in one central place. */
#include <epoxy/gl.h>
#endif /* __UTIL_OPENGL_H__ */

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