UI: Asset Shelf (Experimental Feature) #104831

Closed
Julian Eisel wants to merge 399 commits from asset-shelf into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
59 changed files with 3081 additions and 1597 deletions
Showing only changes of commit 2be8104d8d - Show all commits

View File

@ -528,10 +528,10 @@ set(XR_OPENXR_SDK_HASH a2623ebab3d0b340bc16311b14f02075)
set(XR_OPENXR_SDK_HASH_TYPE MD5)
set(XR_OPENXR_SDK_FILE OpenXR-SDK-${XR_OPENXR_SDK_VERSION}.tar.gz)
set(WL_PROTOCOLS_VERSION 1.21)
set(WL_PROTOCOLS_VERSION 1.31)
set(WL_PROTOCOLS_FILE wayland-protocols-${WL_PROTOCOLS_VERSION}.tar.gz)
set(WL_PROTOCOLS_URI https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/${WL_PROTOCOLS_VERSION}/${WL_PROTOCOLS_FILE})
set(WL_PROTOCOLS_HASH af5ca07e13517cdbab33504492cef54a)
set(WL_PROTOCOLS_HASH a28ff59a56e2ebb746048b6ef8d931d6)
set(WL_PROTOCOLS_HASH_TYPE MD5)
set(WAYLAND_VERSION 1.21.0)

View File

@ -245,6 +245,7 @@ if(WITH_BOOST)
if(WITH_USD AND USD_PYTHON_SUPPORT)
list(APPEND _boost_FIND_COMPONENTS python${PYTHON_VERSION_NO_DOTS})
endif()
set(Boost_NO_WARN_NEW_VERSIONS ON)
find_package(Boost COMPONENTS ${_boost_FIND_COMPONENTS})
# Boost Python is separate to avoid linking Python into tests that don't need it.

View File

@ -394,6 +394,7 @@ if(WITH_BOOST)
list(APPEND __boost_packages python${PYTHON_VERSION_NO_DOTS})
endif()
list(APPEND __boost_packages system)
set(Boost_NO_WARN_NEW_VERSIONS ON)
find_package(Boost 1.48 COMPONENTS ${__boost_packages})
if(NOT Boost_FOUND)
# try to find non-multithreaded if -mt not found, this flag

View File

@ -1,7 +1,7 @@
Project: fast_float
URL: https://github.com/fastfloat/fast_float
License: MIT
Upstream version: 3.4.0 (b7f9d6c)
Upstream version: 4.0.0 (fbd5bd7, 2023 Mar 31)
Local modifications:
- Took only the fast_float.h header and the license/readme files

View File

@ -1,12 +1,5 @@
## fast_float number parsing library: 4x faster than strtod
![Ubuntu 20.04 CI (GCC 9)](https://github.com/lemire/fast_float/workflows/Ubuntu%2020.04%20CI%20(GCC%209)/badge.svg)
![Ubuntu 18.04 CI (GCC 7)](https://github.com/lemire/fast_float/workflows/Ubuntu%2018.04%20CI%20(GCC%207)/badge.svg)
![Alpine Linux](https://github.com/lemire/fast_float/workflows/Alpine%20Linux/badge.svg)
![MSYS2-CI](https://github.com/lemire/fast_float/workflows/MSYS2-CI/badge.svg)
![VS16-CLANG-CI](https://github.com/lemire/fast_float/workflows/VS16-CLANG-CI/badge.svg)
[![VS16-CI](https://github.com/fastfloat/fast_float/actions/workflows/vs16-ci.yml/badge.svg)](https://github.com/fastfloat/fast_float/actions/workflows/vs16-ci.yml)
The fast_float library provides fast header-only implementations for the C++ from_chars
functions for `float` and `double` types. These functions convert ASCII strings representing
decimal values (e.g., `1.3e10`) into binary types. We provide exact rounding (including
@ -28,8 +21,8 @@ struct from_chars_result {
```
It parses the character sequence [first,last) for a number. It parses floating-point numbers expecting
a locale-independent format equivalent to the C++17 from_chars function.
The resulting floating-point value is the closest floating-point values (using either float or double),
a locale-independent format equivalent to the C++17 from_chars function.
The resulting floating-point value is the closest floating-point values (using either float or double),
using the "round to even" convention for values that would otherwise fall right in-between two values.
That is, we provide exact parsing according to the IEEE standard.
@ -47,7 +40,7 @@ Example:
``` C++
#include "fast_float/fast_float.h"
#include <iostream>
int main() {
const std::string input = "3.1416 xyz ";
double result;
@ -60,39 +53,60 @@ int main() {
Like the C++17 standard, the `fast_float::from_chars` functions take an optional last argument of
the type `fast_float::chars_format`. It is a bitset value: we check whether
the type `fast_float::chars_format`. It is a bitset value: we check whether
`fmt & fast_float::chars_format::fixed` and `fmt & fast_float::chars_format::scientific` are set
to determine whether we allow the fixed point and scientific notation respectively.
The default is `fast_float::chars_format::general` which allows both `fixed` and `scientific`.
The library seeks to follow the C++17 (see [20.19.3](http://eel.is/c++draft/charconv.from.chars).(7.1)) specification.
The library seeks to follow the C++17 (see [20.19.3](http://eel.is/c++draft/charconv.from.chars).(7.1)) specification.
* The `from_chars` function does not skip leading white-space characters.
* [A leading `+` sign](https://en.cppreference.com/w/cpp/utility/from_chars) is forbidden.
* It is generally impossible to represent a decimal value exactly as binary floating-point number (`float` and `double` types). We seek the nearest value. We round to an even mantissa when we are in-between two binary floating-point numbers.
* It is generally impossible to represent a decimal value exactly as binary floating-point number (`float` and `double` types). We seek the nearest value. We round to an even mantissa when we are in-between two binary floating-point numbers.
Furthermore, we have the following restrictions:
* We only support `float` and `double` types at this time.
* We only support the decimal format: we do not support hexadecimal strings.
* For values that are either very large or very small (e.g., `1e9999`), we represent it using the infinity or negative infinity value.
* For values that are either very large or very small (e.g., `1e9999`), we represent it using the infinity or negative infinity value and the returned `ec` is set to `std::errc::result_out_of_range`.
We support Visual Studio, macOS, Linux, freeBSD. We support big and little endian. We support 32-bit and 64-bit systems.
We assume that the rounding mode is set to nearest (`std::fegetround() == FE_TONEAREST`).
## C++20: compile-time evaluation (constexpr)
In C++20, you may use `fast_float::from_chars` to parse strings
at compile-time, as in the following example:
```C++
// consteval forces compile-time evaluation of the function in C++20.
consteval double parse(std::string_view input) {
double result;
auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result);
if(answer.ec != std::errc()) { return -1.0; }
return result;
}
// This function should compile to a function which
// merely returns 3.1415.
constexpr double constexptest() {
return parse("3.1415 input");
}
```
## Using commas as decimal separator
The C++ standard stipulate that `from_chars` has to be locale-independent. In
particular, the decimal separator has to be the period (`.`). However,
some users still want to use the `fast_float` library with in a locale-dependent
particular, the decimal separator has to be the period (`.`). However,
some users still want to use the `fast_float` library with in a locale-dependent
manner. Using a separate function called `from_chars_advanced`, we allow the users
to pass a `parse_options` instance which contains a custom decimal separator (e.g.,
to pass a `parse_options` instance which contains a custom decimal separator (e.g.,
the comma). You may use it as follows.
```C++
#include "fast_float/fast_float.h"
#include <iostream>
int main() {
const std::string input = "3,1416 xyz ";
double result;
@ -104,25 +118,62 @@ int main() {
}
```
You can parse delimited numbers:
```C++
const std::string input = "234532.3426362,7869234.9823,324562.645";
double result;
auto answer = fast_float::from_chars(input.data(), input.data()+input.size(), result);
if(answer.ec != std::errc()) {
// check error
}
// we have result == 234532.3426362.
if(answer.ptr[0] != ',') {
// unexpected delimiter
}
answer = fast_float::from_chars(answer.ptr + 1, input.data()+input.size(), result);
if(answer.ec != std::errc()) {
// check error
}
// we have result == 7869234.9823.
if(answer.ptr[0] != ',') {
// unexpected delimiter
}
answer = fast_float::from_chars(answer.ptr + 1, input.data()+input.size(), result);
if(answer.ec != std::errc()) {
// check error
}
// we have result == 324562.645.
```
## Reference
- Daniel Lemire, [Number Parsing at a Gigabyte per Second](https://arxiv.org/abs/2101.11408), Software: Pratice and Experience 51 (8), 2021.
## Relation With Other Work
The fast_float library is part of:
- GCC (as of version 12): the `from_chars` function in GCC relies on fast_float.
- [WebKit](https://github.com/WebKit/WebKit), the engine behind Safari (Apple's web browser)
The fastfloat algorithm is part of the [LLVM standard libraries](https://github.com/llvm/llvm-project/commit/87c016078ad72c46505461e4ff8bfa04819fe7ba).
There is a [derived implementation part of AdaCore](https://github.com/AdaCore/VSS).
The fast_float library provides a performance similar to that of the [fast_double_parser](https://github.com/lemire/fast_double_parser) library but using an updated algorithm reworked from the ground up, and while offering an API more in line with the expectations of C++ programmers. The fast_double_parser library is part of the [Microsoft LightGBM machine-learning framework](https://github.com/microsoft/LightGBM).
## References
- Daniel Lemire, [Number Parsing at a Gigabyte per Second](https://arxiv.org/abs/2101.11408), Software: Practice and Experience 51 (8), 2021.
- Noble Mushtak, Daniel Lemire, [Fast Number Parsing Without Fallback](https://arxiv.org/abs/2212.06644), Software: Practice and Experience (to appear)
## Other programming languages
- [There is an R binding](https://github.com/eddelbuettel/rcppfastfloat) called `rcppfastfloat`.
- [There is a Rust port of the fast_float library](https://github.com/aldanor/fast-float-rust/) called `fast-float-rust`.
- [There is a Java port of the fast_float library](https://github.com/wrandelshofer/FastDoubleParser) called `FastDoubleParser`.
- [There is a Java port of the fast_float library](https://github.com/wrandelshofer/FastDoubleParser) called `FastDoubleParser`. It used for important systems such as [Jackson](https://github.com/FasterXML/jackson-core).
- [There is a C# port of the fast_float library](https://github.com/CarlVerret/csFastFloat) called `csFastFloat`.
## Relation With Other Work
The fastfloat algorithm is part of the [LLVM standard libraries](https://github.com/llvm/llvm-project/commit/87c016078ad72c46505461e4ff8bfa04819fe7ba).
The fast_float library provides a performance similar to that of the [fast_double_parser](https://github.com/lemire/fast_double_parser) library but using an updated algorithm reworked from the ground up, and while offering an API more in line with the expectations of C++ programmers. The fast_double_parser library is part of the [Microsoft LightGBM machine-learning framework](https://github.com/microsoft/LightGBM).
## Users
The fast_float library is used by [Apache Arrow](https://github.com/apache/arrow/pull/8494) where it multiplied the number parsing speed by two or three times. It is also used by [Yandex ClickHouse](https://github.com/ClickHouse/ClickHouse) and by [Google Jsonnet](https://github.com/google/jsonnet).
@ -135,14 +186,14 @@ It can parse random floating-point numbers at a speed of 1 GB/s on some systems.
<img src="http://lemire.me/blog/wp-content/uploads/2020/11/fastfloat_speed.png" width="400">
```
$ ./build/benchmarks/benchmark
$ ./build/benchmarks/benchmark
# parsing random integers in the range [0,1)
volume = 2.09808 MB
netlib : 271.18 MB/s (+/- 1.2 %) 12.93 Mfloat/s
doubleconversion : 225.35 MB/s (+/- 1.2 %) 10.74 Mfloat/s
strtod : 190.94 MB/s (+/- 1.6 %) 9.10 Mfloat/s
abseil : 430.45 MB/s (+/- 2.2 %) 20.52 Mfloat/s
fastfloat : 1042.38 MB/s (+/- 9.9 %) 49.68 Mfloat/s
volume = 2.09808 MB
netlib : 271.18 MB/s (+/- 1.2 %) 12.93 Mfloat/s
doubleconversion : 225.35 MB/s (+/- 1.2 %) 10.74 Mfloat/s
strtod : 190.94 MB/s (+/- 1.6 %) 9.10 Mfloat/s
abseil : 430.45 MB/s (+/- 2.2 %) 20.52 Mfloat/s
fastfloat : 1042.38 MB/s (+/- 9.9 %) 49.68 Mfloat/s
```
See https://github.com/lemire/simple_fastfloat_benchmark for our benchmarking code.
@ -183,23 +234,23 @@ You should change the `GIT_TAG` line so that you recover the version you wish to
## Using as single header
The script `script/amalgamate.py` may be used to generate a single header
The script `script/amalgamate.py` may be used to generate a single header
version of the library if so desired.
Just run the script from the root directory of this repository.
Just run the script from the root directory of this repository.
You can customize the license type and output file if desired as described in
the command line help.
You may directly download automatically generated single-header files:
https://github.com/fastfloat/fast_float/releases/download/v1.1.2/fast_float.h
https://github.com/fastfloat/fast_float/releases/download/v3.4.0/fast_float.h
## Credit
Though this work is inspired by many different people, this work benefited especially from exchanges with
Michael Eisel, who motivated the original research with his key insights, and with Nigel Tao who provided
Though this work is inspired by many different people, this work benefited especially from exchanges with
Michael Eisel, who motivated the original research with his key insights, and with Nigel Tao who provided
invaluable feedback. Rémy Oudompheng first implemented a fast path we use in the case of long digits.
The library includes code adapted from Google Wuffs (written by Nigel Tao) which was originally published
The library includes code adapted from Google Wuffs (written by Nigel Tao) which was originally published
under the Apache 2.0 license.
## License

File diff suppressed because it is too large Load Diff

View File

@ -378,6 +378,14 @@ elseif(WITH_GHOST_X11 OR WITH_GHOST_WAYLAND)
unset(_name)
endmacro()
macro(generate_protocol_bindings_when_found PROT_DEF FOUND_VAR)
if(EXISTS ${PROT_DEF})
generate_protocol_bindings(${PROT_DEF})
set(${FOUND_VAR} TRUE)
else()
set(${FOUND_VAR} FALSE)
endif()
endmacro()
list(APPEND INC_SYS
${INC_DST}
@ -402,6 +410,17 @@ elseif(WITH_GHOST_X11 OR WITH_GHOST_WAYLAND)
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/staging/xdg-activation/xdg-activation-v1.xml"
)
# Fractional scale.
generate_protocol_bindings_when_found(
"${WAYLAND_PROTOCOLS_DIR}/staging/fractional-scale/fractional-scale-v1.xml"
_has_fractional_scale
)
if(_has_fractional_scale)
# Viewport (only required when fractional scale is in use).
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/stable/viewporter/viewporter.xml"
)
endif()
# Pointer-constraints.
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml"
@ -427,6 +446,11 @@ elseif(WITH_GHOST_X11 OR WITH_GHOST_WAYLAND)
add_definitions(-DWITH_GHOST_WAYLAND)
if(_has_fractional_scale)
add_definitions(-DWITH_GHOST_WAYLAND_FRACTIONAL_SCALE)
endif()
unset(_has_fractional_scale)
if(NOT WITH_GHOST_WAYLAND_APP_ID STREQUAL "")
add_definitions(-DWITH_GHOST_WAYLAND_APP_ID=${WITH_GHOST_WAYLAND_APP_ID})
endif()

View File

@ -61,6 +61,10 @@
#include <tablet-unstable-v2-client-protocol.h>
#include <xdg-activation-v1-client-protocol.h>
#include <xdg-output-unstable-v1-client-protocol.h>
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
# include <fractional-scale-v1-client-protocol.h>
# include <viewporter-client-protocol.h>
#endif
/* Decorations `xdg_decor`. */
#include <xdg-decoration-unstable-v1-client-protocol.h>
@ -944,6 +948,10 @@ struct GWL_Display {
struct zwp_relative_pointer_manager_v1 *wp_relative_pointer_manager = nullptr;
struct zwp_primary_selection_device_manager_v1 *wp_primary_selection_device_manager = nullptr;
struct xdg_activation_v1 *xdg_activation_manager = nullptr;
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
struct wp_fractional_scale_manager_v1 *wp_fractional_scale_manager = nullptr;
struct wp_viewporter *wp_viewporter = nullptr;
#endif
struct zwp_pointer_constraints_v1 *wp_pointer_constraints = nullptr;
struct zwp_pointer_gestures_v1 *wp_pointer_gestures = nullptr;
@ -3006,19 +3014,6 @@ static void gesture_pinch_handle_begin(void *data,
if (wl_surface *wl_surface_focus = seat->pointer.wl_surface_window) {
win = ghost_wl_surface_user_data(wl_surface_focus);
}
/* NOTE(@ideasman42): Blender's use of track-pad coordinates is inconsistent and needs work.
* This isn't specific to WAYLAND, in practice they tend to work well enough in most cases.
* Some operators scale by the UI scale, some don't.
* Even this window scale is not correct because it doesn't account for:
* 1) Fractional window scale.
* 2) Blender's UI scale preference (which GHOST doesn't know about).
*
* If support for this were all that was needed it could be handled in GHOST,
* however as the operators are not even using coordinates compatible with each other,
* it would be better to resolve this by passing rotation & zoom levels directly,
* instead of attempting to handle them as cursor coordinates.
*/
const wl_fixed_t win_scale = win ? win->scale() : 1;
/* NOTE(@ideasman42): Scale factors match Blender's operators & default preferences.
* For these values to work correctly, operator logic will need to be changed not to scale input
@ -3033,10 +3028,30 @@ static void gesture_pinch_handle_begin(void *data,
seat->pointer_gesture_pinch.scale.value = wl_fixed_from_int(1);
/* The value 300 matches a value used in clip & image zoom operators.
* It seems OK for the 3D view too. */
seat->pointer_gesture_pinch.scale.factor = 300 * win_scale;
seat->pointer_gesture_pinch.scale.factor = 300;
/* The value 5 is used on macOS and roughly maps 1:1 with turntable rotation,
* although preferences can scale the sensitivity (which would be skipped ideally). */
seat->pointer_gesture_pinch.rotation.factor = 5 * win_scale;
seat->pointer_gesture_pinch.rotation.factor = 5;
if (win) {
/* NOTE(@ideasman42): Blender's use of track-pad coordinates is inconsistent and needs work.
* This isn't specific to WAYLAND, in practice they tend to work well enough in most cases.
* Some operators scale by the UI scale, some don't.
* Even this window scale is not correct because it doesn't account for:
* 1) Fractional window scale.
* 2) Blender's UI scale preference (which GHOST doesn't know about).
*
* If support for this were all that was needed it could be handled in GHOST,
* however as the operators are not even using coordinates compatible with each other,
* it would be better to resolve this by passing rotation & zoom levels directly,
* instead of attempting to handle them as cursor coordinates.
*/
const struct GWL_WindowScaleParams &scale_params = win->scale_params();
seat->pointer_gesture_pinch.scale.factor = gwl_window_scale_int_to(
scale_params, seat->pointer_gesture_pinch.scale.factor);
seat->pointer_gesture_pinch.rotation.factor = gwl_window_scale_int_to(
scale_params, seat->pointer_gesture_pinch.rotation.factor);
}
}
static void gesture_pinch_handle_update(void *data,
@ -5167,6 +5182,47 @@ static void gwl_registry_xdg_activation_remove(GWL_Display *display,
*value_p = nullptr;
}
/* #GWL_Display.wp_fractional_scale_manger */
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
static void gwl_registry_wp_fractional_scale_manager_add(GWL_Display *display,
const GWL_RegisteryAdd_Params *params)
{
display->wp_fractional_scale_manager = static_cast<wp_fractional_scale_manager_v1 *>(
wl_registry_bind(
display->wl_registry, params->name, &wp_fractional_scale_manager_v1_interface, 1));
gwl_registry_entry_add(display, params, nullptr);
}
static void gwl_registry_wp_fractional_scale_manager_remove(GWL_Display *display,
void * /*user_data*/,
const bool /*on_exit*/)
{
struct wp_fractional_scale_manager_v1 **value_p = &display->wp_fractional_scale_manager;
wp_fractional_scale_manager_v1_destroy(*value_p);
*value_p = nullptr;
}
/* #GWL_Display.wl_viewport */
static void gwl_registry_wp_viewporter_add(GWL_Display *display,
const GWL_RegisteryAdd_Params *params)
{
display->wp_viewporter = static_cast<wp_viewporter *>(
wl_registry_bind(display->wl_registry, params->name, &wp_viewporter_interface, 1));
gwl_registry_entry_add(display, params, nullptr);
}
static void gwl_registry_wp_viewporter_remove(GWL_Display *display,
void * /*user_data*/,
const bool /*on_exit*/)
{
struct wp_viewporter **value_p = &display->wp_viewporter;
wp_viewporter_destroy(*value_p);
*value_p = nullptr;
}
#endif /* WITH_GHOST_WAYLAND_FRACTIONAL_SCALE */
/* #GWL_Display.wp_primary_selection_device_manager */
static void gwl_registry_wp_primary_selection_device_manager_add(
@ -5276,6 +5332,20 @@ static const GWL_RegistryHandler gwl_registry_handlers[] = {
/*update_fn*/ nullptr,
/*remove_fn*/ gwl_registry_xdg_activation_remove,
},
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
{
/*interface_p*/ &wp_fractional_scale_manager_v1_interface.name,
/*add_fn*/ gwl_registry_wp_fractional_scale_manager_add,
/*update_fn*/ nullptr,
/*remove_fn*/ gwl_registry_wp_fractional_scale_manager_remove,
},
{
/*interface_p*/ &wp_viewporter_interface.name,
/*add_fn*/ gwl_registry_wp_viewporter_add,
/*update_fn*/ nullptr,
/*remove_fn*/ gwl_registry_wp_viewporter_remove,
},
#endif /* WITH_GHOST_WAYLAND_FRACTIONAL_SCALE */
/* Display outputs. */
{
/*interface_p*/ &wl_output_interface.name,
@ -6840,6 +6910,17 @@ struct xdg_activation_v1 *GHOST_SystemWayland::xdg_activation_manager()
return display_->xdg_activation_manager;
}
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
struct wp_fractional_scale_manager_v1 *GHOST_SystemWayland::wp_fractional_scale_manager()
{
return display_->wp_fractional_scale_manager;
}
struct wp_viewporter *GHOST_SystemWayland::wp_viewporter()
{
return display_->wp_viewporter;
}
#endif /* WITH_GHOST_WAYLAND_FRACTIONAL_SCALE */
struct zwp_pointer_gestures_v1 *GHOST_SystemWayland::wp_pointer_gestures()
{
return display_->wp_pointer_gestures;
@ -7055,7 +7136,7 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod
const GHOST_Rect *wrap_bounds,
const GHOST_TAxisFlag wrap_axis,
wl_surface *wl_surface,
const int scale)
const struct GWL_WindowScaleParams &scale_params)
{
/* Caller must lock `server_mutex`. */
@ -7115,10 +7196,14 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod
GHOST_Rect bounds_scale;
bounds_scale.m_l = wl_fixed_from_int(wrap_bounds->m_l) / scale;
bounds_scale.m_t = wl_fixed_from_int(wrap_bounds->m_t) / scale;
bounds_scale.m_r = wl_fixed_from_int(wrap_bounds->m_r) / scale;
bounds_scale.m_b = wl_fixed_from_int(wrap_bounds->m_b) / scale;
bounds_scale.m_l = gwl_window_scale_wl_fixed_from(scale_params,
wl_fixed_from_int(wrap_bounds->m_l));
bounds_scale.m_t = gwl_window_scale_wl_fixed_from(scale_params,
wl_fixed_from_int(wrap_bounds->m_t));
bounds_scale.m_r = gwl_window_scale_wl_fixed_from(scale_params,
wl_fixed_from_int(wrap_bounds->m_r));
bounds_scale.m_b = gwl_window_scale_wl_fixed_from(scale_params,
wl_fixed_from_int(wrap_bounds->m_b));
bounds_scale.wrapPoint(UNPACK2(xy_next), 0, wrap_axis);
@ -7138,8 +7223,8 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod
if ((init_grab_xy[0] != seat->grab_lock_xy[0]) ||
(init_grab_xy[1] != seat->grab_lock_xy[1])) {
const wl_fixed_t xy_next[2] = {
wl_fixed_from_int(init_grab_xy[0]) / scale,
wl_fixed_from_int(init_grab_xy[1]) / scale,
gwl_window_scale_wl_fixed_from(scale_params, wl_fixed_from_int(init_grab_xy[0])),
gwl_window_scale_wl_fixed_from(scale_params, wl_fixed_from_int(init_grab_xy[1])),
};
zwp_locked_pointer_v1_set_cursor_position_hint(seat->wp_locked_pointer,
UNPACK2(xy_next));
@ -7164,13 +7249,13 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod
#endif
if (xy_motion_create_event) {
seat->system->pushEvent_maybe_pending(
new GHOST_EventCursor(seat->system->getMilliSeconds(),
GHOST_kEventCursorMove,
ghost_wl_surface_user_data(wl_surface),
wl_fixed_to_int(scale * xy_motion[0]),
wl_fixed_to_int(scale * xy_motion[1]),
GHOST_TABLET_DATA_NONE));
seat->system->pushEvent_maybe_pending(new GHOST_EventCursor(
seat->system->getMilliSeconds(),
GHOST_kEventCursorMove,
ghost_wl_surface_user_data(wl_surface),
wl_fixed_to_int(gwl_window_scale_wl_fixed_to(scale_params, xy_motion[0])),
wl_fixed_to_int(gwl_window_scale_wl_fixed_to(scale_params, xy_motion[1])),
GHOST_TABLET_DATA_NONE));
}
zwp_locked_pointer_v1_destroy(seat->wp_locked_pointer);
@ -7206,8 +7291,10 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod
if (mode == GHOST_kGrabHide) {
/* Set the initial position to detect any changes when un-grabbing,
* otherwise the unlocked cursor defaults to un-locking in-place. */
init_grab_xy[0] = wl_fixed_to_int(scale * seat->pointer.xy[0]);
init_grab_xy[1] = wl_fixed_to_int(scale * seat->pointer.xy[1]);
init_grab_xy[0] = wl_fixed_to_int(
gwl_window_scale_wl_fixed_to(scale_params, seat->pointer.xy[0]));
init_grab_xy[1] = wl_fixed_to_int(
gwl_window_scale_wl_fixed_to(scale_params, seat->pointer.xy[1]));
seat->grab_lock_xy[0] = init_grab_xy[0];
seat->grab_lock_xy[1] = init_grab_xy[1];
}

View File

@ -47,6 +47,26 @@ void ghost_wl_surface_tag_cursor_pointer(struct wl_surface *surface);
bool ghost_wl_surface_own_cursor_tablet(const struct wl_surface *surface);
void ghost_wl_surface_tag_cursor_tablet(struct wl_surface *surface);
/* Scaling to: translates from WAYLAND into GHOST (viewport local) coordinates.
* Scaling from: performs the reverse translation.
*
* Scaling "to" is used to map WAYLAND location cursor coordinates to GHOST coordinates.
* Scaling "from" is used to clamp cursor coordinates in WAYLAND local coordinates. */
struct GWL_WindowScaleParams;
wl_fixed_t gwl_window_scale_wl_fixed_to(const GWL_WindowScaleParams &scale_params,
wl_fixed_t value);
wl_fixed_t gwl_window_scale_wl_fixed_from(const GWL_WindowScaleParams &scale_params,
wl_fixed_t value);
/* Avoid this where possible as scaling integers often incurs rounding errors.
* Scale #wl_fixed_t where possible.
*
* In general scale by large values where this is less likely to be a problem. */
int gwl_window_scale_int_to(const GWL_WindowScaleParams &scale_params, int value);
int gwl_window_scale_int_from(const GWL_WindowScaleParams &scale_params, int value);
#ifdef WITH_GHOST_WAYLAND_DYNLOAD
/**
* Return true when all required WAYLAND libraries are present,
@ -181,6 +201,10 @@ class GHOST_SystemWayland : public GHOST_System {
struct zwp_primary_selection_device_manager_v1 *wp_primary_selection_manager();
struct xdg_activation_v1 *xdg_activation_manager();
struct zwp_pointer_gestures_v1 *wp_pointer_gestures();
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
struct wp_fractional_scale_manager_v1 *wp_fractional_scale_manager();
struct wp_viewporter *wp_viewporter();
#endif
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
libdecor *libdecor_context();
@ -234,7 +258,7 @@ class GHOST_SystemWayland : public GHOST_System {
const GHOST_Rect *wrap_bounds,
GHOST_TAxisFlag wrap_axis,
wl_surface *wl_surface,
int scale);
const struct GWL_WindowScaleParams &scale_params);
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
static bool use_libdecor_runtime();

View File

@ -38,6 +38,11 @@
#include <xdg-activation-v1-client-protocol.h>
#include <xdg-decoration-unstable-v1-client-protocol.h>
#include <xdg-shell-client-protocol.h>
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
# include <fractional-scale-v1-client-protocol.h>
# include <viewporter-client-protocol.h>
# define FRACTIONAL_DENOMINATOR 120
#endif
#include <atomic>
@ -83,6 +88,52 @@ static void gwl_xdg_decor_window_destroy(WGL_XDG_Decor_Window *decor)
delete decor;
}
/* -------------------------------------------------------------------- */
/** \name Window-Viewport/Wayland to/from Scale Conversion
* \{ */
struct GWL_WindowScaleParams {
bool is_fractional = false;
/**
* When fractional:
* Scale is multiplied by #FRACTIONAL_DENOMINATOR.
* Otherwise scale is an integer.
*/
wl_fixed_t scale = 0;
};
wl_fixed_t gwl_window_scale_wl_fixed_to(const GWL_WindowScaleParams &scale_params,
wl_fixed_t value)
{
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
if (scale_params.is_fractional) {
return (value * scale_params.scale) / FRACTIONAL_DENOMINATOR;
}
#endif
return value * scale_params.scale;
}
wl_fixed_t gwl_window_scale_wl_fixed_from(const GWL_WindowScaleParams &scale_params,
wl_fixed_t value)
{
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
if (scale_params.is_fractional) {
return (value * FRACTIONAL_DENOMINATOR) / scale_params.scale;
}
#endif
return value / scale_params.scale;
}
int gwl_window_scale_int_to(const GWL_WindowScaleParams &scale_params, int value)
{
return wl_fixed_to_int(gwl_window_scale_wl_fixed_to(scale_params, wl_fixed_from_int(value)));
}
int gwl_window_scale_int_from(const GWL_WindowScaleParams &scale_params, int value)
{
return wl_fixed_to_int(gwl_window_scale_wl_fixed_from(scale_params, wl_fixed_from_int(value)));
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Internal #GWL_Window
* \{ */
@ -111,10 +162,18 @@ enum eGWL_PendingWindowActions {
#endif /* USE_EVENT_BACKGROUND_THREAD */
struct GWL_WindowFrame {
/**
* The frame size (in GHOST window coordinates).
*
* These must be converted to WAYLADN relative coordinates when the window is scaled
* by Hi-DPI/fractional scaling.
*/
int32_t size[2] = {0, 0};
bool is_maximised = false;
bool is_fullscreen = false;
bool is_active = false;
/** Disable when the fractional scale is a whole number. */
int fractional_scale = 0;
};
struct GWL_Window {
@ -132,14 +191,20 @@ struct GWL_Window {
/** The scale value written to #wl_surface_set_buffer_scale. */
int scale = 0;
/**
* The fractional scale used to calculate the DPI.
* (always set, even when scaling is rounded to whole units).
* The scale value to be used in the case fractional scale is disable.
* In general this should only be used when changing states
* (when disabling fractional scale).
*/
wl_fixed_t scale_fractional = 0;
int scale_on_output = 0;
/** A temporary token used for the window to be notified of it's activation. */
struct xdg_activation_token_v1 *xdg_activation_token = nullptr;
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
struct wp_viewport *viewport = nullptr;
struct wp_fractional_scale_v1 *fractional_scale_handle = nullptr;
#endif
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
WGL_LibDecor_Window *libdecor = nullptr;
#endif
@ -166,6 +231,9 @@ struct GWL_Window {
bool is_dialog = false;
/** Currently only initialized on access (avoids allocations & allows to keep private). */
GWL_WindowScaleParams scale_params;
#ifdef USE_EVENT_BACKGROUND_THREAD
/**
* These pending actions can't be performed when WAYLAND handlers are running from a thread.
@ -310,6 +378,124 @@ static bool gwl_window_state_set(GWL_Window *win, const GHOST_TWindowState state
/** \} */
/* -------------------------------------------------------------------- */
/** \name Internal #GWL_Window Viewport Setup/Teardown
*
* A viewport is needed to implement fractional scale,
* as the outputs scale may change at runtime, support creating & clearing the viewport as needed.
* \{ */
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
/**
* Scale a value from a viewport value to Wayland windowing.
* Scale down or not at all.
*/
static int gwl_window_fractional_to_viewport(const GWL_WindowFrame &frame, int value)
{
GHOST_ASSERT(frame.fractional_scale != 0, "Not fractional or called before initiazlized!");
return (value * frame.fractional_scale) / FRACTIONAL_DENOMINATOR;
}
/**
* Scale a value from a Wayland windowing value to the viewport.
* Scales up or not at all.
*/
static int gwl_window_fractional_from_viewport(const GWL_WindowFrame &frame, int value)
{
GHOST_ASSERT(frame.fractional_scale != 0, "Not fractional or called before initiazlized!");
return (value * FRACTIONAL_DENOMINATOR) / frame.fractional_scale;
}
/* NOTE: rounded versions are needed for window-frame dimensions conversions.
* (rounding is part of the WAYLAND spec). All other conversions such as cursor coordinates
* can used simple integer division as rounding is not defined in this case. */
static int gwl_window_fractional_to_viewport_round(const GWL_WindowFrame &frame, int value)
{
GHOST_ASSERT(frame.fractional_scale != 0, "Not fractional or called before initiazlized!");
return lroundf(double(value * frame.fractional_scale) / double(FRACTIONAL_DENOMINATOR));
}
static int gwl_window_fractional_from_viewport_round(const GWL_WindowFrame &frame, int value)
{
GHOST_ASSERT(frame.fractional_scale != 0, "Not fractional or called before initiazlized!");
return lroundf(double(value * FRACTIONAL_DENOMINATOR) / double(frame.fractional_scale));
}
static bool gwl_window_viewport_set(GWL_Window *win, bool *r_surface_needs_commit)
{
if (win->viewport != nullptr) {
return false;
}
struct wp_viewporter *viewporter = win->ghost_system->wp_viewporter();
if (viewporter == nullptr) {
return false;
}
win->viewport = wp_viewporter_get_viewport(viewporter, win->wl_surface);
if (win->viewport == nullptr) {
return false;
}
/* Set the buffer scale to 1 since a viewport will be used. */
if (win->scale != 1) {
win->scale = 1;
wl_surface_set_buffer_scale(win->wl_surface, win->scale);
if (r_surface_needs_commit) {
*r_surface_needs_commit = true;
}
else {
wl_surface_commit(win->wl_surface);
}
}
return true;
}
static bool gwl_window_viewport_unset(GWL_Window *win, bool *r_surface_needs_commit)
{
if (win->viewport == nullptr) {
return false;
}
wp_viewport_destroy(win->viewport);
win->viewport = nullptr;
GHOST_ASSERT(win->scale == 1, "Unexpected scale!");
if (win->scale != win->scale_on_output) {
win->scale = win->scale_on_output;
wl_surface_set_buffer_scale(win->wl_surface, win->scale);
if (r_surface_needs_commit) {
*r_surface_needs_commit = true;
}
else {
wl_surface_commit(win->wl_surface);
}
}
return true;
}
static bool gwl_window_viewport_size_update(GWL_Window *win)
{
if (win->viewport == nullptr) {
return false;
}
wp_viewport_set_source(win->viewport,
wl_fixed_from_int(0),
wl_fixed_from_int(0),
wl_fixed_from_int(win->frame.size[0]),
wl_fixed_from_int(win->frame.size[1]));
wp_viewport_set_destination(
win->viewport,
gwl_window_fractional_from_viewport_round(win->frame, win->frame.size[0]),
gwl_window_fractional_from_viewport_round(win->frame, win->frame.size[1]));
return true;
}
#endif /* WITH_GHOST_WAYLAND_FRACTIONAL_SCALE */
/** \} */
/* -------------------------------------------------------------------- */
/** \name Internal #GWL_Window Activation
* \{ */
@ -369,7 +555,26 @@ static void gwl_window_activate(GWL_Window *win)
/** \name Internal #GWL_Window Pending Actions
* \{ */
static void gwl_window_frame_pending_size_set(GWL_Window *win)
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
static void gwl_window_frame_pending_fractional_scale_set(GWL_Window *win,
bool *r_surface_needs_commit)
{
if (win->frame_pending.fractional_scale == win->frame.fractional_scale) {
return;
}
win->frame.fractional_scale = win->frame_pending.fractional_scale;
if (win->frame_pending.fractional_scale) {
gwl_window_viewport_set(win, r_surface_needs_commit);
gwl_window_viewport_size_update(win);
}
else {
gwl_window_viewport_unset(win, r_surface_needs_commit);
}
}
#endif /* WITH_GHOST_WAYLAND_FRACTIONAL_SCALE */
static void gwl_window_frame_pending_size_set(GWL_Window *win, bool *r_surface_needs_commit)
{
if (win->frame_pending.size[0] == 0 || win->frame_pending.size[1] == 0) {
return;
@ -378,7 +583,19 @@ static void gwl_window_frame_pending_size_set(GWL_Window *win)
win->frame.size[0] = win->frame_pending.size[0];
win->frame.size[1] = win->frame_pending.size[1];
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
if (win->frame_pending.fractional_scale != win->frame.fractional_scale) {
gwl_window_frame_pending_fractional_scale_set(win, r_surface_needs_commit);
}
else {
gwl_window_viewport_size_update(win);
}
#else
(void)r_surface_needs_commit;
#endif
wl_egl_window_resize(win->egl_window, UNPACK2(win->frame.size), 0, 0);
win->ghost_window->notify_size();
win->frame_pending.size[0] = 0;
@ -401,6 +618,9 @@ static void gwl_window_pending_actions_handle(GWL_Window *win)
gwl_window_frame_update_from_pending(win);
}
if (win->pending_actions[PENDING_EGL_WINDOW_RESIZE].exchange(false)) {
# ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
gwl_window_viewport_size_update(win);
# endif
wl_egl_window_resize(win->egl_window, UNPACK2(win->frame.size), 0, 0);
}
# ifdef GHOST_OPENGL_ALPHA
@ -427,13 +647,32 @@ static void gwl_window_frame_update_from_pending_no_lock(GWL_Window *win)
#endif
const bool dpi_changed = win->frame_pending.fractional_scale != win->frame.fractional_scale;
bool surface_needs_commit = false;
if (win->frame_pending.size[0] != 0 && win->frame_pending.size[1] != 0) {
if ((win->frame.size[0] != win->frame_pending.size[0]) ||
(win->frame.size[1] != win->frame_pending.size[1])) {
gwl_window_frame_pending_size_set(win);
gwl_window_frame_pending_size_set(win, &surface_needs_commit);
}
}
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
if (win->frame_pending.fractional_scale != win->frame.fractional_scale) {
gwl_window_frame_pending_fractional_scale_set(win, &surface_needs_commit);
}
#endif
if (surface_needs_commit) {
wl_surface_commit(win->wl_surface);
}
if (dpi_changed) {
GHOST_SystemWayland *system = win->ghost_system;
system->pushEvent(new GHOST_Event(
system->getMilliSeconds(), GHOST_kEventWindowDPIHintChanged, win->ghost_window));
}
if (win->frame_pending.is_active) {
win->ghost_window->activate();
}
@ -476,26 +715,11 @@ static int output_scale_cmp(const GWL_Output *output_a, const GWL_Output *output
if (output_a->scale > output_b->scale) {
return 1;
}
if (output_a->has_scale_fractional || output_b->has_scale_fractional) {
const wl_fixed_t scale_fractional_a = output_a->has_scale_fractional ?
output_a->scale_fractional :
wl_fixed_from_int(output_a->scale);
const wl_fixed_t scale_fractional_b = output_b->has_scale_fractional ?
output_b->scale_fractional :
wl_fixed_from_int(output_b->scale);
if (scale_fractional_a < scale_fractional_b) {
return -1;
}
if (scale_fractional_a > scale_fractional_b) {
return 1;
}
}
return 0;
}
static int outputs_max_scale_or_default(const std::vector<GWL_Output *> &outputs,
const int32_t scale_default,
wl_fixed_t *r_scale_fractional)
const int32_t scale_default)
{
const GWL_Output *output_max = nullptr;
for (const GWL_Output *reg_output : outputs) {
@ -505,17 +729,9 @@ static int outputs_max_scale_or_default(const std::vector<GWL_Output *> &outputs
}
if (output_max) {
if (r_scale_fractional) {
*r_scale_fractional = output_max->has_scale_fractional ?
output_max->scale_fractional :
wl_fixed_from_int(output_max->scale);
}
return output_max->scale;
}
if (r_scale_fractional) {
*r_scale_fractional = wl_fixed_from_int(scale_default);
}
return scale_default;
}
@ -543,8 +759,17 @@ static void xdg_toplevel_handle_configure(void *data,
std::lock_guard lock_frame_guard{win->frame_pending_mutex};
#endif
win->frame_pending.size[0] = win->scale * width;
win->frame_pending.size[1] = win->scale * height;
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
if (win->frame.fractional_scale) {
win->frame_pending.size[0] = gwl_window_fractional_to_viewport_round(win->frame, width);
win->frame_pending.size[1] = gwl_window_fractional_to_viewport_round(win->frame, height);
}
else
#endif /* WITH_GHOST_WAYLAND_FRACTIONAL_SCALE */
{
win->frame_pending.size[0] = width * win->scale;
win->frame_pending.size[1] = height * win->scale;
}
win->frame_pending.is_maximised = false;
win->frame_pending.is_fullscreen = false;
@ -619,6 +844,63 @@ static const struct xdg_activation_token_v1_listener *xdg_activation_listener_ge
/** \} */
/* -------------------------------------------------------------------- */
/** \name Listener (Fractional Scale), #wp_fractional_scale_manager_v1_interface
*
* Used by #gwl_window_activate.
* \{ */
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
static CLG_LogRef LOG_WL_FRACTIONAL_SCALE = {"ghost.wl.handle.fractional_scale"};
# define LOG (&LOG_WL_FRACTIONAL_SCALE)
static void wp_fractional_scale_handle_preferred_scale(
void *data, struct wp_fractional_scale_v1 * /*wp_fractional_scale_v1*/, uint preferred_scale)
{
CLOG_INFO(LOG,
2,
"preferred_scale (preferred_scale=%.6f)",
double(preferred_scale) / FRACTIONAL_DENOMINATOR);
GWL_Window *win = static_cast<GWL_Window *>(data);
const bool is_fractional = (preferred_scale % FRACTIONAL_DENOMINATOR) != 0;
/* When non-fractional, never use fractional scaling! */
win->frame_pending.fractional_scale = is_fractional ? preferred_scale : 0;
if (win->frame.fractional_scale != win->frame_pending.fractional_scale) {
/* Resize the window failing to do so results in severe flickering with a
* multi-monitor setup when multiple monitors have different scales.
*
* NOTE: some flickering is still possible even when resizing this
* happens when dragging the right hand side of the title-bar in KDE
* as expanding changed the size on the RHS, this may be up to the compositor to fix. */
const int scale_prev = win->frame.fractional_scale ?
win->frame.fractional_scale :
win->scale_on_output * FRACTIONAL_DENOMINATOR;
const int scale_next = win->frame_pending.fractional_scale ?
win->frame_pending.fractional_scale :
win->scale_on_output * FRACTIONAL_DENOMINATOR;
for (size_t i = 0; i < ARRAY_SIZE(win->frame_pending.size); i++) {
const int value = win->frame_pending.size[i] ? win->frame_pending.size[i] :
win->frame.size[i];
win->frame_pending.size[i] = lroundf(value * (double(scale_next) / double(scale_prev)));
}
gwl_window_pending_actions_tag(win, PENDING_WINDOW_FRAME_CONFIGURE);
}
}
static const struct wp_fractional_scale_v1_listener wp_fractional_scale_listener = {
/*preferred_scale*/ wp_fractional_scale_handle_preferred_scale,
};
# undef LOG
#endif /* WITH_GHOST_WAYLAND_FRACTIONAL_SCALE */
/** \} */
/* -------------------------------------------------------------------- */
/** \name Listener (LibDecor Frame), #libdecor_frame_interface
* \{ */
@ -643,16 +925,27 @@ static void frame_handle_configure(struct libdecor_frame *frame,
/* Set the size. */
int size_next[2];
{
const int scale = static_cast<GWL_Window *>(data)->scale;
GWL_Window *win = static_cast<GWL_Window *>(data);
const int scale = win->scale;
if (!libdecor_configuration_get_content_size(
configuration, frame, &size_next[0], &size_next[1])) {
GWL_Window *win = static_cast<GWL_Window *>(data);
size_next[0] = win->frame.size[0] / scale;
size_next[1] = win->frame.size[1] / scale;
}
frame_pending->size[0] = scale * size_next[0];
frame_pending->size[1] = scale * size_next[1];
# ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
if (win->frame.fractional_scale) {
win->frame_pending.size[0] = gwl_window_fractional_to_viewport_round(win->frame,
size_next[0]);
win->frame_pending.size[1] = gwl_window_fractional_to_viewport_round(win->frame,
size_next[1]);
}
else
# endif /* WITH_GHOST_WAYLAND_FRACTIONAL_SCALE */
{
frame_pending->size[0] = size_next[0] * scale;
frame_pending->size[1] = size_next[1] * scale;
}
}
/* Set the state. */
@ -892,7 +1185,8 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system,
*
* Using the maximum scale is best as it results in the window first being smaller,
* avoiding a large window flashing before it's made smaller. */
window_->scale = outputs_max_scale_or_default(system_->outputs(), 1, &window_->scale_fractional);
window_->scale = outputs_max_scale_or_default(system_->outputs(), 1);
window_->scale_on_output = window_->scale;
window_->frame.size[0] = int32_t(width);
window_->frame.size[1] = int32_t(height);
@ -915,6 +1209,17 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system,
window_->egl_window = wl_egl_window_create(
window_->wl_surface, int(window_->frame.size[0]), int(window_->frame.size[1]));
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
struct wp_fractional_scale_manager_v1 *fractional_scale_manager =
system->wp_fractional_scale_manager();
if (fractional_scale_manager) {
window_->fractional_scale_handle = wp_fractional_scale_manager_v1_get_fractional_scale(
fractional_scale_manager, window_->wl_surface);
wp_fractional_scale_v1_add_listener(
window_->fractional_scale_handle, &wp_fractional_scale_listener, window_);
}
#endif /* WITH_GHOST_WAYLAND_FRACTIONAL_SCALE */
/* NOTE: The limit is in points (not pixels) so Hi-DPI will limit to larger number of pixels.
* This has the advantage that the size limit is the same when moving the window between monitors
* with different scales set. If it was important to limit in pixels it could be re-calculated
@ -1033,13 +1338,14 @@ GHOST_TSuccess GHOST_WindowWayland::setWindowCursorGrab(GHOST_TGrabCursorMode mo
}
bounds = &bounds_buf;
}
if (system_->window_cursor_grab_set(mode,
m_cursorGrab,
m_cursorGrabInitPos,
bounds,
m_cursorGrabAxis,
window_->wl_surface,
window_->scale)) {
this->scale_params())) {
return GHOST_kSuccess;
}
return GHOST_kFailure;
@ -1125,7 +1431,7 @@ GHOST_TSuccess GHOST_WindowWayland::setClientSize(const uint32_t width, const ui
window_->frame_pending.size[0] = width;
window_->frame_pending.size[1] = height;
gwl_window_frame_pending_size_set(window_);
gwl_window_frame_pending_size_set(window_, nullptr);
return GHOST_kSuccess;
}
@ -1163,6 +1469,18 @@ GHOST_WindowWayland::~GHOST_WindowWayland()
window_->xdg_activation_token = nullptr;
}
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
if (window_->fractional_scale_handle) {
wp_fractional_scale_v1_destroy(window_->fractional_scale_handle);
window_->fractional_scale_handle = nullptr;
}
if (window_->viewport) {
wp_viewport_destroy(window_->viewport);
window_->viewport = nullptr;
}
#endif /* WITH_GHOST_WAYLAND_FRACTIONAL_SCALE */
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
if (use_libdecor) {
gwl_libdecor_window_destroy(window_->libdecor);
@ -1192,25 +1510,13 @@ uint16_t GHOST_WindowWayland::getDPIHint()
/* No need to lock `server_mutex`
* (`outputs_changed_update_scale` never changes values in a non-main thread). */
const wl_fixed_t scale_fractional = window_->scale_fractional;
GHOST_ASSERT(wl_fixed_from_int(window_->scale) >= scale_fractional,
"Fractional scale should always be less than the fixed scale.");
/* Using the physical DPI will cause wrong scaling of the UI
* use a multiplier for the default DPI as a workaround. */
if (wl_fixed_from_int(window_->scale) == scale_fractional || window_->scale <= 1) {
/* No fractional scaling. */
return window_->scale * base_dpi;
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
if (window_->frame.fractional_scale) {
return gwl_window_fractional_to_viewport(window_->frame, base_dpi);
}
const int scale_ceil = window_->scale;
const int scale_floor = scale_ceil - 1;
const wl_fixed_t scale_fractional_final = wl_fixed_to_int(
scale_fractional *
/* Compensate for the buffer being rendered at `window_->scale`,
* then scaled down fractionally. */
(wl_fixed_from_int(1) + ((wl_fixed_from_int(scale_ceil) - scale_fractional) / scale_floor)));
#endif /* WITH_GHOST_WAYLAND_FRACTIONAL_SCALE */
return wl_fixed_to_int(scale_fractional_final * base_dpi);
return window_->scale * base_dpi;
}
GHOST_TSuccess GHOST_WindowWayland::setWindowCursorVisibility(bool visible)
@ -1383,18 +1689,34 @@ int GHOST_WindowWayland::scale() const
return window_->scale;
}
wl_fixed_t GHOST_WindowWayland::scale_fractional() const
const struct GWL_WindowScaleParams &GHOST_WindowWayland::scale_params() const
{
return window_->scale_fractional;
/* NOTE(@ideasman42): This could be kept initialized,
* since it's such a small struct it's not so important. */
GWL_WindowScaleParams *scale_params = &window_->scale_params;
scale_params->is_fractional = (window_->frame.fractional_scale != 0);
scale_params->scale = scale_params->is_fractional ? window_->frame.fractional_scale :
window_->scale;
return *scale_params;
}
wl_fixed_t GHOST_WindowWayland::wl_fixed_from_window(wl_fixed_t value) const
{
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
if (window_->frame.fractional_scale) {
return gwl_window_fractional_from_viewport(window_->frame, value);
}
#endif
return value / window_->scale;
}
wl_fixed_t GHOST_WindowWayland::wl_fixed_to_window(wl_fixed_t value) const
{
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
if (window_->frame.fractional_scale) {
return gwl_window_fractional_to_viewport(window_->frame, value);
}
#endif
return value * window_->scale;
}
@ -1513,13 +1835,19 @@ bool GHOST_WindowWayland::outputs_changed_update_scale()
}
#endif
wl_fixed_t scale_fractional_next = 0;
const int scale_next = outputs_max_scale_or_default(outputs(), 0, &scale_fractional_next);
const int scale_next = outputs_max_scale_or_default(outputs(), 0);
if (UNLIKELY(scale_next == 0)) {
return false;
}
const wl_fixed_t scale_fractional_curr = window_->scale_fractional;
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
if (window_->frame.fractional_scale) {
GHOST_ASSERT(window_->scale == 1, "Scale is expected to be 1 for fractional scaling!");
window_->scale_on_output = scale_next;
return false;
}
#endif /* WITH_GHOST_WAYLAND_FRACTIONAL_SCALE */
const int scale_curr = window_->scale;
bool changed = false;
@ -1544,22 +1872,18 @@ bool GHOST_WindowWayland::outputs_changed_update_scale()
window_->frame_pending.size[0] = (window_->frame.size[0] / scale_curr) * scale_next;
window_->frame_pending.size[1] = (window_->frame.size[1] / scale_curr) * scale_next;
gwl_window_frame_pending_size_set(window_);
gwl_window_frame_pending_size_set(window_, nullptr);
changed = true;
}
if (scale_fractional_next != scale_fractional_curr) {
window_->scale_fractional = scale_fractional_next;
changed = true;
/* As this is a low-level function, we might want adding this event to be optional,
* always add the event unless it causes issues. */
GHOST_SystemWayland *system = window_->ghost_system;
system->pushEvent(
new GHOST_Event(system->getMilliSeconds(), GHOST_kEventWindowDPIHintChanged, this));
changed = true;
}
/* Ensure both are always set. */
window_->scale_on_output = window_->scale;
return changed;
}

View File

@ -140,7 +140,8 @@ class GHOST_WindowWayland : public GHOST_Window {
/* WAYLAND direct-data access. */
int scale() const;
wl_fixed_t scale_fractional() const;
const struct GWL_WindowScaleParams &scale_params() const;
struct wl_surface *wl_surface() const;
const std::vector<GWL_Output *> &outputs();

View File

@ -330,7 +330,7 @@ external contributions to this project including patches, pull requests, etc.
** libAOM; version 3.4.0 -- https://aomedia.googlesource.com/aom/
Copyright (c) 2016, Alliance for Open Media. All rights reserved.
** NASM; version 2.15.02 -- https://www.nasm.us/
Contributions since 2008-12-15 are Copyright Intel Corporation.
Copyright 1996-2010 the NASM Authors - All rights reserved.
** OpenJPEG; version 2.5.0 -- https://github.com/uclouvain/openjpeg
Copyright (c) 2002-2014, Universite catholique de Louvain (UCL), Belgium
Copyright (c) 2002-2014, Professor Benoit Macq
@ -369,7 +369,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Ceres Solver - A fast non-linear least squares minimizer
Copyright 2016 Google Inc. All rights reserved.
** Curve-Fit-nD; version ddcd5bd -- https://github.com/ideasman42/curve-fit-nd
Copyright (c) 2016, Blender Foundation.
Copyright (c) 2016, DWANGO Co., Ltd.
Copyright (c) 2016, Campbell Barton
All rights reserved.
** Google C++ Testing Framework; version 1.10.0 --
https://github.com/google/googletest
Copyright 2007, Google Inc.
@ -381,11 +383,13 @@ All rights reserved.
Copyright (c) 2006, Google Inc.
All rights reserved.
** Imath; version 3.1.5 -- https://github.com/AcademySoftwareFoundation/Imath
Contributors to the OpenEXR Project.
Copyright Contributors to the OpenEXR Project. All rights reserved.
** ISPC; version 1.17.0 -- https://github.com/ispc/ispc
Copyright Intel Corporation
All rights reserved.
** NumPy; version 1.23.5 -- https://numpy.org/
Copyright (c) 2005-2021, NumPy Developers.
Copyright (c) 2005-2022, NumPy Developers.
All rights reserved.
** Ogg; version 1.3.5 -- https://www.xiph.org/ogg/
COPYRIGHT (C) 1994-2019 by the Xiph.Org Foundation https://www.xiph.org/
** Open Shading Language; version

View File

@ -4410,6 +4410,8 @@ def km_face_mask(params):
{"properties": [("deselect", False)]}),
("paint.face_select_linked_pick", {"type": 'L', "value": 'PRESS', "shift": True},
{"properties": [("deselect", True)]}),
("paint.face_select_more", {"type": 'NUMPAD_PLUS', "value": 'PRESS', "ctrl": True}, None),
("paint.face_select_less", {"type": 'NUMPAD_MINUS', "value": 'PRESS', "ctrl": True}, None),
])
return keymap
@ -4437,6 +4439,8 @@ def km_weight_paint_vertex_selection(params):
{"properties": [("select", True)]}),
("paint.vert_select_linked_pick", {"type": 'L', "value": 'PRESS', "shift": True},
{"properties": [("select", False)]}),
("paint.vert_select_more", {"type": 'NUMPAD_PLUS', "value": 'PRESS', "ctrl": True}, None),
("paint.vert_select_less", {"type": 'NUMPAD_MINUS', "value": 'PRESS', "ctrl": True}, None),
])
return keymap

View File

@ -2011,6 +2011,9 @@ class VIEW3D_MT_select_paint_mask(Menu):
layout.operator("paint.face_select_all", text="None").action = 'DESELECT'
layout.operator("paint.face_select_all", text="Invert").action = 'INVERT'
layout.operator("paint.face_select_more")
layout.operator("paint.face_select_less")
layout.separator()
layout.operator("view3d.select_box")
@ -2032,6 +2035,9 @@ class VIEW3D_MT_select_paint_mask_vertex(Menu):
layout.operator("paint.vert_select_all", text="None").action = 'DESELECT'
layout.operator("paint.vert_select_all", text="Invert").action = 'INVERT'
layout.operator("paint.vert_select_more"),
layout.operator("paint.vert_select_less"),
layout.separator()
layout.operator("view3d.select_box")
@ -7047,13 +7053,16 @@ class VIEW3D_PT_snapping(Panel):
col.prop(tool_settings, "use_snap_backface_culling")
if 'FACE' in snap_elements:
col.prop(tool_settings, "use_snap_project")
is_face_nearest_enabled = 'FACE_NEAREST' in snap_elements
if is_face_nearest_enabled or 'FACE' in snap_elements:
sub = col.column()
sub.active = not is_face_nearest_enabled
sub.prop(tool_settings, "use_snap_project")
if 'FACE_NEAREST' in snap_elements:
col.prop(tool_settings, "use_snap_to_same_target")
if object_mode == 'EDIT':
col.prop(tool_settings, "snap_face_nearest_steps")
if is_face_nearest_enabled:
col.prop(tool_settings, "use_snap_to_same_target")
if object_mode == 'EDIT':
col.prop(tool_settings, "snap_face_nearest_steps")
if 'VOLUME' in snap_elements:
col.prop(tool_settings, "use_snap_peel_object")

View File

@ -196,11 +196,14 @@ class AttributeFieldInput : public GeometryFieldInput {
category_ = Category::NamedAttribute;
}
static fn::GField Create(std::string name, const CPPType &type)
{
auto field_input = std::make_shared<AttributeFieldInput>(std::move(name), type);
return fn::GField(field_input);
}
template<typename T> static fn::Field<T> Create(std::string name)
{
const CPPType &type = CPPType::get<T>();
auto field_input = std::make_shared<AttributeFieldInput>(std::move(name), type);
return fn::Field<T>{field_input};
return fn::Field<T>(Create(std::move(name), CPPType::get<T>()));
}
StringRefNull attribute_name() const

View File

@ -185,17 +185,17 @@ struct IDProperty *IDP_CopyProperty_ex(const struct IDProperty *prop,
* Copy content from source #IDProperty into destination one,
* freeing destination property's content first.
*/
void IDP_CopyPropertyContent(struct IDProperty *dst, struct IDProperty *src) ATTR_NONNULL();
void IDP_CopyPropertyContent(struct IDProperty *dst, const struct IDProperty *src) ATTR_NONNULL();
/**
* \param is_strict: When false treat missing items as a match.
*/
bool IDP_EqualsProperties_ex(struct IDProperty *prop1,
struct IDProperty *prop2,
bool IDP_EqualsProperties_ex(const struct IDProperty *prop1,
const struct IDProperty *prop2,
bool is_strict) ATTR_WARN_UNUSED_RESULT;
bool IDP_EqualsProperties(struct IDProperty *prop1,
struct IDProperty *prop2) ATTR_WARN_UNUSED_RESULT;
bool IDP_EqualsProperties(const struct IDProperty *prop1,
const struct IDProperty *prop2) ATTR_WARN_UNUSED_RESULT;
/**
* Allocate a new ID.

View File

@ -21,7 +21,7 @@ void ModifierComputeContext::print_current_in_line(std::ostream &stream) const
NodeGroupComputeContext::NodeGroupComputeContext(
const ComputeContext *parent,
const int node_id,
const int32_t node_id,
const std::optional<ComputeContextHash> &cached_hash)
: ComputeContext(s_static_type, parent), node_id_(node_id)
{

View File

@ -779,7 +779,7 @@ IDProperty *IDP_CopyProperty(const IDProperty *prop)
return IDP_CopyProperty_ex(prop, 0);
}
void IDP_CopyPropertyContent(IDProperty *dst, IDProperty *src)
void IDP_CopyPropertyContent(IDProperty *dst, const IDProperty *src)
{
IDProperty *idprop_tmp = IDP_CopyProperty(src);
idprop_tmp->prev = dst->prev;
@ -805,7 +805,9 @@ IDProperty *IDP_GetProperties(ID *id, const bool create_if_needed)
return id->properties;
}
bool IDP_EqualsProperties_ex(IDProperty *prop1, IDProperty *prop2, const bool is_strict)
bool IDP_EqualsProperties_ex(const IDProperty *prop1,
const IDProperty *prop2,
const bool is_strict)
{
if (prop1 == NULL && prop2 == NULL) {
return true;
@ -859,8 +861,8 @@ bool IDP_EqualsProperties_ex(IDProperty *prop1, IDProperty *prop2, const bool is
return false;
}
LISTBASE_FOREACH (IDProperty *, link1, &prop1->data.group) {
IDProperty *link2 = IDP_GetPropertyFromGroup(prop2, link1->name);
LISTBASE_FOREACH (const IDProperty *, link1, &prop1->data.group) {
const IDProperty *link2 = IDP_GetPropertyFromGroup(prop2, link1->name);
if (!IDP_EqualsProperties_ex(link1, link2, is_strict)) {
return false;
@ -870,8 +872,8 @@ bool IDP_EqualsProperties_ex(IDProperty *prop1, IDProperty *prop2, const bool is
return true;
}
case IDP_IDPARRAY: {
IDProperty *array1 = IDP_IDPArray(prop1);
IDProperty *array2 = IDP_IDPArray(prop2);
const IDProperty *array1 = IDP_IDPArray(prop1);
const IDProperty *array2 = IDP_IDPArray(prop2);
if (prop1->len != prop2->len) {
return false;
@ -894,7 +896,7 @@ bool IDP_EqualsProperties_ex(IDProperty *prop1, IDProperty *prop2, const bool is
return true;
}
bool IDP_EqualsProperties(IDProperty *prop1, IDProperty *prop2)
bool IDP_EqualsProperties(const IDProperty *prop1, const IDProperty *prop2)
{
return IDP_EqualsProperties_ex(prop1, prop2, true);
}

View File

@ -468,6 +468,40 @@ struct CartesianBasis {
from_orthonormal_axes(b_forward, b_up));
}
template<typename T>
[[nodiscard]] inline VecBase<T, 3> transform_point(const CartesianBasis &basis,
const VecBase<T, 3> &v)
{
VecBase<T, 3> result;
result[basis.x().axis().as_int()] = basis.x().is_negative() ? -v[0] : v[0];
result[basis.y().axis().as_int()] = basis.y().is_negative() ? -v[1] : v[1];
result[basis.z().axis().as_int()] = basis.z().is_negative() ? -v[2] : v[2];
return result;
}
/**
* Return the inverse transformation represented by the given basis.
* This is conceptually the equivalent to a rotation matrix transpose, but much faster.
*/
[[nodiscard]] inline CartesianBasis invert(const CartesianBasis &basis)
{
/* Returns the column where the `axis` is found in. The sign is taken from the axis value. */
auto search_axis = [](const CartesianBasis &basis, const Axis axis) {
if (basis.x().axis() == axis) {
return basis.x().is_negative() ? AxisSigned::X_NEG : AxisSigned::X_POS;
}
if (basis.y().axis() == axis) {
return basis.y().is_negative() ? AxisSigned::Y_NEG : AxisSigned::Y_POS;
}
return basis.z().is_negative() ? AxisSigned::Z_NEG : AxisSigned::Z_POS;
};
CartesianBasis result;
result.x() = search_axis(basis, Axis::X);
result.y() = search_axis(basis, Axis::Y);
result.z() = search_axis(basis, Axis::Z);
return result;
}
/** \} */
} // namespace blender::math

View File

@ -1101,8 +1101,8 @@ double determinant_m3_array_db(const double m[3][3])
bool invert_m2_m2(float inverse[2][2], const float mat[2][2])
{
const float det = determinant_m2(mat[0][0], mat[1][0], mat[0][1], mat[1][1]);
adjoint_m2_m2(inverse, mat);
float det = determinant_m2(mat[0][0], mat[1][0], mat[0][1], mat[1][1]);
bool success = (det != 0.0f);
if (success) {
@ -1957,11 +1957,15 @@ void normalize_m4_m4(float rmat[4][4], const float mat[4][4])
void adjoint_m2_m2(float R[2][2], const float M[2][2])
{
BLI_assert(R != M);
R[0][0] = M[1][1];
R[0][1] = -M[0][1];
R[1][0] = -M[1][0];
R[1][1] = M[0][0];
const float r00 = M[1][1];
const float r01 = -M[0][1];
const float r10 = -M[1][0];
const float r11 = M[0][0];
R[0][0] = r00;
R[0][1] = r01;
R[1][0] = r10;
R[1][1] = r11;
}
void adjoint_m3_m3(float R[3][3], const float M[3][3])
@ -2026,9 +2030,8 @@ void adjoint_m4_m4(float R[4][4], const float M[4][4]) /* out = ADJ(in) */
R[3][3] = determinant_m3(a1, a2, a3, b1, b2, b3, c1, c2, c3);
}
float determinant_m2(float a, float b, float c, float d)
float determinant_m2(const float a, const float b, const float c, const float d)
{
return a * d - b * c;
}

View File

@ -641,10 +641,9 @@ TEST(math_rotation, CartesianBasis)
expect.ptr());
}
EXPECT_EQ(from_rotation<float3x3>(
rotation_between(from_orthonormal_axes(src_forward, src_up),
from_orthonormal_axes(dst_forward, dst_up))),
expect);
CartesianBasis rotation = rotation_between(from_orthonormal_axes(src_forward, src_up),
from_orthonormal_axes(dst_forward, dst_up));
EXPECT_EQ(from_rotation<float3x3>(rotation), expect);
if (src_forward == dst_forward) {
expect = float3x3::identity();
@ -656,6 +655,11 @@ TEST(math_rotation, CartesianBasis)
}
EXPECT_EQ(from_rotation<float3x3>(rotation_between(src_forward, dst_forward)), expect);
float3 point(1.0f, 2.0f, 3.0f);
CartesianBasis rotation_inv = invert(rotation);
/* Test inversion identity. */
EXPECT_EQ(transform_point(rotation_inv, transform_point(rotation, point)), point);
}
}
}
@ -667,8 +671,23 @@ TEST(math_rotation, Transform)
Quaternion q(0.927091f, 0.211322f, -0.124857f, 0.283295f);
float3 p(0.576f, -0.6546f, 46.354f);
p = transform_point(q, p);
EXPECT_V3_NEAR(p, float3(-4.33722f, -21.661f, 40.7608f), 1e-4f);
float3 result = transform_point(q, p);
EXPECT_V3_NEAR(result, float3(-4.33722f, -21.661f, 40.7608f), 1e-4f);
/* Validated using `to_quaternion` before doing the transform. */
float3 p2(1.0f, 2.0f, 3.0f);
result = transform_point(CartesianBasis(AxisSigned::X_POS, AxisSigned::Y_POS, AxisSigned::Z_POS),
p2);
EXPECT_EQ(result, float3(1.0f, 2.0f, 3.0f));
result = transform_point(
rotation_between(from_orthonormal_axes(AxisSigned::Y_POS, AxisSigned::Z_POS),
from_orthonormal_axes(AxisSigned::X_POS, AxisSigned::Z_POS)),
p2);
EXPECT_EQ(result, float3(-2.0f, 1.0f, 3.0f));
result = transform_point(from_orthonormal_axes(AxisSigned::Z_POS, AxisSigned::X_POS), p2);
EXPECT_EQ(result, float3(3.0f, 1.0f, 2.0f));
result = transform_point(from_orthonormal_axes(AxisSigned::X_NEG, AxisSigned::Y_POS), p2);
EXPECT_EQ(result, float3(-2.0f, 3.0f, -1.0f));
}
TEST(math_rotation, DualQuaternionNormalize)

View File

@ -2271,19 +2271,6 @@ static int lib_link_main_data_restore_cb(LibraryIDLinkCallbackData *cb_data)
return IDWALK_RET_NOP;
}
/* Special ugly case here, thanks again for those non-IDs IDs... */
/* We probably need to add more cases here (hint: nodetrees),
* but will wait for changes from D5559 to get in first. */
if (GS((*id_pointer)->name) == ID_GR) {
Collection *collection = reinterpret_cast<Collection *>(*id_pointer);
if (collection->flag & COLLECTION_IS_MASTER) {
/* We should never reach that point anymore, since master collection private ID should be
* properly tagged with IDWALK_CB_EMBEDDED. */
BLI_assert_unreachable();
return IDWALK_RET_NOP;
}
}
IDNameLib_Map *id_map = static_cast<IDNameLib_Map *>(cb_data->user_data);
/* NOTE: Handling of user-count here is really bad, defining its own system...

View File

@ -60,6 +60,7 @@ void MaskNode::convert_to_operations(NodeConverter &converter,
scale_operation->set_new_width(operation->get_mask_width());
scale_operation->set_is_aspect(false);
scale_operation->set_is_crop(false);
scale_operation->set_offset(0.0f, 0.0f);
scale_operation->set_scale_canvas_max_size({float(data->size_x), float(data->size_y)});
converter.add_operation(scale_operation);

View File

@ -57,7 +57,9 @@ void workbench_engine_init(void *ved)
wpd->dummy_image_tx = txl->dummy_image_tx;
if (OBJECT_ID_PASS_ENABLED(wpd)) {
wpd->object_id_tx = DRW_texture_pool_query_fullscreen(GPU_R16UI, &draw_engine_workbench);
const eGPUTextureUsage usage = GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_SHADER_READ;
wpd->object_id_tx = DRW_texture_pool_query_fullscreen_ex(
GPU_R16UI, usage, &draw_engine_workbench);
}
else {
/* Don't free because it's a pool texture. */

View File

@ -29,9 +29,10 @@ void workbench_opaque_engine_init(WORKBENCH_Data *data)
/* Reused the same textures format for transparent pipeline to share the textures. */
const eGPUTextureFormat col_tex_format = GPU_RGBA16F;
const eGPUTextureFormat nor_tex_format = NORMAL_ENCODING_ENABLED() ? GPU_RG16F : GPU_RGBA16F;
const eGPUTextureUsage usage = GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_SHADER_READ;
wpd->material_buffer_tx = DRW_texture_pool_query_fullscreen(col_tex_format, owner);
wpd->normal_buffer_tx = DRW_texture_pool_query_fullscreen(nor_tex_format, owner);
wpd->material_buffer_tx = DRW_texture_pool_query_fullscreen_ex(col_tex_format, usage, owner);
wpd->normal_buffer_tx = DRW_texture_pool_query_fullscreen_ex(nor_tex_format, usage, owner);
GPU_framebuffer_ensure_config(&fbl->opaque_fb,
{

View File

@ -352,7 +352,7 @@ void ShadowPass::init(const SceneState &scene_state, SceneResources &resources)
float4x4 view_matrix;
DRW_view_viewmat_get(nullptr, view_matrix.ptr(), false);
resources.world_buf.shadow_direction_vs = float4(
math::transform_direction(view_matrix, direction_ws));
math::transform_direction(view_matrix, direction_ws), 0.0f);
/* Clamp to avoid overshadowing and shading errors. */
float focus = clamp_f(scene.display.shadow_focus, 0.0001f, 0.99999f);

View File

@ -36,8 +36,9 @@ void workbench_transparent_engine_init(WORKBENCH_Data *data)
const eGPUTextureFormat accum_tex_format = GPU_RGBA16F;
const eGPUTextureFormat reveal_tex_format = NORMAL_ENCODING_ENABLED() ? GPU_RG16F : GPU_RGBA32F;
wpd->accum_buffer_tx = DRW_texture_pool_query_fullscreen(accum_tex_format, owner);
wpd->reveal_buffer_tx = DRW_texture_pool_query_fullscreen(reveal_tex_format, owner);
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_SHADER_READ;
wpd->accum_buffer_tx = DRW_texture_pool_query_fullscreen_ex(accum_tex_format, usage, owner);
wpd->reveal_buffer_tx = DRW_texture_pool_query_fullscreen_ex(reveal_tex_format, usage, owner);
GPU_framebuffer_ensure_config(&fbl->transp_accum_fb,
{

View File

@ -23,15 +23,13 @@ namespace blender::ed::curves {
static bool has_surface_deformation_node(const bNodeTree &ntree)
{
LISTBASE_FOREACH (const bNode *, node, &ntree.nodes) {
if (node->type == GEO_NODE_DEFORM_CURVES_ON_SURFACE) {
return true;
}
if (node->type == NODE_GROUP) {
if (node->id != nullptr) {
if (has_surface_deformation_node(*reinterpret_cast<const bNodeTree *>(node->id))) {
return true;
}
if (!ntree.nodes_by_type("GeometryNodeDeformCurvesOnSurface").is_empty()) {
return true;
}
for (const bNode *node : ntree.group_nodes()) {
if (const bNodeTree *sub_tree = reinterpret_cast<const bNodeTree *>(node->id)) {
if (has_surface_deformation_node(*sub_tree)) {
return true;
}
}
}

View File

@ -419,6 +419,11 @@ void paintface_select_linked(struct bContext *C,
struct Object *ob,
const int mval[2],
bool select);
/** Grow the selection of faces.
* \param face_step If true will also select faces that only touch on the corner.
*/
void paintface_select_more(struct Mesh *mesh, bool face_step);
void paintface_select_less(struct Mesh *mesh, bool face_step);
bool paintface_minmax(struct Object *ob, float r_min[3], float r_max[3]);
void paintface_hide(struct bContext *C, struct Object *ob, bool unselected);
@ -444,6 +449,8 @@ void paintvert_select_linked_pick(struct bContext *C,
struct Object *ob,
const int region_coordinates[2],
bool select);
void paintvert_select_more(struct Mesh *mesh, bool face_step);
void paintvert_select_less(struct Mesh *mesh, bool face_step);
void paintvert_hide(struct bContext *C, struct Object *ob, bool unselected);
void paintvert_reveal(struct bContext *C, struct Object *ob, bool select);

View File

@ -361,12 +361,6 @@ int bm_mesh_calc_uv_islands(const Scene *scene,
const float aspect_y,
BMUVOffsets offsets);
struct UVMapUDIM_Params {
const struct Image *image;
/** Copied from #SpaceImage.tile_grid_shape */
int grid_shape[2];
};
/**
* Returns true if UV coordinates lie on a valid tile in UDIM grid or tiled image.
*/

View File

@ -24,6 +24,7 @@
#include "BKE_customdata.h"
#include "BKE_global.h"
#include "BKE_mesh.hh"
#include "BKE_mesh_mapping.h"
#include "BKE_object.h"
#include "ED_mesh.h"
@ -350,6 +351,131 @@ void paintface_select_linked(bContext *C, Object *ob, const int mval[2], const b
paintface_flush_flags(C, ob, true, false);
}
static bool poly_has_selected_neighbor(blender::Span<int> poly_edges,
blender::Span<MEdge> edges,
blender::Span<bool> select_vert,
const bool face_step)
{
for (const int edge_index : poly_edges) {
const MEdge &edge = edges[edge_index];
/* If a poly is selected, all of its verts are selected too, meaning that neighboring faces
* will have some vertices selected. */
if (face_step) {
if (select_vert[edge.v1] || select_vert[edge.v2]) {
return true;
}
}
else {
if (select_vert[edge.v1] && select_vert[edge.v2]) {
return true;
}
}
}
return false;
}
void paintface_select_more(Mesh *mesh, const bool face_step)
{
using namespace blender;
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
bke::SpanAttributeWriter<bool> select_poly = attributes.lookup_or_add_for_write_span<bool>(
".select_poly", ATTR_DOMAIN_FACE);
bke::SpanAttributeWriter<bool> select_vert = attributes.lookup_or_add_for_write_span<bool>(
".select_vert", ATTR_DOMAIN_POINT);
const VArray<bool> hide_poly = attributes.lookup_or_default<bool>(
".hide_poly", ATTR_DOMAIN_FACE, false);
const Span<MPoly> polys = mesh->polys();
const Span<int> corner_edges = mesh->corner_edges();
const Span<MEdge> edges = mesh->edges();
threading::parallel_for(select_poly.span.index_range(), 1024, [&](const IndexRange range) {
for (const int i : range) {
if (select_poly.span[i] || hide_poly[i]) {
continue;
}
const MPoly &poly = polys[i];
if (poly_has_selected_neighbor(corner_edges.slice(poly.loopstart, poly.totloop),
edges,
select_vert.span,
face_step)) {
select_poly.span[i] = true;
}
}
});
select_poly.finish();
select_vert.finish();
}
static bool poly_has_unselected_neighbor(blender::Span<int> poly_edges,
blender::Span<MEdge> edges,
blender::BitSpan verts_of_unselected_faces,
const bool face_step)
{
for (const int edge_index : poly_edges) {
const MEdge &edge = edges[edge_index];
if (face_step) {
if (verts_of_unselected_faces[edge.v1] || verts_of_unselected_faces[edge.v2]) {
return true;
}
}
else {
if (verts_of_unselected_faces[edge.v1] && verts_of_unselected_faces[edge.v2]) {
return true;
}
}
}
return false;
}
void paintface_select_less(Mesh *mesh, const bool face_step)
{
using namespace blender;
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
bke::SpanAttributeWriter<bool> select_poly = attributes.lookup_or_add_for_write_span<bool>(
".select_poly", ATTR_DOMAIN_FACE);
const VArray<bool> hide_poly = attributes.lookup_or_default<bool>(
".hide_poly", ATTR_DOMAIN_FACE, false);
const Span<MPoly> polys = mesh->polys();
const Span<int> corner_verts = mesh->corner_verts();
const Span<int> corner_edges = mesh->corner_edges();
const Span<MEdge> edges = mesh->edges();
BitVector<> verts_of_unselected_faces(mesh->totvert);
/* Find all vertices of unselected faces to help find neighboring faces after. */
for (const int i : polys.index_range()) {
if (select_poly.span[i]) {
continue;
}
const MPoly &poly = polys[i];
for (const int vert : corner_verts.slice(poly.loopstart, poly.totloop)) {
verts_of_unselected_faces[vert].set(true);
}
}
threading::parallel_for(polys.index_range(), 1024, [&](const IndexRange range) {
for (const int i : range) {
if (!select_poly.span[i] || hide_poly[i]) {
continue;
}
const MPoly &poly = polys[i];
if (poly_has_unselected_neighbor(corner_edges.slice(poly.loopstart, poly.totloop),
edges,
verts_of_unselected_faces,
face_step)) {
select_poly.span[i] = false;
}
}
});
select_poly.finish();
}
bool paintface_deselect_all_visible(bContext *C, Object *ob, int action, bool flush_flags)
{
using namespace blender;
@ -660,6 +786,126 @@ void paintvert_select_linked(bContext *C, Object *ob)
paintvert_select_linked_vertices(C, ob, indices, true);
}
void paintvert_select_more(Mesh *mesh, const bool face_step)
{
using namespace blender;
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
bke::SpanAttributeWriter<bool> select_vert = attributes.lookup_or_add_for_write_span<bool>(
".select_vert", ATTR_DOMAIN_POINT);
const VArray<bool> hide_edge = attributes.lookup_or_default<bool>(
".hide_edge", ATTR_DOMAIN_EDGE, false);
const VArray<bool> hide_poly = attributes.lookup_or_default<bool>(
".hide_poly", ATTR_DOMAIN_FACE, false);
const Span<MPoly> polys = mesh->polys();
const Span<int> corner_edges = mesh->corner_edges();
const Span<int> corner_verts = mesh->corner_verts();
const Span<MEdge> edges = mesh->edges();
Array<Vector<int, 2>> edge_to_face_map;
if (face_step) {
edge_to_face_map = bke::mesh_topology::build_edge_to_poly_map(
polys, corner_edges, mesh->totedge);
}
/* Need a copy of the selected verts that we can read from and is not modified. */
BitVector<> select_vert_original(mesh->totvert, false);
for (int i = 0; i < mesh->totvert; i++) {
select_vert_original[i].set(select_vert.span[i]);
}
/* If we iterated over polys we wouldn't extend the selection through edges that have no face
* attached to them. */
for (const int i : edges.index_range()) {
const MEdge &edge = edges[i];
if ((!select_vert_original[edge.v1] && !select_vert_original[edge.v2]) || hide_edge[i]) {
continue;
}
select_vert.span[edge.v1] = true;
select_vert.span[edge.v2] = true;
if (!face_step) {
continue;
}
const Span<int> neighbor_polys = edge_to_face_map[i];
for (const int poly_i : neighbor_polys) {
if (hide_poly[poly_i]) {
continue;
}
const MPoly &poly = polys[poly_i];
for (const int vert : corner_verts.slice(poly.loopstart, poly.totloop)) {
select_vert.span[vert] = true;
}
}
}
select_vert.finish();
}
void paintvert_select_less(Mesh *mesh, const bool face_step)
{
using namespace blender;
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
bke::SpanAttributeWriter<bool> select_vert = attributes.lookup_or_add_for_write_span<bool>(
".select_vert", ATTR_DOMAIN_POINT);
const VArray<bool> hide_edge = attributes.lookup_or_default<bool>(
".hide_edge", ATTR_DOMAIN_EDGE, false);
const VArray<bool> hide_poly = attributes.lookup_or_default<bool>(
".hide_poly", ATTR_DOMAIN_FACE, false);
const Span<MPoly> polys = mesh->polys();
const Span<int> corner_edges = mesh->corner_edges();
const Span<int> corner_verts = mesh->corner_verts();
const Span<MEdge> edges = mesh->edges();
MeshElemMap *edge_poly_map;
int *edge_poly_mem = nullptr;
if (face_step) {
BKE_mesh_edge_poly_map_create(&edge_poly_map,
&edge_poly_mem,
edges.size(),
polys.data(),
polys.size(),
corner_edges.data(),
corner_edges.size());
}
/* Need a copy of the selected verts that we can read from and is not modified. */
BitVector<> select_vert_original(mesh->totvert);
for (int i = 0; i < mesh->totvert; i++) {
select_vert_original[i].set(select_vert.span[i]);
}
for (const int i : edges.index_range()) {
const MEdge &edge = edges[i];
if ((select_vert_original[edge.v1] && select_vert_original[edge.v2]) && !hide_edge[i]) {
continue;
}
select_vert.span[edge.v1] = false;
select_vert.span[edge.v2] = false;
if (!face_step) {
continue;
}
const Span<int> neighbor_polys(edge_poly_map[i].indices, edge_poly_map[i].count);
for (const int poly_i : neighbor_polys) {
if (hide_poly[poly_i]) {
continue;
}
const MPoly &poly = polys[poly_i];
for (const int vert : corner_verts.slice(poly.loopstart, poly.totloop)) {
select_vert.span[vert] = false;
}
}
}
if (edge_poly_mem) {
MEM_freeN(edge_poly_map);
MEM_freeN(edge_poly_mem);
}
select_vert.finish();
}
void paintvert_tag_select_update(bContext *C, Object *ob)
{
DEG_id_tag_update(static_cast<ID *>(ob->data), ID_RECALC_COPY_ON_WRITE | ID_RECALC_SELECT);

View File

@ -81,7 +81,7 @@ void immDrawPixelsTexScaledFullSize(const IMMDrawPixelsTexState *state,
const int mip_len = use_mipmap ? 9999 : 1;
GPUTexture *tex = GPU_texture_create_2d(
"immDrawPixels", img_w, img_h, mip_len, gpu_format, GPU_TEXTURE_USAGE_GENERAL, NULL);
"immDrawPixels", img_w, img_h, mip_len, gpu_format, GPU_TEXTURE_USAGE_SHADER_READ, NULL);
const bool use_float_data = ELEM(gpu_format, GPU_RGBA16F, GPU_RGB16F, GPU_R16F);
eGPUDataFormat gpu_data_format = (use_float_data) ? GPU_DATA_FLOAT : GPU_DATA_UBYTE;
@ -183,7 +183,7 @@ void immDrawPixelsTexTiled_scaling_clipping(IMMDrawPixelsTexState *state,
size_t stride = components * ((use_float_data) ? sizeof(float) : sizeof(uchar));
GPUTexture *tex = GPU_texture_create_2d(
"immDrawPixels", tex_w, tex_h, 1, gpu_format, GPU_TEXTURE_USAGE_GENERAL, NULL);
"immDrawPixels", tex_w, tex_h, 1, gpu_format, GPU_TEXTURE_USAGE_SHADER_READ, NULL);
GPU_texture_filter_mode(tex, use_filter);
GPU_texture_wrap_mode(tex, false, true);

View File

@ -4043,6 +4043,10 @@ static bool proj_paint_state_mesh_eval_init(const bContext *C, ProjPaintState *p
CustomData_MeshMasks cddata_masks = scene_eval->customdata_mask;
cddata_masks.fmask |= CD_MASK_MTFACE;
cddata_masks.lmask |= CD_MASK_PROP_FLOAT2;
cddata_masks.vmask |= CD_MASK_PROP_ALL | CD_MASK_CREASE;
cddata_masks.emask |= CD_MASK_PROP_ALL | CD_MASK_CREASE;
cddata_masks.pmask |= CD_MASK_PROP_ALL | CD_MASK_CREASE;
cddata_masks.lmask |= CD_MASK_PROP_ALL | CD_MASK_CREASE;
if (ps->do_face_sel) {
cddata_masks.vmask |= CD_MASK_ORIGINDEX;
cddata_masks.emask |= CD_MASK_ORIGINDEX;

View File

@ -378,6 +378,8 @@ void BRUSH_OT_sculpt_curves_falloff_preset(struct wmOperatorType *ot);
void PAINT_OT_face_select_linked(struct wmOperatorType *ot);
void PAINT_OT_face_select_linked_pick(struct wmOperatorType *ot);
void PAINT_OT_face_select_all(struct wmOperatorType *ot);
void PAINT_OT_face_select_more(struct wmOperatorType *ot);
void PAINT_OT_face_select_less(struct wmOperatorType *ot);
void PAINT_OT_face_select_hide(struct wmOperatorType *ot);
void PAINT_OT_face_vert_reveal(struct wmOperatorType *ot);
@ -387,6 +389,8 @@ void PAINT_OT_vert_select_ungrouped(struct wmOperatorType *ot);
void PAINT_OT_vert_select_hide(struct wmOperatorType *ot);
void PAINT_OT_vert_select_linked(struct wmOperatorType *ot);
void PAINT_OT_vert_select_linked_pick(struct wmOperatorType *ot);
void PAINT_OT_vert_select_more(struct wmOperatorType *ot);
void PAINT_OT_vert_select_less(struct wmOperatorType *ot);
bool vert_paint_poll(struct bContext *C);
bool mask_paint_poll(struct bContext *C);

View File

@ -1501,6 +1501,8 @@ void ED_operatortypes_paint(void)
WM_operatortype_append(PAINT_OT_vert_select_hide);
WM_operatortype_append(PAINT_OT_vert_select_linked);
WM_operatortype_append(PAINT_OT_vert_select_linked_pick);
WM_operatortype_append(PAINT_OT_vert_select_more);
WM_operatortype_append(PAINT_OT_vert_select_less);
/* vertex */
WM_operatortype_append(PAINT_OT_vertex_paint_toggle);
@ -1518,6 +1520,8 @@ void ED_operatortypes_paint(void)
WM_operatortype_append(PAINT_OT_face_select_linked);
WM_operatortype_append(PAINT_OT_face_select_linked_pick);
WM_operatortype_append(PAINT_OT_face_select_all);
WM_operatortype_append(PAINT_OT_face_select_more);
WM_operatortype_append(PAINT_OT_face_select_less);
WM_operatortype_append(PAINT_OT_face_select_hide);
WM_operatortype_append(PAINT_OT_face_vert_reveal);

View File

@ -693,6 +693,68 @@ void PAINT_OT_face_select_all(wmOperatorType *ot)
WM_operator_properties_select_all(ot);
}
static int paint_select_more_exec(bContext *C, wmOperator *op)
{
Object *ob = CTX_data_active_object(C);
Mesh *mesh = BKE_mesh_from_object(ob);
if (mesh == NULL || mesh->totpoly == 0) {
return OPERATOR_CANCELLED;
}
const bool face_step = RNA_boolean_get(op->ptr, "face_step");
paintface_select_more(mesh, face_step);
paintface_flush_flags(C, ob, true, false);
ED_region_tag_redraw(CTX_wm_region(C));
return OPERATOR_FINISHED;
}
void PAINT_OT_face_select_more(wmOperatorType *ot)
{
ot->name = "Select More";
ot->description = "Select Faces connected to existing selection";
ot->idname = "PAINT_OT_face_select_more";
ot->exec = paint_select_more_exec;
ot->poll = facemask_paint_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_boolean(
ot->srna, "face_step", true, "Face Step", "Also select faces that only touch on a corner");
}
static int paint_select_less_exec(bContext *C, wmOperator *op)
{
Object *ob = CTX_data_active_object(C);
Mesh *mesh = BKE_mesh_from_object(ob);
if (mesh == NULL || mesh->totpoly == 0) {
return OPERATOR_CANCELLED;
}
const bool face_step = RNA_boolean_get(op->ptr, "face_step");
paintface_select_less(mesh, face_step);
paintface_flush_flags(C, ob, true, false);
ED_region_tag_redraw(CTX_wm_region(C));
return OPERATOR_FINISHED;
}
void PAINT_OT_face_select_less(wmOperatorType *ot)
{
ot->name = "Select Less";
ot->description = "Deselect Faces connected to existing selection";
ot->idname = "PAINT_OT_face_select_less";
ot->exec = paint_select_less_exec;
ot->poll = facemask_paint_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_boolean(
ot->srna, "face_step", true, "Face Step", "Also deselect faces that only touch on a corner");
}
static int vert_select_all_exec(bContext *C, wmOperator *op)
{
Object *ob = CTX_data_active_object(C);
@ -796,6 +858,72 @@ void PAINT_OT_vert_select_linked_pick(wmOperatorType *ot)
"Whether to select or deselect linked vertices under the cursor");
}
static int paintvert_select_more_exec(bContext *C, wmOperator *op)
{
Object *ob = CTX_data_active_object(C);
Mesh *mesh = BKE_mesh_from_object(ob);
if (mesh == NULL || mesh->totpoly == 0) {
return OPERATOR_CANCELLED;
}
const bool face_step = RNA_boolean_get(op->ptr, "face_step");
paintvert_select_more(mesh, face_step);
paintvert_flush_flags(ob);
paintvert_tag_select_update(C, ob);
ED_region_tag_redraw(CTX_wm_region(C));
return OPERATOR_FINISHED;
}
void PAINT_OT_vert_select_more(wmOperatorType *ot)
{
ot->name = "Select More";
ot->description = "Select Vertices connected to existing selection";
ot->idname = "PAINT_OT_vert_select_more";
ot->exec = paintvert_select_more_exec;
ot->poll = vert_paint_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_boolean(
ot->srna, "face_step", true, "Face Step", "Also select faces that only touch on a corner");
}
static int paintvert_select_less_exec(bContext *C, wmOperator *op)
{
Object *ob = CTX_data_active_object(C);
Mesh *mesh = BKE_mesh_from_object(ob);
if (mesh == NULL || mesh->totpoly == 0) {
return OPERATOR_CANCELLED;
}
const bool face_step = RNA_boolean_get(op->ptr, "face_step");
paintvert_select_less(mesh, face_step);
paintvert_flush_flags(ob);
paintvert_tag_select_update(C, ob);
ED_region_tag_redraw(CTX_wm_region(C));
return OPERATOR_FINISHED;
}
void PAINT_OT_vert_select_less(wmOperatorType *ot)
{
ot->name = "Select Less";
ot->description = "Deselect Vertices connected to existing selection";
ot->idname = "PAINT_OT_vert_select_less";
ot->exec = paintvert_select_less_exec;
ot->poll = vert_paint_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_boolean(
ot->srna, "face_step", true, "Face Step", "Also deselect faces that only touch on a corner");
}
static int face_select_hide_exec(bContext *C, wmOperator *op)
{
const bool unselected = RNA_boolean_get(op->ptr, "unselected");

View File

@ -182,6 +182,28 @@ static void graph_main_region_init(wmWindowManager *wm, ARegion *region)
WM_event_add_keymap_handler(&region->handlers, keymap);
}
/* Draw a darker area above 1 and below -1. */
static void draw_normalization_borders(Scene *scene, View2D *v2d)
{
GPU_blend(GPU_BLEND_ALPHA);
GPUVertFormat *format = immVertexFormat();
const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
immUniformThemeColorShadeAlpha(TH_BACK, -25, -180);
if (v2d->cur.ymax >= 1) {
immRectf(pos, scene->r.sfra, 1, scene->r.efra, v2d->cur.ymax);
}
if (v2d->cur.ymin <= -1) {
immRectf(pos, scene->r.sfra, v2d->cur.ymin, scene->r.efra, -1);
}
GPU_blend(GPU_BLEND_NONE);
immUnbindProgram();
}
static void graph_main_region_draw(const bContext *C, ARegion *region)
{
/* draw entirely, view changes should be handled here */
@ -207,6 +229,10 @@ static void graph_main_region_draw(const bContext *C, ARegion *region)
ANIM_draw_framerange(scene, v2d);
}
if (sipo->mode == SIPO_MODE_ANIMATION && (sipo->flag & SIPO_NORMALIZE)) {
draw_normalization_borders(scene, v2d);
}
/* draw data */
if (ANIM_animdata_get_context(C, &ac)) {
/* draw ghost curves */

View File

@ -433,8 +433,9 @@ static void nla_draw_strip(SpaceNla *snla,
float yminc,
float ymaxc)
{
const bool non_solo = ((adt && (adt->flag & ADT_NLA_SOLO_TRACK)) &&
(nlt->flag & NLATRACK_SOLO) == 0);
const bool solo = !((adt && (adt->flag & ADT_NLA_SOLO_TRACK)) &&
(nlt->flag & NLATRACK_SOLO) == 0);
const bool muted = ((nlt->flag & NLATRACK_MUTED) || (strip->flag & NLASTRIP_FLAG_MUTED));
float color[4] = {1.0f, 1.0f, 1.0f, 1.0f};
uint shdr_pos;
@ -448,7 +449,7 @@ static void nla_draw_strip(SpaceNla *snla,
/* draw extrapolation info first (as backdrop)
* - but this should only be drawn if track has some contribution
*/
if ((strip->extendmode != NLASTRIP_EXTEND_NOTHING) && (non_solo == 0)) {
if ((strip->extendmode != NLASTRIP_EXTEND_NOTHING) && solo) {
/* enable transparency... */
GPU_blend(GPU_BLEND_ALPHA);
@ -486,7 +487,7 @@ static void nla_draw_strip(SpaceNla *snla,
}
/* draw 'inside' of strip itself */
if (non_solo == 0 && is_nlastrip_enabled(adt, nlt, strip)) {
if (solo && is_nlastrip_enabled(adt, nlt, strip)) {
immUnbindProgram();
/* strip is in normal track */
@ -629,8 +630,9 @@ static void nla_draw_strip_text(AnimData *adt,
float yminc,
float ymaxc)
{
const bool non_solo = ((adt && (adt->flag & ADT_NLA_SOLO_TRACK)) &&
(nlt->flag & NLATRACK_SOLO) == 0);
const bool solo = !((adt && (adt->flag & ADT_NLA_SOLO_TRACK)) &&
(nlt->flag & NLATRACK_SOLO) == 0);
char str[256];
size_t str_len;
uchar col[4];
@ -650,12 +652,12 @@ static void nla_draw_strip_text(AnimData *adt,
else {
col[0] = col[1] = col[2] = 255;
}
// Default strip to 100% opacity.
col[3] = 255;
/* text opacity depends on whether if there's a solo'd track, this isn't it */
if (non_solo == 0) {
col[3] = 255;
}
else {
/* Reduce text opacity if a track is soloed,
* and if target track isn't the soloed track. */
if (!solo) {
col[3] = 128;
}

View File

@ -3480,7 +3480,7 @@ static void do_nurbs_box_select__doSelect(void *userData,
}
}
}
static bool do_nurbs_box_select(ViewContext *vc, rcti *rect, const eSelectOp sel_op)
static bool do_nurbs_box_select(ViewContext *vc, const rcti *rect, const eSelectOp sel_op)
{
const bool deselect_all = (sel_op == SEL_OP_SET);
BoxSelectUserData data;
@ -3520,7 +3520,7 @@ static void do_lattice_box_select__doSelect(void *userData, BPoint *bp, const fl
data->is_changed = true;
}
}
static bool do_lattice_box_select(ViewContext *vc, rcti *rect, const eSelectOp sel_op)
static bool do_lattice_box_select(ViewContext *vc, const rcti *rect, const eSelectOp sel_op)
{
BoxSelectUserData data;
@ -3855,7 +3855,10 @@ static int opengl_bone_select_buffer_cmp(const void *sel_a_p, const void *sel_b_
return 0;
}
static bool do_object_box_select(bContext *C, ViewContext *vc, rcti *rect, const eSelectOp sel_op)
static bool do_object_box_select(bContext *C,
ViewContext *vc,
const rcti *rect,
const eSelectOp sel_op)
{
View3D *v3d = vc->v3d;
int totobj = MAXPICKELEMS; /* XXX solve later */
@ -3928,7 +3931,10 @@ finally:
return changed;
}
static bool do_pose_box_select(bContext *C, ViewContext *vc, rcti *rect, const eSelectOp sel_op)
static bool do_pose_box_select(bContext *C,
ViewContext *vc,
const rcti *rect,
const eSelectOp sel_op)
{
blender::Vector<Base *> bases = do_pose_tag_select_op_prepare(vc);

View File

@ -731,16 +731,27 @@ static eSnapTargetOP snap_target_select_from_spacetype(TransInfo *t)
static void initSnappingMode(TransInfo *t)
{
if (!transformModeUseSnap(t)) {
/* In this case, snapping is always disabled by default. */
t->modifiers &= ~MOD_SNAP;
}
if (doForceIncrementSnap(t)) {
t->tsnap.mode = SCE_SNAP_MODE_INCREMENT;
}
if ((t->spacetype != SPACE_VIEW3D) || !(t->tsnap.mode & SCE_SNAP_MODE_FACE_RAYCAST) ||
(t->tsnap.mode & SCE_SNAP_MODE_FACE_NEAREST) || (t->flag & T_NO_PROJECT)) {
if ((t->spacetype != SPACE_VIEW3D) ||
!(t->tsnap.mode & (SCE_SNAP_MODE_FACE_RAYCAST | SCE_SNAP_MODE_FACE_NEAREST)) ||
(t->flag & T_NO_PROJECT)) {
/* Force project off when not supported. */
t->tsnap.flag &= ~SCE_SNAP_PROJECT;
}
if (t->tsnap.mode & SCE_SNAP_MODE_FACE_NEAREST) {
/* This mode only works with individual projection. */
t->tsnap.flag |= SCE_SNAP_PROJECT;
}
setSnappingCallback(t);
if (t->spacetype == SPACE_VIEW3D) {
@ -881,9 +892,9 @@ void initSnapping(TransInfo *t, wmOperator *op)
t->tsnap.source_operation = snap_source;
transform_snap_flag_from_modifiers_set(t);
initSnappingMode(t);
transform_snap_flag_from_modifiers_set(t);
}
void freeSnapping(TransInfo *t)

View File

@ -1287,15 +1287,17 @@ static bool island_has_pins(const Scene *scene,
* \param bmesh_override: BMesh array aligned with `objects`.
* Optional, when non-null this overrides object's BMesh.
* This is needed to perform UV packing on objects that aren't in edit-mode.
* \param udim_params: Parameters to specify UDIM target and UDIM source image.
* \param udim_source_closest: UDIM source SpaceImage.
* \param original_selection: Pack to original selection.
* \param params: Parameters and options to pass to the packing engine.
*/
static void uvedit_pack_islands_multi(const Scene *scene,
Object **objects,
const int objects_len,
BMesh **bmesh_override,
const UVMapUDIM_Params *closest_udim,
const blender::geometry::UVPackIsland_Params *params)
const SpaceImage *udim_source_closest,
const bool original_selection,
blender::geometry::UVPackIsland_Params *params)
{
blender::Vector<FaceIsland *> island_vector;
@ -1357,12 +1359,10 @@ static void uvedit_pack_islands_multi(const Scene *scene,
for (int index = 0; index < island_vector.size(); index++) {
FaceIsland *island = island_vector[index];
if (closest_udim) {
/* Only calculate selection bounding box if using closest_udim. */
for (int i = 0; i < island->faces_len; i++) {
BMFace *f = island->faces[i];
BM_face_uv_minmax(f, selection_min_co, selection_max_co, island->offsets.uv);
}
for (int i = 0; i < island->faces_len; i++) {
BMFace *f = island->faces[i];
BM_face_uv_minmax(f, selection_min_co, selection_max_co, island->offsets.uv);
}
if (params->rotate) {
@ -1375,9 +1375,15 @@ static void uvedit_pack_islands_multi(const Scene *scene,
/* Center of bounding box containing all selected UVs. */
float selection_center[2];
if (closest_udim) {
selection_center[0] = (selection_min_co[0] + selection_max_co[0]) / 2.0f;
selection_center[1] = (selection_min_co[1] + selection_max_co[1]) / 2.0f;
mid_v2_v2v2(selection_center, selection_min_co, selection_max_co);
if (original_selection) {
/* Protect against degenerate source AABB. */
if ((selection_max_co[0] - selection_min_co[0]) * (selection_max_co[1] - selection_min_co[1]) >
1e-40f) {
params->target_aspect_y = (selection_max_co[0] - selection_min_co[0]) /
(selection_max_co[1] - selection_min_co[1]);
}
}
MemArena *arena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
@ -1415,14 +1421,15 @@ static void uvedit_pack_islands_multi(const Scene *scene,
}
BLI_heap_free(heap, nullptr);
BLI_memarena_free(arena);
pack_islands(pack_island_vector, *params, scale);
float base_offset[2] = {0.0f, 0.0f};
copy_v2_v2(base_offset, params->udim_base_offset);
if (closest_udim) {
const Image *image = closest_udim->image;
const int *udim_grid = closest_udim->grid_shape;
if (udim_source_closest) {
const Image *image = udim_source_closest->image;
const int *udim_grid = udim_source_closest->tile_grid_shape;
/* Check if selection lies on a valid UDIM grid tile. */
bool is_valid_udim = uv_coords_isect_udim(image, udim_grid, selection_center);
if (is_valid_udim) {
@ -1457,12 +1464,7 @@ static void uvedit_pack_islands_multi(const Scene *scene,
for (int64_t i : pack_island_vector.index_range()) {
blender::geometry::PackIsland *pack_island = pack_island_vector[i];
FaceIsland *island = island_vector[pack_island->caller_index];
const float cos_angle = cosf(pack_island->angle);
const float sin_angle = sinf(pack_island->angle);
matrix[0][0] = cos_angle * scale[0];
matrix[0][1] = -sin_angle * scale[0] * pack_island->aspect_y;
matrix[1][0] = sin_angle * scale[1] / pack_island->aspect_y;
matrix[1][1] = cos_angle * scale[1];
pack_island->build_transformation(scale[0], pack_island->angle, matrix);
invert_m2_m2(matrix_inverse, matrix);
/* Add base_offset, post transform. */
@ -1475,6 +1477,20 @@ static void uvedit_pack_islands_multi(const Scene *scene,
/* Perform the transformation. */
island_uv_transform(island, matrix, pre_translate);
if (original_selection) {
const float rescale_x = (selection_max_co[0] - selection_min_co[0]) /
params->target_aspect_y;
const float rescale_y = (selection_max_co[1] - selection_min_co[1]);
const float rescale = std::min(rescale_x, rescale_y);
matrix[0][0] = rescale;
matrix[0][1] = 0.0f;
matrix[1][0] = 0.0f;
matrix[1][1] = rescale;
pre_translate[0] = selection_min_co[0] / rescale;
pre_translate[1] = selection_min_co[1] / rescale;
island_uv_transform(island, matrix, pre_translate);
}
/* Cleanup memory. */
pack_island_vector[i] = nullptr;
delete pack_island;
@ -1499,7 +1515,8 @@ static void uvedit_pack_islands_multi(const Scene *scene,
/* Packing targets. */
enum {
PACK_UDIM_SRC_CLOSEST = 0,
PACK_UDIM_SRC_ACTIVE = 1,
PACK_UDIM_SRC_ACTIVE,
PACK_ORIGINAL_AABB,
};
static int pack_islands_exec(bContext *C, wmOperator *op)
@ -1544,21 +1561,17 @@ static int pack_islands_exec(bContext *C, wmOperator *op)
pack_island_params.shape_method = eUVPackIsland_ShapeMethod(
RNA_enum_get(op->ptr, "shape_method"));
UVMapUDIM_Params closest_udim_buf;
UVMapUDIM_Params *closest_udim = nullptr;
if (udim_source == PACK_UDIM_SRC_ACTIVE) {
pack_island_params.setUDIMOffsetFromSpaceImage(sima);
}
else if (sima) {
BLI_assert(udim_source == PACK_UDIM_SRC_CLOSEST);
closest_udim = &closest_udim_buf;
closest_udim->image = sima->image;
closest_udim->grid_shape[0] = sima->tile_grid_shape[0];
closest_udim->grid_shape[1] = sima->tile_grid_shape[1];
}
uvedit_pack_islands_multi(
scene, objects, objects_len, nullptr, closest_udim, &pack_island_params);
uvedit_pack_islands_multi(scene,
objects,
objects_len,
nullptr,
(udim_source == PACK_UDIM_SRC_CLOSEST) ? sima : nullptr,
(udim_source == PACK_ORIGINAL_AABB),
&pack_island_params);
MEM_freeN(objects);
return OPERATOR_FINISHED;
@ -1596,6 +1609,11 @@ void UV_OT_pack_islands(wmOperatorType *ot)
0,
"Active UDIM",
"Pack islands to active UDIM image tile or UDIM grid tile where 2D cursor is located"},
{PACK_ORIGINAL_AABB,
"ORIGINAL_AABB",
0,
"Original bounding box",
"Pack to starting bounding box of islands"},
{0, nullptr, 0, nullptr, nullptr},
};
/* identifiers */
@ -2341,7 +2359,8 @@ void ED_uvedit_live_unwrap(const Scene *scene, Object **objects, int objects_len
pack_island_params.margin_method = ED_UVPACK_MARGIN_SCALED;
pack_island_params.margin = scene->toolsettings->uvcalc_margin;
uvedit_pack_islands_multi(scene, objects, objects_len, nullptr, nullptr, &pack_island_params);
uvedit_pack_islands_multi(
scene, objects, objects_len, nullptr, nullptr, false, &pack_island_params);
}
}
@ -2483,7 +2502,8 @@ static int unwrap_exec(bContext *C, wmOperator *op)
RNA_enum_get(op->ptr, "margin_method"));
pack_island_params.margin = RNA_float_get(op->ptr, "margin");
uvedit_pack_islands_multi(scene, objects, objects_len, nullptr, nullptr, &pack_island_params);
uvedit_pack_islands_multi(
scene, objects, objects_len, nullptr, nullptr, false, &pack_island_params);
MEM_freeN(objects);
@ -2864,7 +2884,7 @@ static int smart_project_exec(bContext *C, wmOperator *op)
params.margin = RNA_float_get(op->ptr, "island_margin");
uvedit_pack_islands_multi(
scene, objects_changed, object_changed_len, nullptr, nullptr, &params);
scene, objects_changed, object_changed_len, nullptr, nullptr, false, &params);
/* #uvedit_pack_islands_multi only supports `per_face_aspect = false`. */
const bool per_face_aspect = false;
@ -3852,7 +3872,7 @@ void ED_uvedit_add_simple_uvs(Main *bmain, const Scene *scene, Object *ob)
params.margin_method = ED_UVPACK_MARGIN_SCALED;
params.margin = 0.001f;
uvedit_pack_islands_multi(scene, &ob, 1, &bm, nullptr, &params);
uvedit_pack_islands_multi(scene, &ob, 1, &bm, nullptr, false, &params);
/* Write back from BMesh to Mesh. */
BMeshToMeshParams bm_to_me_params{};

View File

@ -250,7 +250,7 @@ struct Output {
*/
class LazyFunction {
protected:
const char *debug_name_ = "<unknown>";
const char *debug_name_ = "unknown";
Vector<Input> inputs_;
Vector<Output> outputs_;
/**

View File

@ -18,6 +18,7 @@ set(SRC
intern/add_curves_on_mesh.cc
intern/curve_constraints.cc
intern/fillet_curves.cc
intern/mesh_flip_faces.cc
intern/mesh_merge_by_distance.cc
intern/mesh_primitive_cuboid.cc
intern/mesh_split_edges.cc
@ -37,6 +38,7 @@ set(SRC
GEO_add_curves_on_mesh.hh
GEO_curve_constraints.hh
GEO_fillet_curves.hh
GEO_mesh_flip_faces.hh
GEO_mesh_merge_by_distance.hh
GEO_mesh_primitive_cuboid.hh
GEO_mesh_split_edges.hh

View File

@ -0,0 +1,17 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_index_mask.hh"
struct Mesh;
namespace blender::geometry {
/**
* Reverse the order of the vertices in the selected faces. This effectively changes the
* direction of the face normal.
*/
void flip_faces(Mesh &mesh, const IndexMask &selection);
} // namespace blender::geometry

View File

@ -66,14 +66,14 @@ class UVPackIsland_Params {
eUVPackIsland_MarginMethod margin_method;
/** Additional translation for bottom left corner. */
float udim_base_offset[2];
/** Target aspect ratio. */
float target_aspect_y;
/** Which shape to use when packing. */
eUVPackIsland_ShapeMethod shape_method;
};
class PackIsland {
public:
/** Calculated automatically. */
rctf bounds_rect;
/** Aspect ratio, required for rotation. */
float aspect_y;
/** Output pre-translation. */
@ -87,8 +87,16 @@ class PackIsland {
void add_polygon(const blender::Span<float2> uvs, MemArena *arena, Heap *heap);
void finalize_geometry(const UVPackIsland_Params &params, MemArena *arena, Heap *heap);
void build_transformation(const float scale, const float angle, float r_matrix[2][2]);
void build_inverse_transformation(const float scale, const float angle, float r_matrix[2][2]);
/** Center of AABB and inside-or-touching the convex hull. */
float2 pivot_;
/** Half of the diagonal of the AABB. */
float2 half_diagonal_;
private:
void calculate_pivot(); /* Choose a pivot based on triangles. */
void calculate_pivot(); /* Calculate `pivot_` and `half_diagonal_` based on added triangles. */
blender::Vector<float2> triangle_vertices_;
friend class Occupancy;
};

View File

@ -0,0 +1,67 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "GEO_mesh_flip_faces.hh"
#include "DNA_mesh_types.h"
#include "BLI_task.hh"
#include "BKE_attribute.hh"
#include "BKE_attribute_math.hh"
#include "BKE_mesh.hh"
namespace blender::geometry {
void flip_faces(Mesh &mesh, const IndexMask &selection)
{
if (mesh.totpoly == 0 || selection.is_empty()) {
return;
}
const Span<MPoly> polys = mesh.polys();
MutableSpan<int> corner_verts = mesh.corner_verts_for_write();
MutableSpan<int> corner_edges = mesh.corner_edges_for_write();
threading::parallel_for(selection.index_range(), 1024, [&](const IndexRange range) {
for (const int i : selection.slice(range)) {
const IndexRange poly(polys[i].loopstart, polys[i].totloop);
for (const int j : IndexRange(poly.size() / 2)) {
const int a = poly[j + 1];
const int b = poly.last(j);
std::swap(corner_verts[a], corner_verts[b]);
std::swap(corner_edges[a - 1], corner_edges[b]);
}
}
});
bke::MutableAttributeAccessor attributes = mesh.attributes_for_write();
attributes.for_all(
[&](const bke::AttributeIDRef &attribute_id, const bke::AttributeMetaData &meta_data) {
if (meta_data.data_type == CD_PROP_STRING) {
return true;
}
if (meta_data.domain != ATTR_DOMAIN_CORNER) {
return true;
}
if (ELEM(attribute_id.name(), ".corner_vert", ".corner_edge")) {
return true;
}
bke::GSpanAttributeWriter attribute = attributes.lookup_for_write_span(attribute_id);
attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) {
using T = decltype(dummy);
MutableSpan<T> dst_span = attribute.span.typed<T>();
threading::parallel_for(selection.index_range(), 1024, [&](const IndexRange range) {
for (const int i : selection.slice(range)) {
const IndexRange poly(polys[i].loopstart, polys[i].totloop);
dst_span.slice(poly.drop_front(1)).reverse();
}
});
});
attribute.finish();
return true;
});
BKE_mesh_tag_topology_changed(&mesh);
}
} // namespace blender::geometry

View File

@ -163,15 +163,8 @@ void PackIsland::finalize_geometry(const UVPackIsland_Params &params, MemArena *
void PackIsland::calculate_pivot()
{
Bounds<float2> triangle_bounds = *bounds::min_max(triangle_vertices_.as_span());
float2 aabb_min = triangle_bounds.min;
float2 aabb_max = triangle_bounds.max;
float2 pivot = (aabb_min + aabb_max) * 0.5f;
float2 half_diagonal = (aabb_max - aabb_min) * 0.5f;
bounds_rect.xmin = pivot.x - half_diagonal.x;
bounds_rect.ymin = pivot.y - half_diagonal.y;
bounds_rect.xmax = pivot.x + half_diagonal.x;
bounds_rect.ymax = pivot.y + half_diagonal.y;
pivot_ = (triangle_bounds.min + triangle_bounds.max) * 0.5f;
half_diagonal_ = (triangle_bounds.max - triangle_bounds.min) * 0.5f;
}
UVPackIsland_Params::UVPackIsland_Params()
@ -188,6 +181,7 @@ UVPackIsland_Params::UVPackIsland_Params()
margin_method = ED_UVPACK_MARGIN_SCALED;
udim_base_offset[0] = 0.0f;
udim_base_offset[1] = 0.0f;
target_aspect_y = 1.0f;
shape_method = ED_UVPACK_SHAPE_AABB;
}
@ -212,21 +206,22 @@ class UVAABBIsland {
* the input must be pre-sorted, which costs an additional `O(nlogn)` time complexity.
*/
static void pack_islands_alpaca_turbo(const Span<UVAABBIsland *> islands,
const float target_aspect_y,
float *r_max_u,
float *r_max_v)
{
/* Exclude an initial AABB near the origin. */
float next_u1 = *r_max_u;
float next_v1 = *r_max_v;
bool zigzag = next_u1 < next_v1; /* Horizontal or Vertical strip? */
bool zigzag = next_u1 / target_aspect_y < next_v1; /* Horizontal or Vertical strip? */
float u0 = zigzag ? next_u1 : 0.0f;
float v0 = zigzag ? 0.0f : next_v1;
/* Visit every island in order. */
for (UVAABBIsland *island : islands) {
float dsm_u = island->uv_diagonal.x;
float dsm_v = island->uv_diagonal.y;
const float dsm_u = island->uv_diagonal.x;
const float dsm_v = island->uv_diagonal.y;
bool restart = false;
if (zigzag) {
@ -237,14 +232,14 @@ static void pack_islands_alpaca_turbo(const Span<UVAABBIsland *> islands,
}
if (restart) {
/* We're at the end of a strip. Restart from U axis or V axis. */
zigzag = next_u1 < next_v1;
zigzag = next_u1 / target_aspect_y < next_v1;
u0 = zigzag ? next_u1 : 0.0f;
v0 = zigzag ? 0.0f : next_v1;
}
/* Place the island. */
island->uv_placement.x = u0;
island->uv_placement.y = v0;
island->uv_placement.x = u0 + dsm_u * 0.5f;
island->uv_placement.y = v0 + dsm_v * 0.5f;
if (zigzag) {
/* Move upwards. */
v0 += dsm_v;
@ -264,6 +259,48 @@ static void pack_islands_alpaca_turbo(const Span<UVAABBIsland *> islands,
*r_max_v = next_v1;
}
/* Wrapper around #BLI_box_pack_2d. */
static void pack_island_box_pack_2d(const Span<UVAABBIsland *> aabbs,
const Span<PackIsland *> islands,
const float scale,
const float margin,
const float target_aspect_y,
float *r_max_u,
float *r_max_v)
{
/* Allocate storage. */
BoxPack *box_array = static_cast<BoxPack *>(
MEM_mallocN(sizeof(*box_array) * islands.size(), __func__));
/* Prepare for box_pack_2d. */
for (const int64_t i : aabbs.index_range()) {
PackIsland *island = islands[aabbs[i]->index];
BoxPack *box = box_array + i;
box->w = (island->half_diagonal_.x * 2 * scale + 2 * margin) / target_aspect_y;
box->h = island->half_diagonal_.y * 2 * scale + 2 * margin;
}
const bool sort_boxes = false; /* Use existing ordering from `aabbs`. */
/* \note Writes to `*r_max_u` and `*r_max_v`. */
BLI_box_pack_2d(box_array, int(aabbs.size()), sort_boxes, r_max_u, r_max_v);
*r_max_u *= target_aspect_y;
/* Write back box_pack UVs. */
for (const int64_t i : aabbs.index_range()) {
PackIsland *island = islands[aabbs[i]->index];
BoxPack *box = box_array + i;
island->angle = 0.0f; /* #BLI_box_pack_2d never rotates. */
island->pre_translate.x = (box->x + box->w * 0.5f) * target_aspect_y / scale -
island->pivot_.x;
island->pre_translate.y = (box->y + box->h * 0.5f) / scale - island->pivot_.y;
}
/* Housekeeping. */
MEM_freeN(box_array);
}
/**
* Helper class for the `xatlas` strategy.
* Accelerates geometry queries by approximating exact queries with a bitmap.
@ -410,13 +447,13 @@ float Occupancy::trace_island(PackIsland *island,
{
if (!write) {
if (uv.x <= 0.0f || uv.y <= 0.0f) {
if (uv.x < island->half_diagonal_.x * scale + margin ||
uv.y < island->half_diagonal_.y * scale + margin) {
return terminal; /* Occupied. */
}
}
const float2 origin(island->bounds_rect.xmin, island->bounds_rect.ymin);
const float2 delta = uv - origin * scale;
uint vert_count = uint(island->triangle_vertices_.size());
const float2 delta = uv - island->pivot_ * scale;
uint vert_count = uint(island->triangle_vertices_.size()); /* `uint` is faster than `int`. */
for (uint i = 0; i < vert_count; i += 3) {
uint j = (i + triangle_hint_) % vert_count;
float extent = trace_triangle(delta + island->triangle_vertices_[j] * scale,
@ -433,39 +470,46 @@ float Occupancy::trace_island(PackIsland *island,
return -1.0f; /* Available. */
}
static float2 find_best_fit_for_island(
PackIsland *island, int scan_line, Occupancy &occupancy, const float scale, const float margin)
static float2 find_best_fit_for_island(PackIsland *island,
int scan_line,
Occupancy &occupancy,
const float scale,
const float margin,
const float target_aspect_y)
{
const float scan_line_bscaled = scan_line / occupancy.bitmap_scale_reciprocal;
const float size_x_scaled = BLI_rctf_size_x(&island->bounds_rect) * scale;
const float size_y_scaled = BLI_rctf_size_y(&island->bounds_rect) * scale;
const float bitmap_scale = 1.0f / occupancy.bitmap_scale_reciprocal;
const float half_x_scaled = island->half_diagonal_.x * scale;
const float half_y_scaled = island->half_diagonal_.y * scale;
const float sqrt_target_aspect_y = sqrtf(target_aspect_y);
int scan_line_x = int(scan_line * sqrt_target_aspect_y);
int scan_line_y = int(scan_line / sqrt_target_aspect_y);
/* Scan using an "Alpaca"-style search, first horizontally using "less-than". */
int t = int(ceilf(size_x_scaled * occupancy.bitmap_scale_reciprocal));
while (t < scan_line) {
const float t_bscaled = t / occupancy.bitmap_scale_reciprocal;
const float2 probe(t_bscaled - size_x_scaled, scan_line_bscaled - size_y_scaled);
int t = int(ceilf((2 * half_x_scaled + margin) * occupancy.bitmap_scale_reciprocal));
while (t < scan_line_x) {
const float2 probe(t * bitmap_scale - half_x_scaled,
scan_line_y * bitmap_scale - half_y_scaled);
const float extent = occupancy.trace_island(island, scale, margin, probe, false);
if (extent < 0.0f) {
return probe;
return probe; /* Success. */
}
t = t + std::max(1, int(extent));
}
/* Then scan vertically using "less-than-or-equal" */
t = int(ceilf(size_y_scaled * occupancy.bitmap_scale_reciprocal));
while (t <= scan_line) {
const float t_bscaled = t / occupancy.bitmap_scale_reciprocal;
const float2 probe(scan_line_bscaled - size_x_scaled, t_bscaled - size_y_scaled);
t = int(ceilf((2 * half_y_scaled + margin) * occupancy.bitmap_scale_reciprocal));
while (t <= scan_line_y) {
const float2 probe(scan_line_x * bitmap_scale - half_x_scaled,
t * bitmap_scale - half_y_scaled);
const float extent = occupancy.trace_island(island, scale, margin, probe, false);
if (extent < 0.0f) {
return probe;
return probe; /* Success. */
}
t = t + std::max(1, int(extent));
}
return float2(-1, -1);
return float2(-1, -1); /* Unable to find a place to fit. */
}
static float guess_initial_scale(const Span<PackIsland *> islands,
@ -475,8 +519,8 @@ static float guess_initial_scale(const Span<PackIsland *> islands,
float sum = 1e-40f;
for (int64_t i : islands.index_range()) {
PackIsland *island = islands[i];
sum += BLI_rctf_size_x(&island->bounds_rect) * scale + 2 * margin;
sum += BLI_rctf_size_y(&island->bounds_rect) * scale + 2 * margin;
sum += island->half_diagonal_.x * 2 * scale + 2 * margin;
sum += island->half_diagonal_.y * 2 * scale + 2 * margin;
}
return sqrtf(sum) / 6.0f;
}
@ -498,9 +542,9 @@ static float guess_initial_scale(const Span<PackIsland *> islands,
*/
static void pack_island_xatlas(const Span<UVAABBIsland *> island_indices,
const Span<PackIsland *> islands,
BoxPack *box_array,
const float scale,
const float margin,
const float target_aspect_y,
float *r_max_u,
float *r_max_v)
{
@ -519,7 +563,8 @@ static void pack_island_xatlas(const Span<UVAABBIsland *> island_indices,
while (i < island_indices.size()) {
PackIsland *island = islands[island_indices[i]->index];
const float2 best = find_best_fit_for_island(island, scan_line, occupancy, scale, margin);
const float2 best = find_best_fit_for_island(
island, scan_line, occupancy, scale, margin, target_aspect_y);
if (best.x <= -1.0f) {
/* Unable to find a fit on this scan_line. */
@ -535,7 +580,8 @@ static void pack_island_xatlas(const Span<UVAABBIsland *> island_indices,
* choice is 2. */
scan_line += 2;
}
if (scan_line < occupancy.bitmap_radix) {
if (scan_line <
occupancy.bitmap_radix * sqrtf(std::min(target_aspect_y, 1.0f / target_aspect_y))) {
continue; /* Try again on next scan_line. */
}
@ -545,22 +591,22 @@ static void pack_island_xatlas(const Span<UVAABBIsland *> island_indices,
/* Redraw already placed islands. (Greedy.) */
for (int j = 0; j < i; j++) {
BoxPack *box = box_array + j;
occupancy.trace_island(
islands[island_indices[j]->index], scale, margin, float2(box->x, box->y), true);
PackIsland *other = islands[island_indices[j]->index];
float2 where = (other->pre_translate + other->pivot_) * scale;
occupancy.trace_island(islands[island_indices[j]->index], scale, margin, where, true);
}
continue;
}
/* Place island. */
BoxPack *box = box_array + i;
box->x = best.x;
box->y = best.y;
max_u = std::max(box->x + BLI_rctf_size_x(&island->bounds_rect) * scale + 2 * margin, max_u);
max_v = std::max(box->y + BLI_rctf_size_y(&island->bounds_rect) * scale + 2 * margin, max_v);
occupancy.trace_island(island, scale, margin, float2(box->x, box->y), true);
island->angle = 0.0f;
island->pre_translate = best / scale - island->pivot_;
occupancy.trace_island(island, scale, margin, best, true);
i++; /* Next island. */
max_u = std::max(best.x + island->half_diagonal_.x * scale + margin, max_u);
max_v = std::max(best.y + island->half_diagonal_.y * scale + margin, max_v);
if (i < 128 || (i & 31) == 16) {
scan_line = 0; /* Restart completely. */
}
@ -627,13 +673,14 @@ static void update_hole_rotate(float2 &hole,
* Tracking the "Hole" has a slight performance cost, while improving packing efficiency.
*/
static void pack_islands_alpaca_rotate(const Span<UVAABBIsland *> islands,
const float target_aspect_y,
float *r_max_u,
float *r_max_v)
{
/* Exclude an initial AABB near the origin. */
float next_u1 = *r_max_u;
float next_v1 = *r_max_v;
bool zigzag = next_u1 < next_v1; /* Horizontal or Vertical strip? */
bool zigzag = next_u1 / target_aspect_y < next_v1; /* Horizontal or Vertical strip? */
/* Track an AABB "hole" which may be filled at any time. */
float2 hole(0.0f);
@ -654,9 +701,13 @@ static void pack_islands_alpaca_rotate(const Span<UVAABBIsland *> islands,
island->uv_placement.y = hole[1];
if (hole_rotate == (min_dsm == island->uv_diagonal.x)) {
island->angle = DEG2RADF(90.0f);
island->uv_placement.x += max_dsm * 0.5f;
island->uv_placement.y += min_dsm * 0.5f;
}
else {
island->angle = 0.0f;
island->uv_placement.x += min_dsm * 0.5f;
island->uv_placement.y += max_dsm * 0.5f;
}
/* Update space left in the hole. */
@ -685,20 +736,24 @@ static void pack_islands_alpaca_rotate(const Span<UVAABBIsland *> islands,
if (restart) {
update_hole_rotate(hole, hole_diagonal, hole_rotate, u0, v0, next_u1, next_v1);
/* We're at the end of a strip. Restart from U axis or V axis. */
zigzag = next_u1 < next_v1;
zigzag = next_u1 / target_aspect_y < next_v1;
u0 = zigzag ? next_u1 : 0.0f;
v0 = zigzag ? 0.0f : next_v1;
}
/* Place the island. */
island->uv_placement.x = u0;
island->uv_placement.y = v0;
if (zigzag == (min_dsm == island->uv_diagonal.x)) {
island->angle = DEG2RADF(90.0f);
island->uv_placement.x += max_dsm * 0.5f;
island->uv_placement.y += min_dsm * 0.5f;
}
else {
island->angle = 0.0f;
island->uv_placement.x += min_dsm * 0.5f;
island->uv_placement.y += max_dsm * 0.5f;
}
island->uv_placement.x = u0;
island->uv_placement.y = v0;
/* Move according to the "Alpaca rules", with rotation. */
if (zigzag) {
@ -720,8 +775,15 @@ static void pack_islands_alpaca_rotate(const Span<UVAABBIsland *> islands,
*r_max_v = next_v1;
}
/**
* Pack islands using a mix of other strategies.
* \param islands: The islands to be packed. Will be modified with results.
* \param scale: Scale islands by `scale` before packing.
* \param margin: Add `margin` units around islands before packing.
* \param params: Additional parameters. Scale and margin information is ignored.
* \return Size of square covering the resulting packed UVs. The maximum `u` or `v` co-ordinate.
*/
static float pack_islands_scale_margin(const Span<PackIsland *> islands,
BoxPack *box_array,
const float scale,
const float margin,
const UVPackIsland_Params &params)
@ -751,8 +813,8 @@ static float pack_islands_scale_margin(const Span<PackIsland *> islands,
PackIsland *pack_island = islands[i];
UVAABBIsland *aabb = new UVAABBIsland();
aabb->index = i;
aabb->uv_diagonal.x = BLI_rctf_size_x(&pack_island->bounds_rect) * scale + 2 * margin;
aabb->uv_diagonal.y = BLI_rctf_size_y(&pack_island->bounds_rect) * scale + 2 * margin;
aabb->uv_diagonal.x = pack_island->half_diagonal_.x * 2 * scale + 2 * margin;
aabb->uv_diagonal.y = pack_island->half_diagonal_.y * 2 * scale + 2 * margin;
aabbs[i] = aabb;
if (pack_island->aspect_y != 1.0f) {
contains_non_square_islands = true;
@ -802,15 +864,6 @@ static float pack_islands_scale_margin(const Span<PackIsland *> islands,
}
const int64_t max_box_pack = std::min(alpaca_cutoff, islands.size());
/* Prepare for box_pack_2d. */
for (const int64_t i : islands.index_range()) {
UVAABBIsland *aabb = aabbs[i];
BoxPack *box = &box_array[i];
box->index = int(aabb->index);
box->w = aabb->uv_diagonal.x;
box->h = aabb->uv_diagonal.y;
}
/* Call box_pack_2d (slow for large N.) */
float max_u = 0.0f;
float max_v = 0.0f;
@ -819,36 +872,44 @@ static float pack_islands_scale_margin(const Span<PackIsland *> islands,
case ED_UVPACK_SHAPE_CONCAVE:
pack_island_xatlas(aabbs.as_span().take_front(max_box_pack),
islands,
box_array,
scale,
margin,
params.target_aspect_y,
&max_u,
&max_v);
break;
default:
BLI_box_pack_2d(box_array, int(max_box_pack), false, &max_u, &max_v);
pack_island_box_pack_2d(aabbs.as_span().take_front(max_box_pack),
islands,
scale,
margin,
params.target_aspect_y,
&max_u,
&max_v);
break;
}
/* At this stage, `max_u` and `max_v` contain the box_pack UVs. */
/* At this stage, `max_u` and `max_v` contain the box_pack/xatlas UVs. */
/* Call Alpaca. */
if (params.rotate && !contains_non_square_islands) {
pack_islands_alpaca_rotate(aabbs.as_mutable_span().drop_front(max_box_pack), &max_u, &max_v);
pack_islands_alpaca_rotate(
aabbs.as_mutable_span().drop_front(max_box_pack), params.target_aspect_y, &max_u, &max_v);
}
else {
pack_islands_alpaca_turbo(aabbs.as_mutable_span().drop_front(max_box_pack), &max_u, &max_v);
pack_islands_alpaca_turbo(
aabbs.as_mutable_span().drop_front(max_box_pack), params.target_aspect_y, &max_u, &max_v);
}
/* Write back Alpaca UVs. */
for (int64_t i = max_box_pack; i < aabbs.size(); i++) {
UVAABBIsland *aabb = aabbs[i];
BoxPack *box = &box_array[i];
box->x = aabb->uv_placement.x;
box->y = aabb->uv_placement.y;
PackIsland *pack_island = islands[aabb->index];
pack_island->angle = aabb->angle;
float matrix_inverse[2][2];
pack_island->build_inverse_transformation(scale, pack_island->angle, matrix_inverse);
mul_v2_m2v2(pack_island->pre_translate, matrix_inverse, aabb->uv_placement);
pack_island->pre_translate -= pack_island->pivot_;
}
/* Memory management. */
@ -858,11 +919,13 @@ static float pack_islands_scale_margin(const Span<PackIsland *> islands,
delete aabb;
}
return std::max(max_u, max_v);
return std::max(max_u / params.target_aspect_y, max_v);
}
/** Find the optimal scale to pack islands into the unit square.
* returns largest scale that will pack `islands` into the unit square.
*/
static float pack_islands_margin_fraction(const Span<PackIsland *> &island_vector,
BoxPack *box_array,
const float margin_fraction,
const UVPackIsland_Params &params)
{
@ -932,7 +995,7 @@ static float pack_islands_margin_fraction(const Span<PackIsland *> &island_vecto
/* Evaluate our `f`. */
scale_last = scale;
const float max_uv = pack_islands_scale_margin(
island_vector, box_array, scale_last, margin_fraction, params);
island_vector, scale_last, margin_fraction, params);
const float value = sqrtf(max_uv) - 1.0f;
if (value <= 0.0f) {
@ -952,25 +1015,15 @@ static float pack_islands_margin_fraction(const Span<PackIsland *> &island_vecto
const bool flush = true;
if (flush) {
/* Write back best pack as a side-effect. First get best pack. */
/* Write back best pack as a side-effect. */
if (scale_last != scale_low) {
scale_last = scale_low;
const float max_uv = pack_islands_scale_margin(
island_vector, box_array, scale_last, margin_fraction, params);
island_vector, scale_last, margin_fraction, params);
BLI_assert(max_uv == value_low);
UNUSED_VARS(max_uv);
/* TODO (?): `if (max_uv < 1.0f) { scale_last /= max_uv; }` */
}
/* Then expand PackIslands by the correct amount. */
for (const int64_t index : island_vector.index_range()) {
BoxPack *box = &box_array[index];
box->x /= scale_last;
box->y /= scale_last;
PackIsland *island = island_vector[index];
BLI_rctf_pad(
&island->bounds_rect, margin_fraction / scale_last, margin_fraction / scale_last);
}
}
return scale_last;
}
@ -983,8 +1036,8 @@ static float calc_margin_from_aabb_length_sum(const Span<PackIsland *> &island_v
* `aabb_length_sum` (was "`area`") to multiply the margin by the length (was "area"). */
double aabb_length_sum = 0.0f;
for (PackIsland *island : island_vector) {
float w = BLI_rctf_size_x(&island->bounds_rect);
float h = BLI_rctf_size_y(&island->bounds_rect);
float w = island->half_diagonal_.x * 2.0f;
float h = island->half_diagonal_.y * 2.0f;
aabb_length_sum += sqrtf(w * h);
}
return params.margin * aabb_length_sum * 0.1f;
@ -993,31 +1046,26 @@ static float calc_margin_from_aabb_length_sum(const Span<PackIsland *> &island_v
/* -------------------------------------------------------------------- */
/** \name Implement `pack_islands`
*
* Smooth differences between old API and new API by converting between storage representations.
* \{ */
static BoxPack *pack_islands_box_array(const Span<PackIsland *> &islands,
const UVPackIsland_Params &params,
float r_scale[2])
void pack_islands(const Span<PackIsland *> &islands,
const UVPackIsland_Params &params,
float r_scale[2])
{
BoxPack *box_array = static_cast<BoxPack *>(
MEM_mallocN(sizeof(*box_array) * islands.size(), __func__));
if (params.margin == 0.0f) {
/* Special case for zero margin. Margin_method is ignored as all formulas give same result. */
const float max_uv = pack_islands_scale_margin(islands, box_array, 1.0f, 0.0f, params);
const float max_uv = pack_islands_scale_margin(islands, 1.0f, 0.0f, params);
r_scale[0] = 1.0f / max_uv;
r_scale[1] = r_scale[0];
return box_array;
return;
}
if (params.margin_method == ED_UVPACK_MARGIN_FRACTION) {
/* Uses a line search on scale. ~10x slower than other method. */
const float scale = pack_islands_margin_fraction(islands, box_array, params.margin, params);
const float scale = pack_islands_margin_fraction(islands, params.margin, params);
r_scale[0] = scale;
r_scale[1] = scale;
/* pack_islands_margin_fraction will pad PackIslands, return early. */
return box_array;
return;
}
float margin = params.margin;
@ -1034,40 +1082,30 @@ static BoxPack *pack_islands_box_array(const Span<PackIsland *> &islands,
BLI_assert_unreachable();
}
const float max_uv = pack_islands_scale_margin(islands, box_array, 1.0f, margin, params);
const float max_uv = pack_islands_scale_margin(islands, 1.0f, margin, params);
r_scale[0] = 1.0f / max_uv;
r_scale[1] = r_scale[0];
for (int index = 0; index < islands.size(); index++) {
PackIsland *island = islands[index];
BLI_rctf_pad(&island->bounds_rect, margin, margin);
}
return box_array;
}
void pack_islands(const Span<PackIsland *> &islands,
const UVPackIsland_Params &params,
float r_scale[2])
{
BoxPack *box_array = pack_islands_box_array(islands, params, r_scale);
for (int64_t i : islands.index_range()) {
BoxPack *box = box_array + i;
PackIsland *island = islands[box->index];
if (island->angle) {
/* TODO: Apply proper rotation. */
island->pre_translate.x = (-box->y / island->aspect_y) - island->bounds_rect.xmax;
island->pre_translate.y = (box->x * island->aspect_y) - island->bounds_rect.ymin;
}
else {
island->pre_translate.x = box->x - island->bounds_rect.xmin;
island->pre_translate.y = box->y - island->bounds_rect.ymin;
}
}
MEM_freeN(box_array);
}
/** \} */
void PackIsland::build_transformation(const float scale, const float angle, float (*r_matrix)[2])
{
const float cos_angle = cosf(angle);
const float sin_angle = sinf(angle);
r_matrix[0][0] = cos_angle * scale;
r_matrix[0][1] = -sin_angle * scale * aspect_y;
r_matrix[1][0] = sin_angle * scale / aspect_y;
r_matrix[1][1] = cos_angle * scale;
}
void PackIsland::build_inverse_transformation(const float scale,
const float angle,
float (*r_matrix)[2])
{
/* TODO: Generate inverse transform directly. */
build_transformation(scale, angle, r_matrix);
invert_m2_m2(r_matrix, r_matrix);
}
} // namespace blender::geometry

View File

@ -444,10 +444,11 @@ static void uv_parametrizer_scale_x(ParamHandle *phandle, const float scale_x)
return; /* Identity transform. */
}
/* Scale every chart. */
for (int i = 0; i < phandle->ncharts; i++) {
PChart *chart = phandle->charts[i];
for (PVert *v = chart->verts; v; v = v->nextlink) {
v->uv[0] *= scale_x;
v->uv[0] *= scale_x; /* Only scale x axis. */
}
}
}
@ -4146,15 +4147,11 @@ void uv_parametrizer_pack(ParamHandle *handle, float margin, bool do_rotate, boo
MemArena *arena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
Heap *heap = BLI_heap_new();
int unpacked = 0;
for (int i = 0; i < handle->ncharts; i++) {
PChart *chart = handle->charts[i];
if (ignore_pinned && chart->has_pins) {
unpacked++;
continue;
}
(void)unpacked; /* Quiet set-but-unused warning (may be removed). */
geometry::PackIsland *pack_island = new geometry::PackIsland();
pack_island->caller_index = i;
@ -4182,17 +4179,9 @@ void uv_parametrizer_pack(ParamHandle *handle, float margin, bool do_rotate, boo
PChart *chart = handle->charts[pack_island->caller_index];
float matrix[2][2];
float b[2];
const float cos_angle = cosf(pack_island->angle);
const float sin_angle = sinf(pack_island->angle);
matrix[0][0] = cos_angle * scale[0];
matrix[0][1] = -sin_angle * scale[0] * pack_island->aspect_y;
matrix[1][0] = sin_angle * scale[1] / pack_island->aspect_y;
matrix[1][1] = cos_angle * scale[1];
b[0] = pack_island->pre_translate.x;
b[1] = pack_island->pre_translate.y;
pack_island->build_transformation(scale[0], pack_island->angle, matrix);
for (PVert *v = chart->verts; v; v = v->nextlink) {
blender::geometry::mul_v2_m2_add_v2v2(v->uv, matrix, v->uv, b);
blender::geometry::mul_v2_m2_add_v2v2(v->uv, matrix, v->uv, pack_island->pre_translate);
}
pack_island_vector[i] = nullptr;

View File

@ -378,7 +378,7 @@ void GPU_framebuffer_config_array(GPUFrameBuffer *gpu_fb,
void GPU_framebuffer_default_size(GPUFrameBuffer *gpu_fb, int width, int height)
{
unwrap(gpu_fb)->size_set(width, height);
unwrap(gpu_fb)->default_size_set(width, height);
}
/* ---------- Viewport & Scissor Region ----------- */

View File

@ -137,6 +137,7 @@ class FrameBuffer {
void *userData);
uint get_bits_per_pixel();
/* Sets the size after creation. */
inline void size_set(int width, int height)
{
width_ = width;
@ -144,6 +145,15 @@ class FrameBuffer {
dirty_state_ = true;
}
/* Sets the size for frame-buffer with no attachments. */
inline void default_size_set(int width, int height)
{
width_ = width;
height_ = height;
dirty_attachments_ = true;
dirty_state_ = true;
}
inline void viewport_set(const int viewport[4])
{
if (!equals_v4v4_int(viewport_, viewport)) {

View File

@ -164,30 +164,30 @@ void BKE_material_defaults_free_gpu()
/** \name Stubs of BKE_customdata.h
* \{ */
int CustomData_get_offset(const struct CustomData * /*data*/, int /*type*/)
int CustomData_get_offset(const struct CustomData * /*data*/, eCustomDataType /*type*/)
{
BLI_assert_unreachable();
return 0;
}
int CustomData_get_named_layer_index(const struct CustomData * /*data*/,
int /*type*/,
eCustomDataType /*type*/,
const char * /*name*/)
{
return -1;
}
int CustomData_get_active_layer_index(const struct CustomData * /*data*/, int /*type*/)
int CustomData_get_active_layer_index(const struct CustomData * /*data*/, eCustomDataType /*type*/)
{
return -1;
}
int CustomData_get_render_layer_index(const struct CustomData * /*data*/, int /*type*/)
int CustomData_get_render_layer_index(const struct CustomData * /*data*/, eCustomDataType /*type*/)
{
return -1;
}
bool CustomData_has_layer(const struct CustomData * /*data*/, int /*type*/)
bool CustomData_has_layer(const struct CustomData * /*data*/, eCustomDataType /*type*/)
{
return false;
}

View File

@ -1041,14 +1041,17 @@ void gpu::MTLTexture::update_sub(
if (texture_.storageMode == MTLStorageModeManaged) {
[blit_encoder synchronizeResource:texture_];
}
[blit_encoder optimizeContentsForGPUAccess:texture_];
}
else {
/* Textures which use MTLStorageModeManaged need to have updated contents
* synced back to CPU to avoid an automatic flush overwriting contents. */
blit_encoder = ctx->main_command_buffer.ensure_begin_blit_encoder();
if (texture_.storageMode == MTLStorageModeManaged) {
blit_encoder = ctx->main_command_buffer.ensure_begin_blit_encoder();
[blit_encoder synchronizeResource:texture_];
}
[blit_encoder optimizeContentsForGPUAccess:texture_];
}
/* Decrement texture reference counts. This ensures temporary texture views are released. */
@ -1110,6 +1113,7 @@ void MTLTexture::update_sub(int offset[3],
if (texture_.storageMode == MTLStorageModeManaged) {
[blit_encoder synchronizeResource:texture_];
}
[blit_encoder optimizeContentsForGPUAccess:texture_];
}
else {
BLI_assert(false);
@ -1230,6 +1234,7 @@ void gpu::MTLTexture::copy_to(Texture *dst)
BLI_assert(mt_dst->d_ == d_);
[blit_encoder copyFromTexture:this->get_metal_handle_base()
toTexture:mt_dst->get_metal_handle_base()];
[blit_encoder optimizeContentsForGPUAccess:mt_dst->get_metal_handle_base()];
} break;
default: {
int slice = 0;

View File

@ -140,11 +140,9 @@ TEST(obj_import_string_utils, parse_float_invalid)
EXPECT_EQ(val, -1.0f);
EXPECT_STRREF_EQ("..5", parse_float("..5", -2.0f, val));
EXPECT_EQ(val, -2.0f);
/* Out of float range. Current float parser (fast_float)
* clamps out of range numbers to +/- infinity, so this
* one gets a +inf instead of fallback -3.0. */
/* Out of float range. */
EXPECT_STRREF_EQ(" a", parse_float("9.0e500 a", -3.0f, val));
EXPECT_EQ(val, std::numeric_limits<float>::infinity());
EXPECT_EQ(val, -3.0f);
/* Has leading white-space when we don't expect it */
EXPECT_STRREF_EQ(" 1", parse_float(" 1", -4.0f, val, false));
EXPECT_EQ(val, -4.0f);

View File

@ -2307,7 +2307,7 @@ typedef enum eSnapMode {
/* Due to dependency conflicts with Cycles, header cannot directly include `BLI_utildefines.h`. */
/* TODO: move this macro to a more general place. */
#ifdef ENUM_OPERATORS
ENUM_OPERATORS(eSnapMode, SCE_SNAP_MODE_GRID)
ENUM_OPERATORS(eSnapMode, SCE_SNAP_MODE_FACE_NEAREST)
#endif
#define SCE_SNAP_MODE_GEOM \

View File

@ -3414,7 +3414,8 @@ static void rna_def_tool_settings(BlenderRNA *brna)
RNA_def_property_boolean_sdna(prop, NULL, "snap_flag", SCE_SNAP_PROJECT);
RNA_def_property_ui_text(prop,
"Project Individual Elements",
"Project individual elements on the surface of other objects");
"Project individual elements on the surface of other objects (Always "
"enabled with Face Nearest)");
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); /* header redraw */
prop = RNA_def_property(srna, "use_snap_backface_culling", PROP_BOOLEAN, PROP_NONE);

View File

@ -94,47 +94,9 @@
#include "FN_multi_function.hh"
namespace lf = blender::fn::lazy_function;
namespace geo_log = blender::nodes::geo_eval_log;
using blender::Array;
using blender::ColorGeometry4f;
using blender::CPPType;
using blender::destruct_ptr;
using blender::float3;
using blender::FunctionRef;
using blender::GMutablePointer;
using blender::GMutableSpan;
using blender::GPointer;
using blender::GVArray;
using blender::IndexRange;
using blender::Map;
using blender::MultiValueMap;
using blender::MutableSpan;
using blender::Set;
using blender::Span;
using blender::Stack;
using blender::StringRef;
using blender::StringRefNull;
using blender::Vector;
using blender::bke::AttributeMetaData;
using blender::bke::AttributeValidator;
using blender::fn::Field;
using blender::fn::FieldOperation;
using blender::fn::GField;
using blender::fn::ValueOrField;
using blender::fn::ValueOrFieldCPPType;
using blender::nodes::FieldInferencingInterface;
using blender::nodes::GeoNodeExecParams;
using blender::nodes::InputSocketFieldType;
using blender::nodes::geo_eval_log::GeometryAttributeInfo;
using blender::nodes::geo_eval_log::GeometryInfoLog;
using blender::nodes::geo_eval_log::GeoModifierLog;
using blender::nodes::geo_eval_log::GeoNodeLog;
using blender::nodes::geo_eval_log::GeoTreeLog;
using blender::nodes::geo_eval_log::NamedAttributeUsage;
using blender::nodes::geo_eval_log::NodeWarning;
using blender::nodes::geo_eval_log::NodeWarningType;
using blender::nodes::geo_eval_log::ValueLog;
using blender::threading::EnumerableThreadSpecific;
namespace blender {
static void initData(ModifierData *md)
{
@ -216,22 +178,25 @@ static bool node_needs_own_transform_relation(const bNode &node)
static void process_nodes_for_depsgraph(const bNodeTree &tree,
Set<ID *> &ids,
bool &r_needs_own_transform_relation)
bool &r_needs_own_transform_relation,
Set<const bNodeTree *> &checked_groups)
{
Set<const bNodeTree *> handled_groups;
if (!checked_groups.add(&tree)) {
return;
}
LISTBASE_FOREACH (const bNode *, node, &tree.nodes) {
tree.ensure_topology_cache();
for (const bNode *node : tree.all_nodes()) {
add_used_ids_from_sockets(node->inputs, ids);
add_used_ids_from_sockets(node->outputs, ids);
if (ELEM(node->type, NODE_GROUP, NODE_CUSTOM_GROUP)) {
const bNodeTree *group = (bNodeTree *)node->id;
if (group != nullptr && handled_groups.add(group)) {
process_nodes_for_depsgraph(*group, ids, r_needs_own_transform_relation);
}
}
r_needs_own_transform_relation |= node_needs_own_transform_relation(*node);
}
for (const bNode *node : tree.group_nodes()) {
if (const bNodeTree *sub_tree = reinterpret_cast<const bNodeTree *>(node->id)) {
process_nodes_for_depsgraph(*sub_tree, ids, r_needs_own_transform_relation, checked_groups);
}
}
}
static void find_used_ids_from_settings(const NodesModifierSettings &settings, Set<ID *> &ids)
@ -289,7 +254,9 @@ static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphConte
bool needs_own_transform_relation = false;
Set<ID *> used_ids;
find_used_ids_from_settings(nmd->settings, used_ids);
process_nodes_for_depsgraph(*nmd->node_group, used_ids, needs_own_transform_relation);
Set<const bNodeTree *> checked_groups;
process_nodes_for_depsgraph(
*nmd->node_group, used_ids, needs_own_transform_relation, checked_groups);
if (ctx->object->type == OB_CURVES) {
Curves *curves_id = static_cast<Curves *>(ctx->object->data);
@ -328,19 +295,18 @@ static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphConte
}
}
static bool check_tree_for_time_node(const bNodeTree &tree,
Set<const bNodeTree *> &r_checked_trees)
static bool check_tree_for_time_node(const bNodeTree &tree, Set<const bNodeTree *> &checked_groups)
{
if (!r_checked_trees.add(&tree)) {
if (!checked_groups.add(&tree)) {
return false;
}
LISTBASE_FOREACH (const bNode *, node, &tree.nodes) {
if (node->type == GEO_NODE_INPUT_SCENE_TIME) {
return true;
}
if (node->type == NODE_GROUP) {
const bNodeTree *sub_tree = reinterpret_cast<const bNodeTree *>(node->id);
if (sub_tree && check_tree_for_time_node(*sub_tree, r_checked_trees)) {
tree.ensure_topology_cache();
if (!tree.nodes_by_type("GeometryNodeInputSceneTime").is_empty()) {
return true;
}
for (const bNode *node : tree.group_nodes()) {
if (const bNodeTree *sub_tree = reinterpret_cast<const bNodeTree *>(node->id)) {
if (check_tree_for_time_node(*sub_tree, checked_groups)) {
return true;
}
}
@ -355,8 +321,8 @@ static bool dependsOnTime(struct Scene * /*scene*/, ModifierData *md)
if (tree == nullptr) {
return false;
}
Set<const bNodeTree *> checked_trees;
return check_tree_for_time_node(*tree, checked_trees);
Set<const bNodeTree *> checked_groups;
return check_tree_for_time_node(*tree, checked_groups);
}
static void foreachIDLink(ModifierData *md, Object *ob, IDWalkFunc walk, void *userData)
@ -426,15 +392,14 @@ static bool socket_type_has_attribute_toggle(const bNodeSocket &socket)
static bool input_has_attribute_toggle(const bNodeTree &node_tree, const int socket_index)
{
BLI_assert(node_tree.runtime->field_inferencing_interface);
const FieldInferencingInterface &field_interface =
const nodes::FieldInferencingInterface &field_interface =
*node_tree.runtime->field_inferencing_interface;
return field_interface.inputs[socket_index] != InputSocketFieldType::None;
return field_interface.inputs[socket_index] != nodes::InputSocketFieldType::None;
}
static std::unique_ptr<IDProperty, blender::bke::idprop::IDPropertyDeleter>
id_property_create_from_socket(const bNodeSocket &socket)
static std::unique_ptr<IDProperty, bke::idprop::IDPropertyDeleter> id_property_create_from_socket(
const bNodeSocket &socket)
{
using namespace blender;
switch (socket.type) {
case SOCK_FLOAT: {
const bNodeSocketValueFloat *value = static_cast<const bNodeSocketValueFloat *>(
@ -581,34 +546,34 @@ static void init_socket_cpp_value_from_property(const IDProperty &property,
else if (property.type == IDP_DOUBLE) {
value = float(IDP_Double(&property));
}
new (r_value) ValueOrField<float>(value);
new (r_value) fn::ValueOrField<float>(value);
break;
}
case SOCK_INT: {
int value = IDP_Int(&property);
new (r_value) ValueOrField<int>(value);
new (r_value) fn::ValueOrField<int>(value);
break;
}
case SOCK_VECTOR: {
float3 value;
copy_v3_v3(value, (const float *)IDP_Array(&property));
new (r_value) ValueOrField<float3>(value);
new (r_value) fn::ValueOrField<float3>(value);
break;
}
case SOCK_RGBA: {
blender::ColorGeometry4f value;
ColorGeometry4f value;
copy_v4_v4((float *)value, (const float *)IDP_Array(&property));
new (r_value) ValueOrField<ColorGeometry4f>(value);
new (r_value) fn::ValueOrField<ColorGeometry4f>(value);
break;
}
case SOCK_BOOLEAN: {
const bool value = IDP_Bool(&property);
new (r_value) ValueOrField<bool>(value);
new (r_value) fn::ValueOrField<bool>(value);
break;
}
case SOCK_STRING: {
std::string value = IDP_String(&property);
new (r_value) ValueOrField<std::string>(std::move(value));
new (r_value) fn::ValueOrField<std::string>(std::move(value));
break;
}
case SOCK_OBJECT: {
@ -648,43 +613,33 @@ static void init_socket_cpp_value_from_property(const IDProperty &property,
}
}
void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd)
static void update_input_properties_from_node_tree(const bNodeTree &tree,
const IDProperty *old_properties,
IDProperty &properties)
{
if (nmd->node_group == nullptr) {
if (nmd->settings.properties) {
IDP_FreeProperty(nmd->settings.properties);
nmd->settings.properties = nullptr;
}
return;
}
IDProperty *old_properties = nmd->settings.properties;
{
IDPropertyTemplate idprop = {0};
nmd->settings.properties = IDP_New(IDP_GROUP, &idprop, "Nodes Modifier Settings");
}
int socket_index;
LISTBASE_FOREACH_INDEX (bNodeSocket *, socket, &nmd->node_group->inputs, socket_index) {
IDProperty *new_prop = id_property_create_from_socket(*socket).release();
tree.ensure_topology_cache();
const Span<const bNodeSocket *> tree_inputs = tree.interface_inputs();
for (const int i : tree_inputs.index_range()) {
const bNodeSocket &socket = *tree_inputs[i];
IDProperty *new_prop = id_property_create_from_socket(socket).release();
if (new_prop == nullptr) {
/* Out of the set of supported input sockets, only
* geometry sockets aren't added to the modifier. */
BLI_assert(socket->type == SOCK_GEOMETRY);
BLI_assert(socket.type == SOCK_GEOMETRY);
continue;
}
new_prop->flag |= IDP_FLAG_OVERRIDABLE_LIBRARY;
if (socket->description[0] != '\0') {
if (socket.description[0] != '\0') {
IDPropertyUIData *ui_data = IDP_ui_data_ensure(new_prop);
ui_data->description = BLI_strdup(socket->description);
ui_data->description = BLI_strdup(socket.description);
}
IDP_AddToGroup(nmd->settings.properties, new_prop);
IDP_AddToGroup(&properties, new_prop);
if (old_properties != nullptr) {
IDProperty *old_prop = IDP_GetPropertyFromGroup(old_properties, socket->identifier);
const IDProperty *old_prop = IDP_GetPropertyFromGroup(old_properties, socket.identifier);
if (old_prop != nullptr) {
if (id_property_type_matches_socket(*socket, *old_prop)) {
if (id_property_type_matches_socket(socket, *old_prop)) {
/* #IDP_CopyPropertyContent replaces the UI data as well, which we don't (we only
* want to replace the values). So release it temporarily and replace it after. */
IDPropertyUIData *ui_data = new_prop->ui_data;
@ -703,20 +658,20 @@ void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd)
}
}
if (socket_type_has_attribute_toggle(*socket)) {
const std::string use_attribute_id = socket->identifier + use_attribute_suffix;
const std::string attribute_name_id = socket->identifier + attribute_name_suffix;
if (socket_type_has_attribute_toggle(socket)) {
const std::string use_attribute_id = socket.identifier + use_attribute_suffix;
const std::string attribute_name_id = socket.identifier + attribute_name_suffix;
IDPropertyTemplate idprop = {0};
IDProperty *use_attribute_prop = IDP_New(IDP_INT, &idprop, use_attribute_id.c_str());
IDP_AddToGroup(nmd->settings.properties, use_attribute_prop);
IDP_AddToGroup(&properties, use_attribute_prop);
IDProperty *attribute_prop = IDP_New(IDP_STRING, &idprop, attribute_name_id.c_str());
IDP_AddToGroup(nmd->settings.properties, attribute_prop);
IDP_AddToGroup(&properties, attribute_prop);
if (old_properties == nullptr) {
if (socket->default_attribute_name && socket->default_attribute_name[0] != '\0') {
IDP_AssignString(attribute_prop, socket->default_attribute_name, MAX_NAME);
if (socket.default_attribute_name && socket.default_attribute_name[0] != '\0') {
IDP_AssignString(attribute_prop, socket.default_attribute_name, MAX_NAME);
IDP_Int(use_attribute_prop) = 1;
}
}
@ -735,23 +690,31 @@ void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd)
}
}
}
}
LISTBASE_FOREACH (bNodeSocket *, socket, &nmd->node_group->outputs) {
if (!socket_type_has_attribute_toggle(*socket)) {
static void update_output_properties_from_node_tree(const bNodeTree &tree,
const IDProperty *old_properties,
IDProperty &properties)
{
tree.ensure_topology_cache();
const Span<const bNodeSocket *> tree_outputs = tree.interface_outputs();
for (const int i : tree_outputs.index_range()) {
const bNodeSocket &socket = *tree_outputs[i];
if (!socket_type_has_attribute_toggle(socket)) {
continue;
}
const std::string idprop_name = socket->identifier + attribute_name_suffix;
const std::string idprop_name = socket.identifier + attribute_name_suffix;
IDProperty *new_prop = IDP_NewString("", idprop_name.c_str(), MAX_NAME);
if (socket->description[0] != '\0') {
if (socket.description[0] != '\0') {
IDPropertyUIData *ui_data = IDP_ui_data_ensure(new_prop);
ui_data->description = BLI_strdup(socket->description);
ui_data->description = BLI_strdup(socket.description);
}
IDP_AddToGroup(nmd->settings.properties, new_prop);
IDP_AddToGroup(&properties, new_prop);
if (old_properties == nullptr) {
if (socket->default_attribute_name && socket->default_attribute_name[0] != '\0') {
IDP_AssignString(new_prop, socket->default_attribute_name, MAX_NAME);
if (socket.default_attribute_name && socket.default_attribute_name[0] != '\0') {
IDP_AssignString(new_prop, socket.default_attribute_name, MAX_NAME);
}
}
else {
@ -769,6 +732,30 @@ void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd)
}
}
}
}
} // namespace blender
void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd)
{
using namespace blender;
if (nmd->node_group == nullptr) {
if (nmd->settings.properties) {
IDP_FreeProperty(nmd->settings.properties);
nmd->settings.properties = nullptr;
}
return;
}
IDProperty *old_properties = nmd->settings.properties;
{
IDPropertyTemplate idprop = {0};
nmd->settings.properties = IDP_New(IDP_GROUP, &idprop, "Nodes Modifier Settings");
}
IDProperty *new_properties = nmd->settings.properties;
update_input_properties_from_node_tree(*nmd->node_group, old_properties, *new_properties);
update_output_properties_from_node_tree(*nmd->node_group, old_properties, *new_properties);
if (old_properties != nullptr) {
IDP_FreeProperty(old_properties);
@ -777,38 +764,39 @@ void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd)
DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY);
}
static void initialize_group_input(NodesModifierData &nmd,
const bNodeSocket &interface_socket,
namespace blender {
static void initialize_group_input(const bNodeTree &tree,
const IDProperty *properties,
const int input_index,
void *r_value)
{
const bNodeSocketType &socket_type = *interface_socket.typeinfo;
const eNodeSocketDatatype socket_data_type = static_cast<eNodeSocketDatatype>(
interface_socket.type);
if (nmd.settings.properties == nullptr) {
socket_type.get_geometry_nodes_cpp_value(interface_socket, r_value);
const bNodeSocket &io_input = *tree.interface_inputs()[input_index];
const bNodeSocketType &socket_type = *io_input.typeinfo;
const eNodeSocketDatatype socket_data_type = static_cast<eNodeSocketDatatype>(io_input.type);
if (properties == nullptr) {
socket_type.get_geometry_nodes_cpp_value(io_input, r_value);
return;
}
const IDProperty *property = IDP_GetPropertyFromGroup(nmd.settings.properties,
interface_socket.identifier);
const IDProperty *property = IDP_GetPropertyFromGroup(properties, io_input.identifier);
if (property == nullptr) {
socket_type.get_geometry_nodes_cpp_value(interface_socket, r_value);
socket_type.get_geometry_nodes_cpp_value(io_input, r_value);
return;
}
if (!id_property_type_matches_socket(interface_socket, *property)) {
socket_type.get_geometry_nodes_cpp_value(interface_socket, r_value);
if (!id_property_type_matches_socket(io_input, *property)) {
socket_type.get_geometry_nodes_cpp_value(io_input, r_value);
return;
}
if (!input_has_attribute_toggle(*nmd.node_group, input_index)) {
if (!input_has_attribute_toggle(tree, input_index)) {
init_socket_cpp_value_from_property(*property, socket_data_type, r_value);
return;
}
const IDProperty *property_use_attribute = IDP_GetPropertyFromGroup(
nmd.settings.properties, (interface_socket.identifier + use_attribute_suffix).c_str());
properties, (io_input.identifier + use_attribute_suffix).c_str());
const IDProperty *property_attribute_name = IDP_GetPropertyFromGroup(
nmd.settings.properties, (interface_socket.identifier + attribute_name_suffix).c_str());
properties, (io_input.identifier + attribute_name_suffix).c_str());
if (property_use_attribute == nullptr || property_attribute_name == nullptr) {
init_socket_cpp_value_from_property(*property, socket_data_type, r_value);
return;
@ -817,14 +805,13 @@ static void initialize_group_input(NodesModifierData &nmd,
const bool use_attribute = IDP_Int(property_use_attribute) != 0;
if (use_attribute) {
const StringRef attribute_name{IDP_String(property_attribute_name)};
if (!blender::bke::allow_procedural_attribute_access(attribute_name)) {
if (!bke::allow_procedural_attribute_access(attribute_name)) {
init_socket_cpp_value_from_property(*property, socket_data_type, r_value);
return;
}
auto attribute_input = std::make_shared<blender::bke::AttributeFieldInput>(
attribute_name, *socket_type.base_cpp_type);
GField attribute_field{std::move(attribute_input), 0};
const auto *value_or_field_cpp_type = ValueOrFieldCPPType::get_from_self(
fn::GField attribute_field = bke::AttributeFieldInput::Create(attribute_name,
*socket_type.base_cpp_type);
const auto *value_or_field_cpp_type = fn::ValueOrFieldCPPType::get_from_self(
*socket_type.geometry_nodes_cpp_type);
BLI_assert(value_or_field_cpp_type != nullptr);
value_or_field_cpp_type->construct_from_field(r_value, std::move(attribute_field));
@ -836,16 +823,16 @@ static void initialize_group_input(NodesModifierData &nmd,
static const lf::FunctionNode *find_viewer_lf_node(const bNode &viewer_bnode)
{
if (const blender::nodes::GeometryNodesLazyFunctionGraphInfo *lf_graph_info =
blender::nodes::ensure_geometry_nodes_lazy_function_graph(viewer_bnode.owner_tree())) {
if (const nodes::GeometryNodesLazyFunctionGraphInfo *lf_graph_info =
nodes::ensure_geometry_nodes_lazy_function_graph(viewer_bnode.owner_tree())) {
return lf_graph_info->mapping.viewer_node_map.lookup_default(&viewer_bnode, nullptr);
}
return nullptr;
}
static const lf::FunctionNode *find_group_lf_node(const bNode &group_bnode)
{
if (const blender::nodes::GeometryNodesLazyFunctionGraphInfo *lf_graph_info =
blender::nodes::ensure_geometry_nodes_lazy_function_graph(group_bnode.owner_tree())) {
if (const nodes::GeometryNodesLazyFunctionGraphInfo *lf_graph_info =
nodes::ensure_geometry_nodes_lazy_function_graph(group_bnode.owner_tree())) {
return lf_graph_info->mapping.group_node_map.lookup_default(&group_bnode, nullptr);
}
return nullptr;
@ -855,10 +842,10 @@ static void find_side_effect_nodes_for_viewer_path(
const ViewerPath &viewer_path,
const NodesModifierData &nmd,
const ModifierEvalContext &ctx,
MultiValueMap<blender::ComputeContextHash, const lf::FunctionNode *> &r_side_effect_nodes)
MultiValueMap<ComputeContextHash, const lf::FunctionNode *> &r_side_effect_nodes)
{
const std::optional<blender::ed::viewer_path::ViewerPathForGeometryNodesViewer> parsed_path =
blender::ed::viewer_path::parse_geometry_nodes_viewer(viewer_path);
const std::optional<ed::viewer_path::ViewerPathForGeometryNodesViewer> parsed_path =
ed::viewer_path::parse_geometry_nodes_viewer(viewer_path);
if (!parsed_path.has_value()) {
return;
}
@ -869,8 +856,8 @@ static void find_side_effect_nodes_for_viewer_path(
return;
}
blender::ComputeContextBuilder compute_context_builder;
compute_context_builder.push<blender::bke::ModifierComputeContext>(parsed_path->modifier_name);
ComputeContextBuilder compute_context_builder;
compute_context_builder.push<bke::ModifierComputeContext>(parsed_path->modifier_name);
const bNodeTree *group = nmd.node_group;
Stack<const bNode *> group_node_stack;
@ -887,7 +874,7 @@ static void find_side_effect_nodes_for_viewer_path(
}
group_node_stack.push(found_node);
group = reinterpret_cast<bNodeTree *>(found_node->id);
compute_context_builder.push<blender::bke::NodeGroupComputeContext>(*found_node);
compute_context_builder.push<bke::NodeGroupComputeContext>(*found_node);
}
const bNode *found_viewer_node = group->node_by_id(parsed_path->viewer_node_id);
@ -917,7 +904,7 @@ static void find_side_effect_nodes_for_viewer_path(
static void find_side_effect_nodes(
const NodesModifierData &nmd,
const ModifierEvalContext &ctx,
MultiValueMap<blender::ComputeContextHash, const lf::FunctionNode *> &r_side_effect_nodes)
MultiValueMap<ComputeContextHash, const lf::FunctionNode *> &r_side_effect_nodes)
{
Main *bmain = DEG_get_bmain(ctx.depsgraph);
wmWindowManager *wm = (wmWindowManager *)bmain->wm.first;
@ -945,7 +932,7 @@ static void find_side_effect_nodes(
static void find_socket_log_contexts(const NodesModifierData &nmd,
const ModifierEvalContext &ctx,
Set<blender::ComputeContextHash> &r_socket_log_contexts)
Set<ComputeContextHash> &r_socket_log_contexts)
{
Main *bmain = DEG_get_bmain(ctx.depsgraph);
wmWindowManager *wm = (wmWindowManager *)bmain->wm.first;
@ -958,9 +945,9 @@ static void find_socket_log_contexts(const NodesModifierData &nmd,
const SpaceLink *sl = static_cast<SpaceLink *>(area->spacedata.first);
if (sl->spacetype == SPACE_NODE) {
const SpaceNode &snode = *reinterpret_cast<const SpaceNode *>(sl);
if (const std::optional<blender::ComputeContextHash> hash = blender::nodes::geo_eval_log::
GeoModifierLog::get_compute_context_hash_for_node_editor(snode,
nmd.modifier.name)) {
if (const std::optional<ComputeContextHash> hash =
geo_log::GeoModifierLog::get_compute_context_hash_for_node_editor(
snode, nmd.modifier.name)) {
r_socket_log_contexts.add(*hash);
}
}
@ -971,13 +958,13 @@ static void find_socket_log_contexts(const NodesModifierData &nmd,
static void clear_runtime_data(NodesModifierData *nmd)
{
if (nmd->runtime_eval_log != nullptr) {
delete static_cast<GeoModifierLog *>(nmd->runtime_eval_log);
delete static_cast<geo_log::GeoModifierLog *>(nmd->runtime_eval_log);
nmd->runtime_eval_log = nullptr;
}
}
struct OutputAttributeInfo {
GField field;
fn::GField field;
StringRefNull name;
};
@ -993,7 +980,10 @@ struct OutputAttributeToStore {
* can be evaluated together.
*/
static MultiValueMap<eAttrDomain, OutputAttributeInfo> find_output_attributes_to_store(
const NodesModifierData &nmd, const bNode &output_node, Span<GMutablePointer> output_values)
const bNodeTree &tree,
const IDProperty *properties,
const bNode &output_node,
Span<GMutablePointer> output_values)
{
MultiValueMap<eAttrDomain, OutputAttributeInfo> outputs_by_domain;
for (const bNodeSocket *socket : output_node.input_sockets().drop_front(1).drop_back(1)) {
@ -1002,7 +992,7 @@ static MultiValueMap<eAttrDomain, OutputAttributeInfo> find_output_attributes_to
}
const std::string prop_name = socket->identifier + attribute_name_suffix;
const IDProperty *prop = IDP_GetPropertyFromGroup(nmd.settings.properties, prop_name.c_str());
const IDProperty *prop = IDP_GetPropertyFromGroup(properties, prop_name.c_str());
if (prop == nullptr) {
continue;
}
@ -1010,18 +1000,17 @@ static MultiValueMap<eAttrDomain, OutputAttributeInfo> find_output_attributes_to
if (attribute_name.is_empty()) {
continue;
}
if (!blender::bke::allow_procedural_attribute_access(attribute_name)) {
if (!bke::allow_procedural_attribute_access(attribute_name)) {
continue;
}
const int index = socket->index();
const GPointer value = output_values[index];
const auto *value_or_field_type = ValueOrFieldCPPType::get_from_self(*value.type());
const auto *value_or_field_type = fn::ValueOrFieldCPPType::get_from_self(*value.type());
BLI_assert(value_or_field_type != nullptr);
const GField field = value_or_field_type->as_field(value.get());
const fn::GField field = value_or_field_type->as_field(value.get());
const bNodeSocket *interface_socket = (const bNodeSocket *)BLI_findlink(
&nmd.node_group->outputs, index);
const bNodeSocket *interface_socket = (const bNodeSocket *)BLI_findlink(&tree.outputs, index);
const eAttrDomain domain = (eAttrDomain)interface_socket->attribute_domain;
OutputAttributeInfo output_info;
output_info.field = std::move(field);
@ -1048,7 +1037,7 @@ static Vector<OutputAttributeToStore> compute_attributes_to_store(
continue;
}
const GeometryComponent &component = *geometry.get_component_for_read(component_type);
const blender::bke::AttributeAccessor attributes = *component.attributes();
const bke::AttributeAccessor attributes = *component.attributes();
for (const auto item : outputs_by_domain.items()) {
const eAttrDomain domain = item.key;
const Span<OutputAttributeInfo> outputs_info = item.value;
@ -1056,18 +1045,18 @@ static Vector<OutputAttributeToStore> compute_attributes_to_store(
continue;
}
const int domain_size = attributes.domain_size(domain);
blender::bke::GeometryFieldContext field_context{component, domain};
blender::fn::FieldEvaluator field_evaluator{field_context, domain_size};
bke::GeometryFieldContext field_context{component, domain};
fn::FieldEvaluator field_evaluator{field_context, domain_size};
for (const OutputAttributeInfo &output_info : outputs_info) {
const CPPType &type = output_info.field.cpp_type();
const AttributeValidator validator = attributes.lookup_validator(output_info.name);
const bke::AttributeValidator validator = attributes.lookup_validator(output_info.name);
OutputAttributeToStore store{
component_type,
domain,
output_info.name,
GMutableSpan{
type, MEM_malloc_arrayN(domain_size, type.size(), __func__), domain_size}};
GField field = validator.validate_field_if_necessary(output_info.field);
fn::GField field = validator.validate_field_if_necessary(output_info.field);
field_evaluator.add_with_destination(std::move(field), store.data);
attributes_to_store.append(store);
}
@ -1082,11 +1071,11 @@ static void store_computed_output_attributes(
{
for (const OutputAttributeToStore &store : attributes_to_store) {
GeometryComponent &component = geometry.get_component_for_write(store.component_type);
blender::bke::MutableAttributeAccessor attributes = *component.attributes_for_write();
bke::MutableAttributeAccessor attributes = *component.attributes_for_write();
const eCustomDataType data_type = blender::bke::cpp_type_to_custom_data_type(
store.data.type());
const std::optional<AttributeMetaData> meta_data = attributes.lookup_meta_data(store.name);
const eCustomDataType data_type = bke::cpp_type_to_custom_data_type(store.data.type());
const std::optional<bke::AttributeMetaData> meta_data = attributes.lookup_meta_data(
store.name);
/* Attempt to remove the attribute if it already exists but the domain and type don't match.
* Removing the attribute won't succeed if it is built in and non-removable. */
@ -1099,12 +1088,12 @@ static void store_computed_output_attributes(
* attribute didn't exist before, or if it existed but was removed above. */
if (attributes.add(store.name,
store.domain,
blender::bke::cpp_type_to_custom_data_type(store.data.type()),
blender::bke::AttributeInitMoveArray(store.data.data()))) {
bke::cpp_type_to_custom_data_type(store.data.type()),
bke::AttributeInitMoveArray(store.data.data()))) {
continue;
}
blender::bke::GAttributeWriter attribute = attributes.lookup_or_add_for_write(
bke::GAttributeWriter attribute = attributes.lookup_or_add_for_write(
store.name, store.domain, data_type);
if (attribute) {
attribute.varray.set_all(store.data.data());
@ -1118,14 +1107,15 @@ static void store_computed_output_attributes(
}
static void store_output_attributes(GeometrySet &geometry,
const NodesModifierData &nmd,
const bNodeTree &tree,
const IDProperty *properties,
const bNode &output_node,
Span<GMutablePointer> output_values)
{
/* All new attribute values have to be computed before the geometry is actually changed. This is
* necessary because some fields might depend on attributes that are overwritten. */
MultiValueMap<eAttrDomain, OutputAttributeInfo> outputs_by_domain =
find_output_attributes_to_store(nmd, output_node, output_values);
find_output_attributes_to_store(tree, properties, output_node, output_values);
Vector<OutputAttributeToStore> attributes_to_store = compute_attributes_to_store(
geometry, outputs_by_domain);
store_computed_output_attributes(geometry, attributes_to_store);
@ -1134,15 +1124,14 @@ static void store_output_attributes(GeometrySet &geometry,
/**
* Evaluate a node group to compute the output geometry.
*/
static GeometrySet compute_geometry(
const bNodeTree &btree,
const blender::nodes::GeometryNodesLazyFunctionGraphInfo &lf_graph_info,
const bNode &output_node,
GeometrySet input_geometry_set,
NodesModifierData *nmd,
const ModifierEvalContext *ctx)
static GeometrySet compute_geometry(const bNodeTree &btree,
const nodes::GeometryNodesLazyFunctionGraphInfo &lf_graph_info,
const bNode &output_node,
GeometrySet input_geometry_set,
NodesModifierData *nmd,
const ModifierEvalContext *ctx)
{
const blender::nodes::GeometryNodeLazyFunctionGraphMapping &mapping = lf_graph_info.mapping;
const nodes::GeometryNodeLazyFunctionGraphMapping &mapping = lf_graph_info.mapping;
Vector<const lf::OutputSocket *> graph_inputs = mapping.group_input_sockets;
graph_inputs.extend(mapping.group_output_used_sockets);
@ -1156,35 +1145,36 @@ static GeometrySet compute_geometry(
Array<lf::ValueUsage> param_output_usages(graph_outputs.size(), lf::ValueUsage::Used);
Array<bool> param_set_outputs(graph_outputs.size(), false);
blender::nodes::GeometryNodesLazyFunctionLogger lf_logger(lf_graph_info);
blender::nodes::GeometryNodesLazyFunctionSideEffectProvider lf_side_effect_provider;
nodes::GeometryNodesLazyFunctionLogger lf_logger(lf_graph_info);
nodes::GeometryNodesLazyFunctionSideEffectProvider lf_side_effect_provider;
lf::GraphExecutor graph_executor{
lf_graph_info.graph, graph_inputs, graph_outputs, &lf_logger, &lf_side_effect_provider};
blender::nodes::GeoNodesModifierData geo_nodes_modifier_data;
nodes::GeoNodesModifierData geo_nodes_modifier_data;
geo_nodes_modifier_data.depsgraph = ctx->depsgraph;
geo_nodes_modifier_data.self_object = ctx->object;
auto eval_log = std::make_unique<GeoModifierLog>();
auto eval_log = std::make_unique<geo_log::GeoModifierLog>();
Set<blender::ComputeContextHash> socket_log_contexts;
Set<ComputeContextHash> socket_log_contexts;
if (logging_enabled(ctx)) {
geo_nodes_modifier_data.eval_log = eval_log.get();
find_socket_log_contexts(*nmd, *ctx, socket_log_contexts);
geo_nodes_modifier_data.socket_log_contexts = &socket_log_contexts;
}
MultiValueMap<blender::ComputeContextHash, const lf::FunctionNode *> r_side_effect_nodes;
MultiValueMap<ComputeContextHash, const lf::FunctionNode *> r_side_effect_nodes;
find_side_effect_nodes(*nmd, *ctx, r_side_effect_nodes);
geo_nodes_modifier_data.side_effect_nodes = &r_side_effect_nodes;
blender::nodes::GeoNodesLFUserData user_data;
nodes::GeoNodesLFUserData user_data;
user_data.modifier_data = &geo_nodes_modifier_data;
blender::bke::ModifierComputeContext modifier_compute_context{nullptr, nmd->modifier.name};
bke::ModifierComputeContext modifier_compute_context{nullptr, nmd->modifier.name};
user_data.compute_context = &modifier_compute_context;
blender::LinearAllocator<> allocator;
LinearAllocator<> allocator;
Vector<GMutablePointer> inputs_to_destruct;
const IDProperty *properties = nmd->settings.properties;
int input_index = -1;
for (const int i : btree.interface_inputs().index_range()) {
input_index++;
@ -1197,7 +1187,7 @@ static GeometrySet compute_geometry(
const CPPType *type = interface_socket.typeinfo->geometry_nodes_cpp_type;
BLI_assert(type != nullptr);
void *value = allocator.allocate(type->size(), type->alignment());
initialize_group_input(*nmd, interface_socket, i, value);
initialize_group_input(btree, properties, i, value);
param_inputs[input_index] = {type, value};
inputs_to_destruct.append({type, value});
}
@ -1208,7 +1198,7 @@ static GeometrySet compute_geometry(
param_inputs[input_index] = &output_used_inputs[i];
}
Array<blender::bke::AnonymousAttributeSet> attributes_to_propagate(
Array<bke::AnonymousAttributeSet> attributes_to_propagate(
mapping.attribute_set_by_geometry_output.size());
for (const int i : attributes_to_propagate.index_range()) {
input_index++;
@ -1239,7 +1229,7 @@ static GeometrySet compute_geometry(
}
GeometrySet output_geometry_set = std::move(*static_cast<GeometrySet *>(param_outputs[0].get()));
store_output_attributes(output_geometry_set, *nmd, output_node, param_outputs);
store_output_attributes(output_geometry_set, btree, properties, output_node, param_outputs);
for (GMutablePointer &ptr : param_outputs) {
ptr.destruct();
@ -1248,7 +1238,7 @@ static GeometrySet compute_geometry(
if (logging_enabled(ctx)) {
NodesModifierData *nmd_orig = reinterpret_cast<NodesModifierData *>(
BKE_modifier_get_original(ctx->object, &nmd->modifier));
delete static_cast<GeoModifierLog *>(nmd_orig->runtime_eval_log);
delete static_cast<geo_log::GeoModifierLog *>(nmd_orig->runtime_eval_log);
nmd_orig->runtime_eval_log = eval_log.release();
}
@ -1302,6 +1292,7 @@ static void modifyGeometry(ModifierData *md,
const ModifierEvalContext *ctx,
GeometrySet &geometry_set)
{
using namespace blender;
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
if (nmd->node_group == nullptr) {
return;
@ -1332,8 +1323,8 @@ static void modifyGeometry(ModifierData *md,
return;
}
const blender::nodes::GeometryNodesLazyFunctionGraphInfo *lf_graph_info =
blender::nodes::ensure_geometry_nodes_lazy_function_graph(tree);
const nodes::GeometryNodesLazyFunctionGraphInfo *lf_graph_info =
nodes::ensure_geometry_nodes_lazy_function_graph(tree);
if (lf_graph_info == nullptr) {
BKE_modifier_set_error(ctx->object, md, "Cannot evaluate node group");
geometry_set.clear();
@ -1423,13 +1414,13 @@ static NodesModifierData *get_modifier_data(Main &bmain,
return reinterpret_cast<NodesModifierData *>(md);
}
static GeoTreeLog *get_root_tree_log(const NodesModifierData &nmd)
static geo_log::GeoTreeLog *get_root_tree_log(const NodesModifierData &nmd)
{
if (nmd.runtime_eval_log == nullptr) {
return nullptr;
}
GeoModifierLog &modifier_log = *static_cast<GeoModifierLog *>(nmd.runtime_eval_log);
blender::bke::ModifierComputeContext compute_context{nullptr, nmd.modifier.name};
auto &modifier_log = *static_cast<geo_log::GeoModifierLog *>(nmd.runtime_eval_log);
bke::ModifierComputeContext compute_context{nullptr, nmd.modifier.name};
return &modifier_log.get_tree_log(compute_context.hash());
}
@ -1444,7 +1435,7 @@ static void attribute_search_update_fn(
if (nmd->node_group == nullptr) {
return;
}
GeoTreeLog *tree_log = get_root_tree_log(*nmd);
geo_log::GeoTreeLog *tree_log = get_root_tree_log(*nmd);
if (tree_log == nullptr) {
return;
}
@ -1471,22 +1462,21 @@ static void attribute_search_update_fn(
}
}
Set<StringRef> names;
Vector<const GeometryAttributeInfo *> attributes;
Vector<const geo_log::GeometryAttributeInfo *> attributes;
for (const bNodeSocket *socket : sockets_to_check) {
const ValueLog *value_log = tree_log->find_socket_value_log(*socket);
const geo_log::ValueLog *value_log = tree_log->find_socket_value_log(*socket);
if (value_log == nullptr) {
continue;
}
if (const GeometryInfoLog *geo_log = dynamic_cast<const GeometryInfoLog *>(value_log)) {
for (const GeometryAttributeInfo &attribute : geo_log->attributes) {
if (const auto *geo_log = dynamic_cast<const geo_log::GeometryInfoLog *>(value_log)) {
for (const geo_log::GeometryAttributeInfo &attribute : geo_log->attributes) {
if (names.add(attribute.name)) {
attributes.append(&attribute);
}
}
}
}
blender::ui::attribute_search_add_items(
str, data.is_output, attributes.as_span(), items, is_first);
ui::attribute_search_add_items(str, data.is_output, attributes.as_span(), items, is_first);
}
static void attribute_search_exec_fn(bContext *C, void *data_v, void *item_v)
@ -1495,7 +1485,7 @@ static void attribute_search_exec_fn(bContext *C, void *data_v, void *item_v)
return;
}
AttributeSearchData &data = *static_cast<AttributeSearchData *>(data_v);
const GeometryAttributeInfo &item = *static_cast<const GeometryAttributeInfo *>(item_v);
const auto &item = *static_cast<const geo_log::GeometryAttributeInfo *>(item_v);
const NodesModifierData *nmd = get_modifier_data(*CTX_data_main(C), *CTX_wm_manager(C), data);
if (nmd == nullptr) {
return;
@ -1566,7 +1556,7 @@ static void add_attribute_search_button(const bContext &C,
char *attribute_name = RNA_string_get_alloc(
md_ptr, rna_path_attribute_name.c_str(), nullptr, 0, nullptr);
const bool access_allowed = blender::bke::allow_procedural_attribute_access(attribute_name);
const bool access_allowed = bke::allow_procedural_attribute_access(attribute_name);
MEM_freeN(attribute_name);
if (!access_allowed) {
UI_but_flag_enable(but, UI_BUT_REDALERT);
@ -1756,11 +1746,11 @@ static void panel_draw(const bContext *C, Panel *panel)
}
/* Draw node warnings. */
GeoTreeLog *tree_log = get_root_tree_log(*nmd);
geo_log::GeoTreeLog *tree_log = get_root_tree_log(*nmd);
if (tree_log != nullptr) {
tree_log->ensure_node_warnings();
for (const NodeWarning &warning : tree_log->all_warnings) {
if (warning.type != NodeWarningType::Info) {
for (const geo_log::NodeWarning &warning : tree_log->all_warnings) {
if (warning.type != geo_log::NodeWarningType::Info) {
uiItemL(layout, warning.message.c_str(), ICON_ERROR);
}
}
@ -1800,13 +1790,13 @@ static void internal_dependencies_panel_draw(const bContext * /*C*/, Panel *pane
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, nullptr);
NodesModifierData *nmd = static_cast<NodesModifierData *>(ptr->data);
GeoTreeLog *tree_log = get_root_tree_log(*nmd);
geo_log::GeoTreeLog *tree_log = get_root_tree_log(*nmd);
if (tree_log == nullptr) {
return;
}
tree_log->ensure_used_named_attributes();
const Map<StringRefNull, NamedAttributeUsage> &usage_by_attribute =
const Map<StringRefNull, geo_log::NamedAttributeUsage> &usage_by_attribute =
tree_log->used_named_attributes;
if (usage_by_attribute.is_empty()) {
@ -1816,7 +1806,7 @@ static void internal_dependencies_panel_draw(const bContext * /*C*/, Panel *pane
struct NameWithUsage {
StringRefNull name;
NamedAttributeUsage usage;
geo_log::NamedAttributeUsage usage;
};
Vector<NameWithUsage> sorted_used_attribute;
@ -1831,20 +1821,20 @@ static void internal_dependencies_panel_draw(const bContext * /*C*/, Panel *pane
for (const NameWithUsage &attribute : sorted_used_attribute) {
const StringRefNull attribute_name = attribute.name;
const NamedAttributeUsage usage = attribute.usage;
const geo_log::NamedAttributeUsage usage = attribute.usage;
/* #uiLayoutRowWithHeading doesn't seem to work in this case. */
uiLayout *split = uiLayoutSplit(layout, 0.4f, false);
std::stringstream ss;
Vector<std::string> usages;
if ((usage & NamedAttributeUsage::Read) != NamedAttributeUsage::None) {
if ((usage & geo_log::NamedAttributeUsage::Read) != geo_log::NamedAttributeUsage::None) {
usages.append(TIP_("Read"));
}
if ((usage & NamedAttributeUsage::Write) != NamedAttributeUsage::None) {
if ((usage & geo_log::NamedAttributeUsage::Write) != geo_log::NamedAttributeUsage::None) {
usages.append(TIP_("Write"));
}
if ((usage & NamedAttributeUsage::Remove) != NamedAttributeUsage::None) {
if ((usage & geo_log::NamedAttributeUsage::Remove) != geo_log::NamedAttributeUsage::None) {
usages.append(TIP_("Remove"));
}
for (const int i : usages.index_range()) {
@ -1866,6 +1856,7 @@ static void internal_dependencies_panel_draw(const bContext * /*C*/, Panel *pane
static void panelRegister(ARegionType *region_type)
{
using namespace blender;
PanelType *panel_type = modifier_panel_register(region_type, eModifierType_Nodes, panel_draw);
modifier_subpanel_register(region_type,
"output_attributes",
@ -1968,6 +1959,8 @@ static void requiredDataMask(ModifierData * /*md*/, CustomData_MeshMasks *r_cdda
r_cddata_masks->vmask |= CD_MASK_PROP_ALL;
}
} // namespace blender
ModifierTypeInfo modifierType_Nodes = {
/*name*/ N_("GeometryNodes"),
/*structName*/ "NodesModifierData",
@ -1981,26 +1974,26 @@ ModifierTypeInfo modifierType_Nodes = {
eModifierTypeFlag_SupportsMapping),
/*icon*/ ICON_GEOMETRY_NODES,
/*copyData*/ copyData,
/*copyData*/ blender::copyData,
/*deformVerts*/ nullptr,
/*deformMatrices*/ nullptr,
/*deformVertsEM*/ nullptr,
/*deformMatricesEM*/ nullptr,
/*modifyMesh*/ modifyMesh,
/*modifyGeometrySet*/ modifyGeometrySet,
/*modifyMesh*/ blender::modifyMesh,
/*modifyGeometrySet*/ blender::modifyGeometrySet,
/*initData*/ initData,
/*requiredDataMask*/ requiredDataMask,
/*freeData*/ freeData,
/*isDisabled*/ isDisabled,
/*updateDepsgraph*/ updateDepsgraph,
/*dependsOnTime*/ dependsOnTime,
/*initData*/ blender::initData,
/*requiredDataMask*/ blender::requiredDataMask,
/*freeData*/ blender::freeData,
/*isDisabled*/ blender::isDisabled,
/*updateDepsgraph*/ blender::updateDepsgraph,
/*dependsOnTime*/ blender::dependsOnTime,
/*dependsOnNormals*/ nullptr,
/*foreachIDLink*/ foreachIDLink,
/*foreachTexLink*/ foreachTexLink,
/*foreachIDLink*/ blender::foreachIDLink,
/*foreachTexLink*/ blender::foreachTexLink,
/*freeRuntimeData*/ nullptr,
/*panelRegister*/ panelRegister,
/*blendWrite*/ blendWrite,
/*blendRead*/ blendRead,
/*panelRegister*/ blender::panelRegister,
/*blendWrite*/ blender::blendWrite,
/*blendRead*/ blender::blendRead,
};

View File

@ -7,6 +7,8 @@
#include "BKE_attribute_math.hh"
#include "GEO_mesh_flip_faces.hh"
#include "node_geometry_util.hh"
namespace blender::nodes::node_geo_flip_faces_cc {
@ -29,48 +31,7 @@ static void mesh_flip_faces(Mesh &mesh, const Field<bool> &selection_field)
evaluator.evaluate();
const IndexMask selection = evaluator.get_evaluated_as_mask(0);
const Span<MPoly> polys = mesh.polys();
MutableSpan<int> corner_verts = mesh.corner_verts_for_write();
MutableSpan<int> corner_edges = mesh.corner_edges_for_write();
threading::parallel_for(selection.index_range(), 1024, [&](const IndexRange range) {
for (const int i : selection.slice(range)) {
const IndexRange poly(polys[i].loopstart, polys[i].totloop);
for (const int j : IndexRange(poly.size() / 2)) {
const int a = poly[j + 1];
const int b = poly.last(j);
std::swap(corner_verts[a], corner_verts[b]);
std::swap(corner_edges[a - 1], corner_edges[b]);
}
}
});
MutableAttributeAccessor attributes = mesh.attributes_for_write();
attributes.for_all(
[&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
if (meta_data.data_type == CD_PROP_STRING) {
return true;
}
if (meta_data.domain != ATTR_DOMAIN_CORNER) {
return true;
}
if (ELEM(attribute_id.name(), ".corner_vert", ".corner_edge")) {
return true;
}
GSpanAttributeWriter attribute = attributes.lookup_for_write_span(attribute_id);
attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) {
using T = decltype(dummy);
MutableSpan<T> dst_span = attribute.span.typed<T>();
threading::parallel_for(selection.index_range(), 1024, [&](const IndexRange range) {
for (const int i : selection.slice(range)) {
const IndexRange poly(polys[i].loopstart, polys[i].totloop);
dst_span.slice(poly.drop_front(1)).reverse();
}
});
});
attribute.finish();
return true;
});
geometry::flip_faces(mesh, selection);
}
static void node_geo_exec(GeoNodeExecParams params)

View File

@ -93,6 +93,9 @@ static void window_manager_foreach_id(ID *id, LibraryForeachIDData *data)
}
}
}
BKE_LIB_FOREACHID_PROCESS_IDSUPER(
data, wm->xr.session_settings.base_pose_object, IDWALK_CB_USER_ONE);
}
static void write_wm_xr_data(BlendWriter *writer, wmXrData *xr_data)

View File

@ -736,8 +736,9 @@ if(WITH_CYCLES OR WITH_OPENGL_RENDER_TESTS)
set(_cycles_render_tests bake;${render_tests};osl)
foreach(render_test ${_cycles_render_tests})
set(_cycles_test_name "cycles_${render_test}_${_cycles_device_lower}")
add_python_test(
cycles_${render_test}_${_cycles_device_lower}
${_cycles_test_name}
${CMAKE_CURRENT_LIST_DIR}/cycles_render_tests.py
-blender "${TEST_BLENDER_EXE}"
-testdir "${TEST_SRC_DIR}/render/${render_test}"
@ -746,6 +747,11 @@ if(WITH_CYCLES OR WITH_OPENGL_RENDER_TESTS)
-device ${_cycles_device}
-blacklist ${_cycles_blacklist}
)
if(NOT("${_cycles_device_lower}" STREQUAL "cpu"))
set_tests_properties(${_cycles_test_name} PROPERTIES RUN_SERIAL TRUE)
endif()
unset(_cycles_test_name)
endforeach()
endforeach()
unset(_cycles_blacklist)