Merge branch 'master' into temp-sculpt-roll-mapping

This commit is contained in:
Joseph Eagar 2022-11-17 01:02:26 -08:00
commit 997ff01503
118 changed files with 1576 additions and 1046 deletions

View File

@ -257,6 +257,12 @@ if(UNIX AND NOT (APPLE OR HAIKU))
option(WITH_GHOST_WAYLAND_DYNLOAD "Enable runtime dynamic WAYLAND libraries loading" ON)
mark_as_advanced(WITH_GHOST_WAYLAND_DYNLOAD)
set(WITH_GHOST_WAYLAND_APP_ID "" CACHE STRING "\
The application ID used for Blender (use default when an empty string), \
this can be used to differentiate Blender instances by version or branch for example."
)
mark_as_advanced(WITH_GHOST_WAYLAND_APP_ID)
endif()
endif()

View File

@ -103,10 +103,6 @@ if(EXISTS ${SOURCE_DIR}/.git)
endif()
endif()
if(MY_WC_BRANCH MATCHES "^blender-v")
set(MY_WC_BRANCH "master")
endif()
unset(_git_below_check)
endif()

View File

@ -72,6 +72,11 @@ bool BlenderImageLoader::load_metadata(const ImageDeviceFeatures &, ImageMetaDat
metadata.colorspace = u_colorspace_raw;
}
else {
/* In some cases (e.g. T94135), the colorspace setting in Blender gets updated as part of the
* metadata queries in this function, so update the colorspace setting here. */
PointerRNA colorspace_ptr = b_image.colorspace_settings().ptr;
metadata.colorspace = get_enum_identifier(colorspace_ptr, "name");
if (metadata.channels == 1) {
metadata.type = IMAGE_DATA_TYPE_BYTE;
}

View File

@ -24,9 +24,9 @@ DenoiserGPU::~DenoiserGPU()
}
bool DenoiserGPU::denoise_buffer(const BufferParams &buffer_params,
RenderBuffers *render_buffers,
const int num_samples,
bool allow_inplace_modification)
RenderBuffers *render_buffers,
const int num_samples,
bool allow_inplace_modification)
{
Device *denoiser_device = get_denoiser_device();
if (!denoiser_device) {

View File

@ -393,6 +393,11 @@ elseif(WITH_GHOST_X11 OR WITH_GHOST_WAYLAND)
unset(INC_DST)
add_definitions(-DWITH_GHOST_WAYLAND)
if(NOT WITH_GHOST_WAYLAND_APP_ID STREQUAL "")
add_definitions(-DWITH_GHOST_WAYLAND_APP_ID=${WITH_GHOST_WAYLAND_APP_ID})
endif()
endif()
if(WITH_INPUT_NDOF)

View File

@ -805,6 +805,21 @@ static GWL_SeatStatePointer *gwl_seat_state_pointer_from_cursor_surface(
return nullptr;
}
static bool gwl_seat_key_depressed_suppress_warning(const GWL_Seat *seat)
{
bool suppress_warning = false;
#ifdef USE_GNOME_KEYBOARD_SUPPRESS_WARNING
if ((seat->key_depressed_suppress_warning.any_mod_held == true) &&
(seat->key_depressed_suppress_warning.any_keys_held_on_enter == false)) {
/* The compositor gave us invalid information, don't show a warning. */
suppress_warning = true;
}
#endif
return suppress_warning;
}
/** \} */
/* -------------------------------------------------------------------- */
@ -1751,13 +1766,17 @@ static void keyboard_depressed_state_key_event(GWL_Seat *seat,
const GHOST_TKey gkey,
const GHOST_TEventType etype)
{
const bool show_warning = !gwl_seat_key_depressed_suppress_warning(seat);
if (GHOST_KEY_MODIFIER_CHECK(gkey)) {
const int index = GHOST_KEY_MODIFIER_TO_INDEX(gkey);
int16_t &value = seat->key_depressed.mods[index];
if (etype == GHOST_kEventKeyUp) {
value -= 1;
if (UNLIKELY(value < 0)) {
CLOG_WARN(LOG, "modifier (%d) has negative keys held (%d)!", index, value);
if (show_warning) {
CLOG_WARN(LOG, "modifier (%d) has negative keys held (%d)!", index, value);
}
value = 0;
}
}
@ -2576,7 +2595,7 @@ static void pointer_handle_enter(void *data,
seat->system->seat_active_set(seat);
win->setCursorShape(win->getCursorShape());
seat->system->cursor_shape_set(win->getCursorShape());
const wl_fixed_t scale = win->scale();
seat->system->pushEvent_maybe_pending(
@ -3238,7 +3257,7 @@ static void tablet_tool_handle_proximity_in(void *data,
win->activate();
win->setCursorShape(win->getCursorShape());
seat->system->cursor_shape_set(win->getCursorShape());
}
static void tablet_tool_handle_proximity_out(void *data,
struct zwp_tablet_tool_v2 * /*zwp_tablet_tool_v2*/)
@ -3437,7 +3456,7 @@ static void tablet_tool_handle_frame(void *data,
wl_fixed_to_int(scale * seat->tablet.xy[1]),
tablet_tool->data));
if (tablet_tool->proximity == false) {
win->setCursorShape(win->getCursorShape());
seat->system->cursor_shape_set(win->getCursorShape());
}
}
@ -5514,14 +5533,7 @@ GHOST_TSuccess GHOST_SystemWayland::getModifierKeys(GHOST_ModifierKeys &keys) co
const xkb_mod_mask_t state = xkb_state_serialize_mods(seat->xkb_state, XKB_STATE_MODS_DEPRESSED);
bool show_warning = true;
#ifdef USE_GNOME_KEYBOARD_SUPPRESS_WARNING
if ((seat->key_depressed_suppress_warning.any_mod_held == true) &&
(seat->key_depressed_suppress_warning.any_keys_held_on_enter == false)) {
/* The compositor gave us invalid information, don't show a warning. */
show_warning = false;
}
#endif
const bool show_warning = !gwl_seat_key_depressed_suppress_warning(seat);
/* Use local #GWL_KeyboardDepressedState to check which key is pressed.
* Use XKB as the source of truth, if there is any discrepancy. */
@ -5568,6 +5580,10 @@ GHOST_TSuccess GHOST_SystemWayland::getModifierKeys(GHOST_ModifierKeys &keys) co
GHOST_TSuccess GHOST_SystemWayland::getButtons(GHOST_Buttons &buttons) const
{
#ifdef USE_EVENT_BACKGROUND_THREAD
std::lock_guard lock_server_guard{*server_mutex};
#endif
GWL_Seat *seat = gwl_display_seat_active_get(display_);
if (UNLIKELY(!seat)) {
return GHOST_kFailure;
@ -5796,6 +5812,10 @@ void GHOST_SystemWayland::putClipboard(const char *buffer, bool selection) const
uint8_t GHOST_SystemWayland::getNumDisplays() const
{
#ifdef USE_EVENT_BACKGROUND_THREAD
std::lock_guard lock_server_guard{*server_mutex};
#endif
return display_ ? uint8_t(display_->outputs.size()) : 0;
}
@ -5914,7 +5934,11 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorPosition(const int32_t x, const int
void GHOST_SystemWayland::getMainDisplayDimensions(uint32_t &width, uint32_t &height) const
{
if (getNumDisplays() == 0) {
#ifdef USE_EVENT_BACKGROUND_THREAD
std::lock_guard lock_server_guard{*server_mutex};
#endif
if (display_->outputs.empty()) {
return;
}
/* We assume first output as main. */
@ -5924,6 +5948,10 @@ void GHOST_SystemWayland::getMainDisplayDimensions(uint32_t &width, uint32_t &he
void GHOST_SystemWayland::getAllDisplayDimensions(uint32_t &width, uint32_t &height) const
{
#ifdef USE_EVENT_BACKGROUND_THREAD
std::lock_guard lock_server_guard{*server_mutex};
#endif
int32_t xy_min[2] = {INT32_MAX, INT32_MAX};
int32_t xy_max[2] = {INT32_MIN, INT32_MIN};
@ -5947,6 +5975,7 @@ static GHOST_Context *createOffscreenContext_impl(GHOST_SystemWayland *system,
struct wl_display *wl_display,
wl_egl_window *egl_window)
{
/* Caller must lock `system->server_mutex`. */
GHOST_Context *context;
for (int minor = 6; minor >= 0; --minor) {
@ -6016,6 +6045,10 @@ GHOST_IContext *GHOST_SystemWayland::createOffscreenContext(GHOST_GLSettings /*g
GHOST_TSuccess GHOST_SystemWayland::disposeContext(GHOST_IContext *context)
{
#ifdef USE_EVENT_BACKGROUND_THREAD
std::lock_guard lock_server_guard{*server_mutex};
#endif
struct wl_surface *wl_surface = (struct wl_surface *)((GHOST_Context *)context)->getUserData();
wl_egl_window *egl_window = (wl_egl_window *)wl_surface_get_user_data(wl_surface);
wl_egl_window_destroy(egl_window);
@ -6256,7 +6289,8 @@ static bool cursor_is_software(const GHOST_TGrabCursorMode mode, const bool use_
GHOST_TSuccess GHOST_SystemWayland::cursor_shape_set(const GHOST_TStandardCursor shape)
{
/* No need to lock (caller must lock). */
/* Caller must lock `server_mutex`. */
GWL_Seat *seat = gwl_display_seat_active_get(display_);
if (UNLIKELY(!seat)) {
return GHOST_kFailure;
@ -6299,6 +6333,7 @@ GHOST_TSuccess GHOST_SystemWayland::cursor_shape_set(const GHOST_TStandardCursor
GHOST_TSuccess GHOST_SystemWayland::cursor_shape_check(const GHOST_TStandardCursor cursorShape)
{
/* No need to lock `server_mutex`. */
auto cursor_find = ghost_wl_cursors.find(cursorShape);
if (cursor_find == ghost_wl_cursors.end()) {
return GHOST_kFailure;
@ -6678,6 +6713,8 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod
wl_surface *wl_surface,
const int scale)
{
/* Caller must lock `server_mutex`. */
/* Ignore, if the required protocols are not supported. */
if (UNLIKELY(!display_->wp_relative_pointer_manager || !display_->wp_pointer_constraints)) {
return GHOST_kFailure;

View File

@ -47,8 +47,6 @@ static constexpr size_t base_dpi = 96;
# define use_libdecor GHOST_SystemWayland::use_libdecor_runtime()
#endif
static GHOST_WindowManager *window_manager = nullptr;
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
struct WGL_LibDecor_Window {
struct libdecor_frame *frame = nullptr;
@ -115,7 +113,10 @@ struct GWL_Window {
#endif
WGL_XDG_Decor_Window *xdg_decor = nullptr;
/** The current value of frame, copied from `frame_pending` when applying updates. */
/**
* The current value of frame, copied from `frame_pending` when applying updates.
* This avoids the need for locking when reading from `frame`.
*/
GWL_WindowFrame frame;
GWL_WindowFrame frame_pending;
@ -170,35 +171,24 @@ static GHOST_TWindowState gwl_window_state_get(const GWL_Window *win)
return GHOST_kWindowStateNormal;
}
static bool gwl_window_state_set(GWL_Window *win, const GHOST_TWindowState state)
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
/**
* \note Keep in sync with #gwl_window_state_set_for_xdg.
*/
static bool gwl_window_state_set_for_libdecor(struct libdecor_frame *frame,
const GHOST_TWindowState state,
const GHOST_TWindowState state_current)
{
const GHOST_TWindowState state_current = gwl_window_state_get(win);
switch (state) {
case GHOST_kWindowStateNormal:
/* Unset states. */
switch (state_current) {
case GHOST_kWindowStateMaximized: {
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
if (use_libdecor) {
libdecor_frame_unset_maximized(win->libdecor->frame);
}
else
#endif
{
xdg_toplevel_unset_maximized(win->xdg_decor->toplevel);
}
libdecor_frame_unset_maximized(frame);
break;
}
case GHOST_kWindowStateFullScreen: {
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
if (use_libdecor) {
libdecor_frame_unset_fullscreen(win->libdecor->frame);
}
else
#endif
{
xdg_toplevel_unset_fullscreen(win->xdg_decor->toplevel);
}
libdecor_frame_unset_fullscreen(frame);
break;
}
default: {
@ -207,46 +197,83 @@ static bool gwl_window_state_set(GWL_Window *win, const GHOST_TWindowState state
}
break;
case GHOST_kWindowStateMaximized: {
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
if (use_libdecor) {
libdecor_frame_set_maximized(win->libdecor->frame);
}
else
#endif
{
xdg_toplevel_set_maximized(win->xdg_decor->toplevel);
}
libdecor_frame_set_maximized(frame);
break;
}
case GHOST_kWindowStateMinimized: {
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
if (use_libdecor) {
libdecor_frame_set_minimized(win->libdecor->frame);
}
else
#endif
{
xdg_toplevel_set_minimized(win->xdg_decor->toplevel);
}
libdecor_frame_set_minimized(frame);
break;
}
case GHOST_kWindowStateFullScreen: {
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
if (use_libdecor) {
libdecor_frame_set_fullscreen(win->libdecor->frame, nullptr);
}
else
#endif
{
xdg_toplevel_set_fullscreen(win->xdg_decor->toplevel, nullptr);
}
libdecor_frame_set_fullscreen(frame, nullptr);
break;
}
case GHOST_kWindowStateEmbedded: {
return GHOST_kFailure;
return false;
}
}
return GHOST_kSuccess;
return true;
}
#endif /* WITH_GHOST_WAYLAND_LIBDECOR */
/**
* \note Keep in sync with #gwl_window_state_set_for_libdecor.
*/
static bool gwl_window_state_set_for_xdg(struct xdg_toplevel *toplevel,
const GHOST_TWindowState state,
const GHOST_TWindowState state_current)
{
switch (state) {
case GHOST_kWindowStateNormal:
/* Unset states. */
switch (state_current) {
case GHOST_kWindowStateMaximized: {
xdg_toplevel_unset_maximized(toplevel);
break;
}
case GHOST_kWindowStateFullScreen: {
xdg_toplevel_unset_fullscreen(toplevel);
break;
}
default: {
break;
}
}
break;
case GHOST_kWindowStateMaximized: {
xdg_toplevel_set_maximized(toplevel);
break;
}
case GHOST_kWindowStateMinimized: {
xdg_toplevel_set_minimized(toplevel);
break;
}
case GHOST_kWindowStateFullScreen: {
xdg_toplevel_set_fullscreen(toplevel, nullptr);
break;
}
case GHOST_kWindowStateEmbedded: {
return false;
}
}
return true;
}
static bool gwl_window_state_set(GWL_Window *win, const GHOST_TWindowState state)
{
const GHOST_TWindowState state_current = gwl_window_state_get(win);
bool result;
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
if (use_libdecor) {
result = gwl_window_state_set_for_libdecor(win->libdecor->frame, state, state_current);
}
else
#endif
{
result = gwl_window_state_set_for_xdg(win->xdg_decor->toplevel, state, state_current);
}
return result;
}
/** \} */
@ -326,6 +353,8 @@ static void gwl_window_frame_update_from_pending_lockfree(GWL_Window *win)
#endif
bool do_redraw = 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])) {
@ -333,6 +362,9 @@ static void gwl_window_frame_update_from_pending_lockfree(GWL_Window *win)
}
}
bool is_active_ghost = (win->ghost_window ==
win->ghost_system->getWindowManager()->getActiveWindow());
if (win->frame_pending.is_active) {
win->ghost_window->activate();
}
@ -340,6 +372,10 @@ static void gwl_window_frame_update_from_pending_lockfree(GWL_Window *win)
win->ghost_window->deactivate();
}
if (is_active_ghost != win->frame_pending.is_active) {
do_redraw = true;
}
win->frame_pending.size[0] = win->frame.size[0];
win->frame_pending.size[1] = win->frame.size[1];
@ -348,6 +384,15 @@ static void gwl_window_frame_update_from_pending_lockfree(GWL_Window *win)
/* Signal not to apply the scale unless it's configured. */
win->frame_pending.size[0] = 0;
win->frame_pending.size[1] = 0;
if (do_redraw) {
#ifdef USE_EVENT_BACKGROUND_THREAD
/* Could swap buffers, use pending to a redundant call in some cases. */
gwl_window_pending_actions_tag(win, PENDING_SWAP_BUFFERS);
#else
win->ghost_window->swapBuffers();
#endif
}
}
static void gwl_window_frame_update_from_pending(GWL_Window *win)
@ -543,14 +588,13 @@ static void frame_handle_configure(struct libdecor_frame *frame,
win->libdecor->configured = true;
}
/* Apply the changes. */
{
GWL_Window *win = static_cast<GWL_Window *>(data);
# ifdef USE_EVENT_BACKGROUND_THREAD
GHOST_SystemWayland *system = win->ghost_system;
const bool is_main_thread = system->main_thread_id == std::this_thread::get_id();
if (!is_main_thread) {
/* NOTE(@campbellbarton): this only gets one redraw,
* I could not find a case where this causes problems. */
gwl_window_pending_actions_tag(win, PENDING_FRAME_CONFIGURE);
}
else
@ -747,11 +791,6 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system,
std::lock_guard lock_server_guard{*system->server_mutex};
#endif
/* Globally store pointer to window manager. */
if (!window_manager) {
window_manager = system_->getWindowManager();
}
window_->ghost_window = this;
window_->ghost_system = system;
@ -795,7 +834,13 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system,
* simply called `blender.desktop`, so the it's important to follow that name.
* Other distributions such as SNAP & FLATPAK may need to change this value T101779.
* Currently there isn't a way to configure this, we may want to support that. */
const char *xdg_app_id = "blender";
const char *xdg_app_id = (
#ifdef WITH_GHOST_WAYLAND_APP_ID
STRINGIFY(WITH_GHOST_WAYLAND_APP_ID)
#else
"blender"
#endif
);
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
if (use_libdecor) {
@ -854,26 +899,23 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system,
wl_surface_commit(window_->wl_surface);
wl_display_roundtrip(system_->wl_display());
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
if (use_libdecor) {
WGL_LibDecor_Window &decor = *window_->libdecor;
/* It's important not to return until the window is configured or
* calls to `setState` from Blender will crash `libdecor`. */
while (!decor.configured) {
if (libdecor_dispatch(system_->libdecor_context(), 0) < 0) {
break;
}
}
}
#endif
#ifdef GHOST_OPENGL_ALPHA
setOpaque();
#endif
/* Causes a glitch with `libdecor` for some reason. */
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
if (use_libdecor == false)
if (use_libdecor) {
/* Additional round-trip is needed to ensure `xdg_toplevel` is set. */
wl_display_roundtrip(system_->wl_display());
/* NOTE: LIBDECOR requires the window to be created & configured before the state can be set.
* Workaround this by using the underlying `xdg_toplevel` */
WGL_LibDecor_Window &decor = *window_->libdecor;
struct xdg_toplevel *toplevel = libdecor_frame_get_xdg_toplevel(decor.frame);
gwl_window_state_set_for_xdg(toplevel, state, GHOST_kWindowStateNormal);
}
else
#endif
{
gwl_window_state_set(window_, state);
@ -888,13 +930,13 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system,
setSwapInterval(0);
}
#ifdef USE_EVENT_BACKGROUND_THREAD
GHOST_TSuccess GHOST_WindowWayland::swapBuffers()
{
#ifdef USE_EVENT_BACKGROUND_THREAD
GHOST_ASSERT(system_->main_thread_id == std::this_thread::get_id(), "Only from main thread!");
#endif
return GHOST_Window::swapBuffers();
}
#endif /* USE_EVENT_BACKGROUND_THREAD */
GHOST_TSuccess GHOST_WindowWayland::setWindowCursorGrab(GHOST_TGrabCursorMode mode)
{
@ -924,6 +966,9 @@ GHOST_TSuccess GHOST_WindowWayland::setWindowCursorGrab(GHOST_TGrabCursorMode mo
GHOST_TSuccess GHOST_WindowWayland::setWindowCursorShape(GHOST_TStandardCursor shape)
{
#ifdef USE_EVENT_BACKGROUND_THREAD
std::lock_guard lock_server_guard{*system_->server_mutex};
#endif
const GHOST_TSuccess ok = system_->cursor_shape_set(shape);
m_cursorShape = (ok == GHOST_kSuccess) ? shape : GHOST_kStandardCursorDefault;
return ok;
@ -940,6 +985,9 @@ bool GHOST_WindowWayland::getCursorGrabUseSoftwareDisplay()
GHOST_TSuccess GHOST_WindowWayland::setWindowCustomCursorShape(
uint8_t *bitmap, uint8_t *mask, int sizex, int sizey, int hotX, int hotY, bool canInvertColor)
{
#ifdef USE_EVENT_BACKGROUND_THREAD
std::lock_guard lock_server_guard{*system_->server_mutex};
#endif
return system_->cursor_shape_custom_set(bitmap, mask, sizex, sizey, hotX, hotY, canInvertColor);
}
@ -961,6 +1009,7 @@ void GHOST_WindowWayland::setTitle(const char *title)
std::string GHOST_WindowWayland::getTitle() const
{
/* No need to lock `server_mutex` (WAYLAND never changes this). */
return window_->title.empty() ? "untitled" : window_->title;
}
@ -971,6 +1020,7 @@ void GHOST_WindowWayland::getWindowBounds(GHOST_Rect &bounds) const
void GHOST_WindowWayland::getClientBounds(GHOST_Rect &bounds) const
{
/* No need to lock `server_mutex` (WAYLAND never changes this in a thread). */
bounds.set(0, 0, UNPACK2(window_->frame.size));
}
@ -1053,6 +1103,9 @@ GHOST_WindowWayland::~GHOST_WindowWayland()
uint16_t GHOST_WindowWayland::getDPIHint()
{
/* No need to lock `server_mutex`
* (`outputs_changed_update_scale` never changes values in a non-main thread). */
/* Using the physical DPI will cause wrong scaling of the UI
* use a multiplier for the default DPI as a workaround. */
return wl_fixed_to_int(window_->scale_fractional * base_dpi);

View File

@ -23,13 +23,32 @@
*
* Solve this using a thread that handles events, locking must be performed as follows:
*
* - Lock #GWL_Display.server_mutex to prevent wl_display_dispatch / wl_display_roundtrip
* - Lock #GWL_Display.server_mutex to prevent #wl_display_dispatch / #wl_display_roundtrip
* running from multiple threads at once.
* GHOST functions that communicate with WAYLAND must use this lock to to be thread safe.
*
* Even though WAYLAND functions that communicate with `wl_display_*` have their own locks,
* GHOST functions that communicate with WAYLAND must use this lock to be thread safe.
*
* Without this reading/writing values such as changing the cursor or setting the window size
* could conflict with WAYLAND callbacks running in a separate thread.
* So the `server_mutex` ensures either GHOST or WAYLAND is manipulating this data,
* having two WAYLAND callbacks accessing the data at once isn't a problem.
*
* \warning Some operations such as #ContextEGL creation/deletion & swap-buffers may call
* #wl_display_dispatch indirectly, so it's important to take care to lock the `server_mutex`,
* before accessing these functions too.
*
* \warning An unfortunate side-effect of this is care needs to be taken not to call public
* GHOST functions from each other in situations that would otherwise be supported.
* As both using a `server_mutex` results in a dead-lock. In some cases this means the
* implementation and the public function may need to be split.
*
* - Lock #GWL_Display.timer_mutex when WAYLAND callbacks manipulate timers.
*
* - Lock #GWL_Display.events_pending_mutex before manipulating #GWL_Display.events_pending.
*
* - Lock #GWL_Window.frame_pending_mutex before changing window size & frame settings,
* this is flushed in #GHOST_WindowWayland::pending_actions_handle.
*/
#define USE_EVENT_BACKGROUND_THREAD
@ -59,7 +78,9 @@ class GHOST_WindowWayland : public GHOST_Window {
/* Ghost API */
GHOST_TSuccess swapBuffers() override;
#ifdef USE_EVENT_BACKGROUND_THREAD
GHOST_TSuccess swapBuffers() override; /* Only for assertion. */
#endif
uint16_t getDPIHint() override;

View File

@ -19,6 +19,7 @@ WAYLAND_DYNLOAD_FN(libdecor_configuration_get_window_state)
WAYLAND_DYNLOAD_FN(libdecor_decorate)
WAYLAND_DYNLOAD_FN(libdecor_dispatch)
WAYLAND_DYNLOAD_FN(libdecor_frame_commit)
WAYLAND_DYNLOAD_FN(libdecor_frame_get_xdg_toplevel)
WAYLAND_DYNLOAD_FN(libdecor_frame_map)
WAYLAND_DYNLOAD_FN(libdecor_frame_set_app_id)
WAYLAND_DYNLOAD_FN(libdecor_frame_set_fullscreen)
@ -73,6 +74,7 @@ struct WaylandDynload_Libdecor {
void WL_DYN_FN(libdecor_frame_commit)(struct libdecor_frame *frame,
struct libdecor_state *state,
struct libdecor_configuration *configuration);
struct xdg_toplevel *WL_DYN_FN(libdecor_frame_get_xdg_toplevel)(struct libdecor_frame *frame);
void WL_DYN_FN(libdecor_frame_map)(struct libdecor_frame *frame);
void WL_DYN_FN(libdecor_frame_set_app_id)(struct libdecor_frame *frame, const char *app_id);
void WL_DYN_FN(libdecor_frame_set_fullscreen)(struct libdecor_frame *frame,
@ -108,6 +110,8 @@ struct WaylandDynload_Libdecor {
# define libdecor_dispatch(...) (*wayland_dynload_libdecor.libdecor_dispatch)(__VA_ARGS__)
# define libdecor_frame_commit(...) \
(*wayland_dynload_libdecor.libdecor_frame_commit)(__VA_ARGS__)
# define libdecor_frame_get_xdg_toplevel(...) \
(*wayland_dynload_libdecor.libdecor_frame_get_xdg_toplevel)(__VA_ARGS__)
# define libdecor_frame_map(...) (*wayland_dynload_libdecor.libdecor_frame_map)(__VA_ARGS__)
# define libdecor_frame_set_app_id(...) \
(*wayland_dynload_libdecor.libdecor_frame_set_app_id)(__VA_ARGS__)

View File

@ -549,6 +549,7 @@ CUSTOM_PY_UI_FILES = [
os.path.join("scripts", "startup", "bl_operators"),
os.path.join("scripts", "modules", "rna_prop_ui.py"),
os.path.join("scripts", "modules", "rna_keymap_ui.py"),
os.path.join("scripts", "modules", "bpy_types.py"),
os.path.join("scripts", "presets", "keyconfig"),
]

View File

@ -16,6 +16,7 @@ from bpy.props import (
EnumProperty,
StringProperty,
)
from bpy.app.translations import pgettext_tip as tip_
class ANIM_OT_keying_set_export(Operator):
@ -111,7 +112,7 @@ class ANIM_OT_keying_set_export(Operator):
break
if not found:
self.report({'WARN'}, "Could not find material or light using Shader Node Tree - %s" % (ksp.id))
self.report({'WARN'}, tip_("Could not find material or light using Shader Node Tree - %s") % (ksp.id))
elif ksp.id.bl_rna.identifier.startswith("CompositorNodeTree"):
# Find compositor nodetree using this node tree...
for scene in bpy.data.scenes:
@ -119,7 +120,7 @@ class ANIM_OT_keying_set_export(Operator):
id_bpy_path = "bpy.data.scenes[\"%s\"].node_tree" % (scene.name)
break
else:
self.report({'WARN'}, "Could not find scene using Compositor Node Tree - %s" % (ksp.id))
self.report({'WARN'}, tip_("Could not find scene using Compositor Node Tree - %s") % (ksp.id))
elif ksp.id.bl_rna.name == "Key":
# "keys" conflicts with a Python keyword, hence the simple solution won't work
id_bpy_path = "bpy.data.shape_keys[\"%s\"]" % (ksp.id.name)
@ -324,7 +325,7 @@ class ClearUselessActions(Operator):
action.user_clear()
removed += 1
self.report({'INFO'}, "Removed %d empty and/or fake-user only Actions"
self.report({'INFO'}, tip_("Removed %d empty and/or fake-user only Actions")
% removed)
return {'FINISHED'}
@ -409,7 +410,7 @@ class UpdateAnimatedTransformConstraint(Operator):
print(log)
text = bpy.data.texts.new("UpdateAnimatedTransformConstraint Report")
text.from_string(log)
self.report({'INFO'}, "Complete report available on '%s' text datablock" % text.name)
self.report({'INFO'}, tip_("Complete report available on '%s' text datablock") % text.name)
return {'FINISHED'}

View File

@ -3,8 +3,10 @@ from __future__ import annotations
import bpy
from bpy.types import Operator
from bpy.app.translations import pgettext_data as data_
from bpy.app.translations import (
pgettext_data as data_,
pgettext_tip as tip_,
)
from bpy_extras.asset_utils import (
@ -125,7 +127,7 @@ class ASSET_OT_open_containing_blend_file(Operator):
return {'RUNNING_MODAL'}
if returncode:
self.report({'WARNING'}, "Blender sub-process exited with error code %d" % returncode)
self.report({'WARNING'}, tip_("Blender sub-process exited with error code %d") % returncode)
if bpy.ops.asset.library_refresh.poll():
bpy.ops.asset.library_refresh()

View File

@ -6,6 +6,7 @@ from mathutils import (
Vector,
Matrix,
)
from bpy.app.translations import pgettext_tip as tip_
def CLIP_spaces_walk(context, all_screens, tarea, tspace, callback, *args):
@ -193,7 +194,7 @@ class CLIP_OT_filter_tracks(Operator):
def execute(self, context):
num_tracks = self._filter_values(context, self.track_threshold)
self.report({'INFO'}, "Identified %d problematic tracks" % num_tracks)
self.report({'INFO'}, tip_("Identified %d problematic tracks") % num_tracks)
return {'FINISHED'}

View File

@ -10,6 +10,7 @@ from bpy.props import (
CollectionProperty,
StringProperty,
)
from bpy.app.translations import pgettext_tip as tip_
# ########## Datablock previews... ##########
@ -123,7 +124,7 @@ class WM_OT_previews_batch_generate(Operator):
if not self.use_backups:
cmd.append("--no_backups")
if subprocess.call(cmd):
self.report({'ERROR'}, "Previews generation process failed for file '%s'!" % blen_path)
self.report({'ERROR'}, tip_("Previews generation process failed for file '%s'!") % blen_path)
context.window_manager.progress_end()
return {'CANCELLED'}
context.window_manager.progress_update(i + 1)
@ -234,7 +235,7 @@ class WM_OT_previews_batch_clear(Operator):
if not self.use_backups:
cmd.append("--no_backups")
if subprocess.call(cmd):
self.report({'ERROR'}, "Previews clear process failed for file '%s'!" % blen_path)
self.report({'ERROR'}, tip_("Previews clear process failed for file '%s'!") % blen_path)
context.window_manager.progress_end()
return {'CANCELLED'}
context.window_manager.progress_update(i + 1)

View File

@ -3,6 +3,7 @@
import bpy
from bpy.types import Operator
from bpy.props import StringProperty
from bpy.app.translations import pgettext_tip as tip_
class EditExternally(Operator):
@ -52,8 +53,8 @@ class EditExternally(Operator):
if not os.path.exists(filepath) or not os.path.isfile(filepath):
self.report({'ERROR'},
"Image path %r not found, image may be packed or "
"unsaved" % filepath)
tip_("Image path %r not found, image may be packed or "
"unsaved") % filepath)
return {'CANCELLED'}
cmd = self._editor_guess(context) + [filepath]
@ -183,7 +184,7 @@ class ProjectApply(Operator):
except KeyError:
import traceback
traceback.print_exc()
self.report({'ERROR'}, "Could not find image '%s'" % image_name)
self.report({'ERROR'}, tip_("Could not find image '%s'") % image_name)
return {'CANCELLED'}
image.reload()

View File

@ -7,6 +7,7 @@ from bpy.props import (
EnumProperty,
IntProperty,
)
from bpy.app.translations import pgettext_tip as tip_
class MeshMirrorUV(Operator):
@ -164,18 +165,18 @@ class MeshMirrorUV(Operator):
if total_duplicates and total_no_active_UV:
self.report({'WARNING'},
"%d mesh(es) with no active UV layer, "
"%d duplicates found in %d mesh(es), mirror may be incomplete"
tip_("%d mesh(es) with no active UV layer, "
"%d duplicates found in %d mesh(es), mirror may be incomplete")
% (total_no_active_UV,
total_duplicates,
meshes_with_duplicates))
elif total_no_active_UV:
self.report({'WARNING'},
"%d mesh(es) with no active UV layer"
tip_("%d mesh(es) with no active UV layer")
% (total_no_active_UV,))
elif total_duplicates:
self.report({'WARNING'},
"%d duplicates found in %d mesh(es), mirror may be incomplete"
tip_("%d duplicates found in %d mesh(es), mirror may be incomplete")
% (total_duplicates, meshes_with_duplicates))
return {'FINISHED'}

View File

@ -8,6 +8,7 @@ from bpy.props import (
IntProperty,
StringProperty,
)
from bpy.app.translations import pgettext_tip as tip_
class SelectPattern(Operator):
@ -363,14 +364,12 @@ class ShapeTransfer(Operator):
for ob_other in objects:
if ob_other.type != 'MESH':
self.report({'WARNING'},
("Skipping '%s', "
"not a mesh") % ob_other.name)
tip_("Skipping '%s', not a mesh") % ob_other.name)
continue
me_other = ob_other.data
if len(me_other.vertices) != len(me.vertices):
self.report({'WARNING'},
("Skipping '%s', "
"vertex count differs") % ob_other.name)
tip_("Skipping '%s', vertex count differs") % ob_other.name)
continue
target_normals = me_nos(me_other.vertices)
@ -508,7 +507,7 @@ class JoinUVs(Operator):
if not mesh.uv_layers:
self.report({'WARNING'},
"Object: %s, Mesh: '%s' has no UVs"
tip_("Object: %s, Mesh: '%s' has no UVs")
% (obj.name, mesh.name))
else:
nbr_loops = len(mesh.loops)
@ -531,9 +530,10 @@ class JoinUVs(Operator):
mesh_other.tag = True
if len(mesh_other.loops) != nbr_loops:
self.report({'WARNING'}, "Object: %s, Mesh: "
"'%s' has %d loops (for %d faces),"
" expected %d\n"
self.report({'WARNING'},
tip_("Object: %s, Mesh: "
"'%s' has %d loops (for %d faces),"
" expected %d\n")
% (obj_other.name,
mesh_other.name,
len(mesh_other.loops),
@ -547,9 +547,10 @@ class JoinUVs(Operator):
mesh_other.uv_layers.new()
uv_other = mesh_other.uv_layers.active
if not uv_other:
self.report({'ERROR'}, "Could not add "
"a new UV map to object "
"'%s' (Mesh '%s')\n"
self.report({'ERROR'},
tip_("Could not add "
"a new UV map to object "
"'%s' (Mesh '%s')\n")
% (obj_other.name,
mesh_other.name,
),
@ -784,8 +785,8 @@ class TransformsToDeltasAnim(Operator):
adt = obj.animation_data
if (adt is None) or (adt.action is None):
self.report({'WARNING'},
"No animation data to convert on object: %r" %
obj.name)
tip_("No animation data to convert on object: %r")
% obj.name)
continue
# first pass over F-Curves: ensure that we don't have conflicting
@ -811,8 +812,8 @@ class TransformsToDeltasAnim(Operator):
if fcu.array_index in existingFCurves[dpath]:
# conflict
self.report({'ERROR'},
"Object '%r' already has '%r' F-Curve(s). "
"Remove these before trying again" %
tip_("Object '%r' already has '%r' F-Curve(s). "
"Remove these before trying again") %
(obj.name, dpath))
return {'CANCELLED'}
else:

View File

@ -9,6 +9,7 @@ from bpy.props import (
FloatProperty,
IntProperty,
)
from bpy.app.translations import pgettext_tip as tip_
def object_ensure_material(obj, mat_name):
@ -176,8 +177,8 @@ class QuickExplode(ObjectModeOperator, Operator):
for obj in mesh_objects:
if obj.particle_systems:
self.report({'ERROR'},
"Object %r already has a "
"particle system" % obj.name)
tip_("Object %r already has a "
"particle system") % obj.name)
return {'CANCELLED'}

View File

@ -10,8 +10,11 @@ from bpy.props import (
BoolProperty,
StringProperty,
)
from bpy.app.translations import (
pgettext_tip as tip_,
pgettext_data as data_,
)
from bpy.app.translations import pgettext_data as data_
# For preset popover menu
WindowManager.preset_name = StringProperty(
@ -186,7 +189,7 @@ class AddPresetBase:
else:
os.remove(filepath)
except Exception as e:
self.report({'ERROR'}, "Unable to remove preset: %r" % e)
self.report({'ERROR'}, tip_("Unable to remove preset: %r") % e)
import traceback
traceback.print_exc()
return {'CANCELLED'}
@ -236,7 +239,7 @@ class ExecutePreset(Operator):
ext = splitext(filepath)[1].lower()
if ext not in {".py", ".xml"}:
self.report({'ERROR'}, "Unknown file type: %r" % ext)
self.report({'ERROR'}, tip_("Unknown file type: %r") % ext)
return {'CANCELLED'}
if hasattr(preset_class, "reset_cb"):

View File

@ -4,7 +4,6 @@
import bpy
from bpy.types import Operator
from bpy.app.translations import pgettext_tip as tip_

View File

@ -8,6 +8,7 @@ from bpy.props import (
FloatProperty,
IntProperty,
)
from bpy.app.translations import pgettext_tip as tip_
class SequencerCrossfadeSounds(Operator):
@ -231,7 +232,7 @@ class SequencerFadesAdd(Operator):
sequence.invalidate_cache('COMPOSITE')
sequence_string = "sequence" if len(faded_sequences) == 1 else "sequences"
self.report({'INFO'}, "Added fade animation to %d %s" % (len(faded_sequences), sequence_string))
self.report({'INFO'}, tip_("Added fade animation to %d %s") % (len(faded_sequences), sequence_string))
return {'FINISHED'}
def calculate_fade_duration(self, context, sequence):

View File

@ -12,8 +12,10 @@ from bpy.props import (
StringProperty,
CollectionProperty,
)
from bpy.app.translations import pgettext_tip as tip_
from bpy.app.translations import (
pgettext_iface as iface_,
pgettext_tip as tip_,
)
def _zipfile_root_namelist(file_to_extract):
@ -224,7 +226,7 @@ class PREFERENCES_OT_keyconfig_import(Operator):
else:
shutil.move(self.filepath, path)
except Exception as ex:
self.report({'ERROR'}, "Installing keymap failed: %s" % ex)
self.report({'ERROR'}, tip_("Installing keymap failed: %s") % ex)
return {'CANCELLED'}
# sneaky way to check we're actually running the code.
@ -450,11 +452,11 @@ class PREFERENCES_OT_addon_enable(Operator):
if info_ver > bpy.app.version:
self.report(
{'WARNING'},
"This script was written Blender "
"version %d.%d.%d and might not "
"function (correctly), "
"though it is enabled" %
info_ver
tip_("This script was written Blender "
"version %d.%d.%d and might not "
"function (correctly), "
"though it is enabled")
% info_ver
)
return {'FINISHED'}
else:
@ -538,7 +540,7 @@ class PREFERENCES_OT_theme_install(Operator):
if not self.overwrite:
if os.path.exists(path_dest):
self.report({'WARNING'}, "File already installed to %r\n" % path_dest)
self.report({'WARNING'}, tip_("File already installed to %r\n") % path_dest)
return {'CANCELLED'}
try:
@ -645,7 +647,7 @@ class PREFERENCES_OT_addon_install(Operator):
pyfile_dir = os.path.dirname(pyfile)
for addon_path in addon_utils.paths():
if os.path.samefile(pyfile_dir, addon_path):
self.report({'ERROR'}, "Source file is in the add-on search path: %r" % addon_path)
self.report({'ERROR'}, tip_("Source file is in the add-on search path: %r") % addon_path)
return {'CANCELLED'}
del addon_path
del pyfile_dir
@ -669,7 +671,7 @@ class PREFERENCES_OT_addon_install(Operator):
for f in file_to_extract_root:
path_dest = os.path.join(path_addons, os.path.basename(f))
if os.path.exists(path_dest):
self.report({'WARNING'}, "File already installed to %r\n" % path_dest)
self.report({'WARNING'}, tip_("File already installed to %r\n") % path_dest)
return {'CANCELLED'}
try: # extract the file to "addons"
@ -684,7 +686,7 @@ class PREFERENCES_OT_addon_install(Operator):
if self.overwrite:
_module_filesystem_remove(path_addons, os.path.basename(pyfile))
elif os.path.exists(path_dest):
self.report({'WARNING'}, "File already installed to %r\n" % path_dest)
self.report({'WARNING'}, tip_("File already installed to %r\n") % path_dest)
return {'CANCELLED'}
# if not compressed file just copy into the addon path
@ -764,7 +766,7 @@ class PREFERENCES_OT_addon_remove(Operator):
path, isdir = PREFERENCES_OT_addon_remove.path_from_addon(self.module)
if path is None:
self.report({'WARNING'}, "Add-on path %r could not be found" % path)
self.report({'WARNING'}, tip_("Add-on path %r could not be found") % path)
return {'CANCELLED'}
# in case its enabled
@ -783,9 +785,9 @@ class PREFERENCES_OT_addon_remove(Operator):
# lame confirmation check
def draw(self, _context):
self.layout.label(text="Remove Add-on: %r?" % self.module)
self.layout.label(text=iface_("Remove Add-on: %r?") % self.module, translate=False)
path, _isdir = PREFERENCES_OT_addon_remove.path_from_addon(self.module)
self.layout.label(text="Path: %r" % path)
self.layout.label(text=iface_("Path: %r") % path, translate=False)
def invoke(self, context, _event):
wm = context.window_manager
@ -914,7 +916,7 @@ class PREFERENCES_OT_app_template_install(Operator):
for f in file_to_extract_root:
path_dest = os.path.join(path_app_templates, os.path.basename(f))
if os.path.exists(path_dest):
self.report({'WARNING'}, "File already installed to %r\n" % path_dest)
self.report({'WARNING'}, tip_("File already installed to %r\n") % path_dest)
return {'CANCELLED'}
try: # extract the file to "bl_app_templates_user"
@ -925,7 +927,7 @@ class PREFERENCES_OT_app_template_install(Operator):
else:
# Only support installing zipfiles
self.report({'WARNING'}, "Expected a zip-file %r\n" % filepath)
self.report({'WARNING'}, tip_("Expected a zip-file %r\n") % filepath)
return {'CANCELLED'}
app_templates_new = set(os.listdir(path_app_templates)) - app_templates_old

View File

@ -772,7 +772,7 @@ class WM_OT_operator_pie_enum(Operator):
try:
op_rna = op.get_rna_type()
except KeyError:
self.report({'ERROR'}, "Operator not found: bpy.ops.%s" % data_path)
self.report({'ERROR'}, tip_("Operator not found: bpy.ops.%s") % data_path)
return {'CANCELLED'}
def draw_cb(self, context):
@ -872,7 +872,7 @@ class WM_OT_context_collection_boolean_set(Operator):
elif value_orig is False:
pass
else:
self.report({'WARNING'}, "Non boolean value found: %s[ ].%s" %
self.report({'WARNING'}, tip_("Non boolean value found: %s[ ].%s") %
(data_path_iter, data_path_item))
return {'CANCELLED'}
@ -975,7 +975,7 @@ class WM_OT_context_modal_mouse(Operator):
(item, ) = self._values.keys()
header_text = header_text % eval("item.%s" % self.data_path_item)
else:
header_text = (self.header_text % delta) + " (delta)"
header_text = (self.header_text % delta) + tip_(" (delta)")
context.area.header_text_set(header_text)
elif 'LEFTMOUSE' == event_type:
@ -995,7 +995,7 @@ class WM_OT_context_modal_mouse(Operator):
self._values_store(context)
if not self._values:
self.report({'WARNING'}, "Nothing to operate on: %s[ ].%s" %
self.report({'WARNING'}, tip_("Nothing to operate on: %s[ ].%s") %
(self.data_path_iter, self.data_path_item))
return {'CANCELLED'}
@ -1133,7 +1133,7 @@ class WM_OT_path_open(Operator):
filepath = os.path.normpath(filepath)
if not os.path.exists(filepath):
self.report({'ERROR'}, "File '%s' not found" % filepath)
self.report({'ERROR'}, tip_("File '%s' not found") % filepath)
return {'CANCELLED'}
if sys.platform[:3] == "win":
@ -1204,7 +1204,7 @@ def _wm_doc_get_id(doc_id, *, do_url=True, url_prefix="", report=None):
if rna_class is None:
if report is not None:
report({'ERROR'}, iface_("Type \"%s\" can not be found") % class_name)
report({'ERROR'}, tip_("Type \"%s\" can not be found") % class_name)
return None
# Detect if this is a inherited member and use that name instead.
@ -1275,9 +1275,9 @@ class WM_OT_doc_view_manual(Operator):
if url is None:
self.report(
{'WARNING'},
"No reference available %r, "
"Update info in 'rna_manual_reference.py' "
"or callback to bpy.utils.manual_map()" %
tip_("No reference available %r, "
"Update info in 'rna_manual_reference.py' "
"or callback to bpy.utils.manual_map()") %
self.doc_id
)
return {'CANCELLED'}
@ -2156,7 +2156,7 @@ class WM_OT_tool_set_by_id(Operator):
tool_settings.workspace_tool_type = 'FALLBACK'
return {'FINISHED'}
else:
self.report({'WARNING'}, "Tool %r not found for space %r" % (self.name, space_type))
self.report({'WARNING'}, tip_("Tool %r not found for space %r") % (self.name, space_type))
return {'CANCELLED'}
@ -2289,7 +2289,7 @@ class WM_OT_toolbar_fallback_pie(Operator):
ToolSelectPanelHelper.draw_fallback_tool_items_for_pie_menu(self.layout, context)
wm = context.window_manager
wm.popup_menu_pie(draw_func=draw_cb, title="Fallback Tool", event=event)
wm.popup_menu_pie(draw_func=draw_cb, title=iface_("Fallback Tool"), event=event)
return {'FINISHED'}
@ -2943,7 +2943,7 @@ class WM_OT_batch_rename(Operator):
row.prop(action, "op_remove", text="", icon='REMOVE')
row.prop(action, "op_add", text="", icon='ADD')
layout.label(text=iface_("Rename %d %s") % (len(self._data[0]), self._data[2]))
layout.label(text=iface_("Rename %d %s") % (len(self._data[0]), self._data[2]), translate=False)
def check(self, context):
changed = False
@ -3083,7 +3083,7 @@ class WM_MT_splash_quick_setup(Menu):
old_version = bpy.types.PREFERENCES_OT_copy_prev.previous_version()
if bpy.types.PREFERENCES_OT_copy_prev.poll(context) and old_version:
sub.operator("preferences.copy_prev", text=iface_("Load %d.%d Settings", "Operator") % old_version)
sub.operator("preferences.copy_prev", text=iface_("Load %d.%d Settings", "Operator") % old_version, translate=False)
sub.operator("wm.save_userpref", text="Save New Settings")
else:
sub.label()

View File

@ -3,8 +3,10 @@ import bpy
from bpy.types import Menu, Panel, UIList
from rna_prop_ui import PropertyPanel
from bpy.app.translations import pgettext_tip as tip_
from bpy.app.translations import (
pgettext_tip as iface_,
pgettext_tip as tip_,
)
class MESH_MT_vertex_group_context_menu(Menu):
bl_label = "Vertex Group Specials"
@ -550,7 +552,8 @@ class MESH_UL_attributes(UIList):
sub = split.row()
sub.alignment = 'RIGHT'
sub.active = False
sub.label(text="%s%s" % (domain_name, data_type.name))
sub.label(text="%s%s" % (iface_(domain_name), iface_(data_type.name)),
translate=False)
class DATA_PT_mesh_attributes(MeshButtonsPanel, Panel):
@ -651,7 +654,8 @@ class MESH_UL_color_attributes(UIList, ColorAttributesListBase):
sub = split.row()
sub.alignment = 'RIGHT'
sub.active = False
sub.label(text="%s%s" % (domain_name, data_type.name))
sub.label(text="%s%s" % (iface_(domain_name), iface_(data_type.name)),
translate=False)
active_render = _index == data.color_attributes.render_color_index

View File

@ -400,9 +400,12 @@ class PARTICLE_PT_hair_dynamics(ParticleButtonsPanel, Panel):
label = "ERROR"
icon = 'ERROR'
box.label(text=label, icon=icon)
box.label(text="Iterations: %d .. %d (avg. %d)" %
(result.min_iterations, result.max_iterations, result.avg_iterations))
box.label(text="Error: %.5f .. %.5f (avg. %.5f)" % (result.min_error, result.max_error, result.avg_error))
box.label(text=iface_("Iterations: %d .. %d (avg. %d)") %
(result.min_iterations, result.max_iterations, result.avg_iterations),
translate=False)
box.label(text=iface_("Error: %.5f .. %.5f (avg. %.5f)")
% (result.min_error, result.max_error, result.avg_error),
translate=False)
class PARTICLE_PT_hair_dynamics_collision(ParticleButtonsPanel, Panel):
@ -756,7 +759,7 @@ class PARTICLE_PT_physics_fluid_advanced(ParticleButtonsPanel, Panel):
particle_volume = part.mass / fluid.rest_density
spacing = pow(particle_volume, 1.0 / 3.0)
sub.label(text="Spacing: %g" % spacing)
sub.label(text=iface_("Spacing: %g") % spacing, translate=False)
class PARTICLE_PT_physics_fluid_springs(ParticleButtonsPanel, Panel):

View File

@ -3,6 +3,7 @@ import bpy
from bpy.types import (
Panel,
)
from bpy.app.translations import pgettext_iface as iface_
from rna_prop_ui import PropertyPanel
@ -67,7 +68,8 @@ class WORKSPACE_PT_addons(WorkSpaceButtonsPanel, Panel):
row.operator(
"wm.owner_disable" if is_enabled else "wm.owner_enable",
icon='CHECKBOX_HLT' if is_enabled else 'CHECKBOX_DEHLT',
text="%s: %s" % (info["category"], info["name"]),
text=iface_("%s: %s" ) % (iface_(info["category"]), iface_(info["name"])),
translate=False,
emboss=False,
).owner_id = module_name
if is_enabled:

View File

@ -168,8 +168,9 @@ class CLIP_HT_header(Header):
r = active_object.reconstruction
if r.is_valid and sc.view == 'CLIP':
layout.label(text="Solve error: %.2f px" %
(r.average_error))
layout.label(text=iface_("Solve error: %.2f px") %
(r.average_error),
translate=False)
row = layout.row()
row.prop(sc, "pivot_point", text="", icon_only=True)
@ -744,8 +745,8 @@ class CLIP_PT_track(CLIP_PT_tracking_panel, Panel):
layout.prop(act_track, "weight_stab")
if act_track.has_bundle:
label_text = "Average Error: %.2f px" % (act_track.average_error)
layout.label(text=label_text)
label_text = iface_("Average Error: %.2f px") % (act_track.average_error)
layout.label(text=label_text, translate=False)
layout.use_property_split = False

View File

@ -407,15 +407,20 @@ class TOPBAR_MT_file_defaults(Menu):
app_template = None
if app_template:
layout.label(text=bpy.path.display_name(
app_template, has_ext=False))
layout.label(
text=iface_(bpy.path.display_name(app_template, has_ext=False),
i18n_contexts.id_workspace), translate=False)
layout.operator("wm.save_homefile")
if app_template:
display_name = bpy.path.display_name(iface_(app_template))
props = layout.operator("wm.read_factory_settings", text="Load Factory Blender Settings")
props = layout.operator("wm.read_factory_settings",
text="Load Factory Blender Settings")
props.app_template = app_template
props = layout.operator("wm.read_factory_settings", text="Load Factory %s Settings" % display_name)
props = layout.operator("wm.read_factory_settings",
text=iface_("Load Factory %s Settings",
i18n_contexts.operator_default) % display_name,
translate=False)
props.app_template = app_template
props.use_factory_startup_app_template_only = True
del display_name

View File

@ -114,7 +114,9 @@ class USERPREF_MT_save_load(Menu):
if app_template:
display_name = bpy.path.display_name(iface_(app_template))
layout.operator("wm.read_factory_userpref", text="Load Factory Blender Preferences")
props = layout.operator("wm.read_factory_userpref", text="Load Factory %s Preferences" % display_name)
props = layout.operator("wm.read_factory_userpref",
text=iface_("Load Factory %s Preferences") % display_name,
translate=False)
props.use_factory_startup_app_template_only = True
del display_name
else:
@ -2317,7 +2319,6 @@ class USERPREF_PT_experimental_prototypes(ExperimentalPanel, Panel):
({"property": "use_sculpt_texture_paint"}, "T96225"),
({"property": "use_full_frame_compositor"}, "T88150"),
({"property": "enable_eevee_next"}, "T93220"),
({"property": "use_draw_manager_acquire_lock"}, "T98016"),
),
)

View File

@ -18,7 +18,11 @@ from bl_ui.properties_grease_pencil_common import (
from bl_ui.space_toolsystem_common import (
ToolActivePanelHelper,
)
from bpy.app.translations import contexts as i18n_contexts
from bpy.app.translations import (
pgettext_iface as iface_,
pgettext_tip as tip_,
contexts as i18n_contexts,
)
class VIEW3D_HT_tool_header(Header):
@ -700,7 +704,7 @@ class VIEW3D_HT_header(Header):
sub.ui_units_x = 5.5
sub.operator_menu_enum(
"object.mode_set", "mode",
text=bpy.app.translations.pgettext_iface(act_mode_item.name, act_mode_i18n_context),
text=iface_(act_mode_item.name, act_mode_i18n_context),
icon=act_mode_item.icon,
)
del act_mode_item
@ -1137,7 +1141,9 @@ class VIEW3D_MT_mirror(Menu):
for (space_name, space_id) in (("Global", 'GLOBAL'), ("Local", 'LOCAL')):
for axis_index, axis_name in enumerate("XYZ"):
props = layout.operator("transform.mirror", text="%s %s" % (axis_name, space_name))
props = layout.operator("transform.mirror",
text="%s %s" % (axis_name, iface_(space_name)),
translate=False)
props.constraint_axis[axis_index] = True
props.orient_type = space_id
@ -2553,16 +2559,16 @@ class VIEW3D_MT_object_context_menu(Menu):
props.data_path_item = "data.lens"
props.input_scale = 0.1
if obj.data.lens_unit == 'MILLIMETERS':
props.header_text = "Camera Focal Length: %.1fmm"
props.header_text = tip_("Camera Focal Length: %.1fmm")
else:
props.header_text = "Camera Focal Length: %.1f\u00B0"
props.header_text = tip_("Camera Focal Length: %.1f\u00B0")
else:
props = layout.operator("wm.context_modal_mouse", text="Camera Lens Scale")
props.data_path_iter = "selected_editable_objects"
props.data_path_item = "data.ortho_scale"
props.input_scale = 0.01
props.header_text = "Camera Lens Scale: %.3f"
props.header_text = tip_("Camera Lens Scale: %.3f")
if not obj.data.dof.focus_object:
if view and view.camera == obj and view.region_3d.view_perspective == 'CAMERA':
@ -2572,7 +2578,7 @@ class VIEW3D_MT_object_context_menu(Menu):
props.data_path_iter = "selected_editable_objects"
props.data_path_item = "data.dof.focus_distance"
props.input_scale = 0.02
props.header_text = "Focus Distance: %.3f"
props.header_text = tip_("Focus Distance: %.3f")
layout.separator()
@ -2583,13 +2589,13 @@ class VIEW3D_MT_object_context_menu(Menu):
props.data_path_iter = "selected_editable_objects"
props.data_path_item = "data.extrude"
props.input_scale = 0.01
props.header_text = "Extrude: %.3f"
props.header_text = tip_("Extrude: %.3f")
props = layout.operator("wm.context_modal_mouse", text="Adjust Offset")
props.data_path_iter = "selected_editable_objects"
props.data_path_item = "data.offset"
props.input_scale = 0.01
props.header_text = "Offset: %.3f"
props.header_text = tip_("Offset: %.3f")
layout.separator()
@ -2600,7 +2606,7 @@ class VIEW3D_MT_object_context_menu(Menu):
props.data_path_iter = "selected_editable_objects"
props.data_path_item = "empty_display_size"
props.input_scale = 0.01
props.header_text = "Empty Display Size: %.3f"
props.header_text = tip_("Empty Display Size: %.3f")
layout.separator()
@ -2618,36 +2624,36 @@ class VIEW3D_MT_object_context_menu(Menu):
props.data_path_iter = "selected_editable_objects"
props.data_path_item = "data.energy"
props.input_scale = 1.0
props.header_text = "Light Power: %.3f"
props.header_text = tip_("Light Power: %.3f")
if light.type == 'AREA':
if light.shape in {'RECTANGLE', 'ELLIPSE'}:
props = layout.operator("wm.context_modal_mouse", text="Adjust Area Light X Size")
props.data_path_iter = "selected_editable_objects"
props.data_path_item = "data.size"
props.header_text = "Light Size X: %.3f"
props.header_text = tip_("Light Size X: %.3f")
props = layout.operator("wm.context_modal_mouse", text="Adjust Area Light Y Size")
props.data_path_iter = "selected_editable_objects"
props.data_path_item = "data.size_y"
props.header_text = "Light Size Y: %.3f"
props.header_text = tip_("Light Size Y: %.3f")
else:
props = layout.operator("wm.context_modal_mouse", text="Adjust Area Light Size")
props.data_path_iter = "selected_editable_objects"
props.data_path_item = "data.size"
props.header_text = "Light Size: %.3f"
props.header_text = tip_("Light Size: %.3f")
elif light.type in {'SPOT', 'POINT'}:
props = layout.operator("wm.context_modal_mouse", text="Adjust Light Radius")
props.data_path_iter = "selected_editable_objects"
props.data_path_item = "data.shadow_soft_size"
props.header_text = "Light Radius: %.3f"
props.header_text = tip_("Light Radius: %.3f")
elif light.type == 'SUN':
props = layout.operator("wm.context_modal_mouse", text="Adjust Sun Light Angle")
props.data_path_iter = "selected_editable_objects"
props.data_path_item = "data.angle"
props.header_text = "Light Angle: %.3f"
props.header_text = tip_("Light Angle: %.3f")
if light.type == 'SPOT':
layout.separator()
@ -2656,13 +2662,13 @@ class VIEW3D_MT_object_context_menu(Menu):
props.data_path_iter = "selected_editable_objects"
props.data_path_item = "data.spot_size"
props.input_scale = 0.01
props.header_text = "Spot Size: %.2f"
props.header_text = tip_("Spot Size: %.2f")
props = layout.operator("wm.context_modal_mouse", text="Adjust Spot Light Blend")
props.data_path_iter = "selected_editable_objects"
props.data_path_item = "data.spot_blend"
props.input_scale = -0.01
props.header_text = "Spot Blend: %.2f"
props.header_text = tip_("Spot Blend: %.2f")
layout.separator()
@ -7108,13 +7114,13 @@ class VIEW3D_PT_overlay_gpencil_options(Panel):
def draw_header(self, context):
layout = self.layout
layout.label(text={
'PAINT_GPENCIL': "Draw Grease Pencil",
'EDIT_GPENCIL': "Edit Grease Pencil",
'SCULPT_GPENCIL': "Sculpt Grease Pencil",
'WEIGHT_GPENCIL': "Weight Grease Pencil",
'VERTEX_GPENCIL': "Vertex Grease Pencil",
'OBJECT': "Grease Pencil",
}[context.mode])
'PAINT_GPENCIL': iface_("Draw Grease Pencil"),
'EDIT_GPENCIL': iface_("Edit Grease Pencil"),
'SCULPT_GPENCIL': iface_("Sculpt Grease Pencil"),
'WEIGHT_GPENCIL': iface_("Weight Grease Pencil"),
'VERTEX_GPENCIL': iface_("Vertex Grease Pencil"),
'OBJECT': iface_("Grease Pencil"),
}[context.mode], translate=False)
def draw(self, context):
layout = self.layout

View File

@ -8,6 +8,7 @@
#include "DNA_asset_types.h"
#include "BLI_set.hh"
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
@ -90,7 +91,7 @@ struct AssetLibrary {
* already in memory and which not. Neither do we keep track of how many parts of Blender are
* using an asset or an asset library, which is needed to know when assets can be freed.
*/
Vector<std::unique_ptr<AssetRepresentation>> asset_storage_;
Set<std::unique_ptr<AssetRepresentation>> asset_storage_;
std::optional<int> find_asset_index(const AssetRepresentation &asset);
};

View File

@ -139,39 +139,25 @@ void AssetLibrary::refresh()
AssetRepresentation &AssetLibrary::add_external_asset(StringRef name,
std::unique_ptr<AssetMetaData> metadata)
{
asset_storage_.append(std::make_unique<AssetRepresentation>(name, std::move(metadata)));
return *asset_storage_.last();
return *asset_storage_.lookup_key_or_add(
std::make_unique<AssetRepresentation>(name, std::move(metadata)));
}
AssetRepresentation &AssetLibrary::add_local_id_asset(ID &id)
{
asset_storage_.append(std::make_unique<AssetRepresentation>(id));
return *asset_storage_.last();
}
std::optional<int> AssetLibrary::find_asset_index(const AssetRepresentation &asset)
{
int index = 0;
/* Find index of asset. */
for (auto &asset_uptr : asset_storage_) {
if (&asset == asset_uptr.get()) {
return index;
}
index++;
}
return {};
return *asset_storage_.lookup_key_or_add(std::make_unique<AssetRepresentation>(id));
}
bool AssetLibrary::remove_asset(AssetRepresentation &asset)
{
std::optional<int> asset_index = find_asset_index(asset);
if (!asset_index) {
return false;
}
/* Create a "fake" unique_ptr to figure out the hash for the pointed to asset representation. The
* standard requires that this is the same for all unique_ptr's wrapping the same address. */
std::unique_ptr<AssetRepresentation> fake_asset_ptr{&asset};
asset_storage_.remove_and_reorder(*asset_index);
return true;
const bool was_removed = asset_storage_.remove_as(fake_asset_ptr);
/* Make sure the contained storage is not destructed. */
fake_asset_ptr.release();
return was_removed;
}
namespace {

View File

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

View File

@ -90,6 +90,13 @@ typedef struct BPathForeachPathData {
/** The root to use as base for relative paths. Only set if `BKE_BPATH_FOREACH_PATH_ABSOLUTE`
* flag is set, NULL otherwise. */
const char *absolute_base_path;
/** ID owning the path being processed. */
struct ID *owner_id;
/** IDTypeInfo callbacks are responsible to set this boolean if they modified one or more paths.
*/
bool is_path_modified;
} BPathForeachPathData;
/** Run `bpath_data.callback_function` on all paths contained in `id`. */

View File

@ -11,12 +11,14 @@
#include <mutex>
#include "BLI_bounds_types.hh"
#include "BLI_cache_mutex.hh"
#include "BLI_float3x3.hh"
#include "BLI_float4x4.hh"
#include "BLI_generic_virtual_array.hh"
#include "BLI_index_mask.hh"
#include "BLI_math_vec_types.hh"
#include "BLI_shared_cache.hh"
#include "BLI_span.hh"
#include "BLI_task.hh"
#include "BLI_vector.hh"
@ -63,9 +65,7 @@ struct BasisCache {
} // namespace curves::nurbs
/**
* Contains derived data, caches, and other information not saved in files, besides a few pointers
* to arrays that are kept in the non-runtime struct to avoid dereferencing this whenever they are
* accessed.
* Contains derived data, caches, and other information not saved in files.
*/
class CurvesGeometryRuntime {
public:
@ -95,6 +95,13 @@ class CurvesGeometryRuntime {
*/
mutable Span<float3> evaluated_positions_span;
/**
* A cache of bounds shared between data-blocks with unchanged positions and radii.
* When data changes affect the bounds, the cache is "un-shared" with other geometries.
* See #SharedCache comments.
*/
mutable SharedCache<Bounds<float3>> bounds_cache;
/**
* Cache of lengths along each evaluated curve for each evaluated point. If a curve is
* cyclic, it needs one more length value to correspond to the last segment, so in order to
@ -391,6 +398,11 @@ class CurvesGeometry : public ::CurvesGeometry {
void tag_topology_changed();
/** Call after changing the "tilt" or "up" attributes. */
void tag_normals_changed();
/**
* Call when making manual changes to the "radius" attribute. The attribute API will also call
* this in #finish() calls.
*/
void tag_radii_changed();
void translate(const float3 &translation);
void transform(const float4x4 &matrix);

View File

@ -381,15 +381,9 @@ const float (*BKE_mesh_poly_normals_ensure(const struct Mesh *mesh))[3];
void BKE_mesh_normals_tag_dirty(struct Mesh *mesh);
/**
* Check that a mesh with non-dirty normals has vertex and face custom data layers.
* If these asserts fail, it means some area cleared the dirty flag but didn't copy or add the
* normal layers, or removed normals but didn't set the dirty flag.
*/
void BKE_mesh_assert_normals_dirty_or_calculated(const struct Mesh *mesh);
/**
* Retrieve write access to the vertex normal layer, ensuring that it exists and that it is not
* shared. The provided vertex normals should be the same as if they were calculated automatically.
* Retrieve write access to the cached vertex normals, ensuring that they are allocated but *not*
* that they are calculated. The provided vertex normals should be the same as if they were
* calculated automatically.
*
* \note In order to clear the dirty flag, this function should be followed by a call to
* #BKE_mesh_vertex_normals_clear_dirty. This is separate so that normals are still tagged dirty
@ -401,8 +395,9 @@ void BKE_mesh_assert_normals_dirty_or_calculated(const struct Mesh *mesh);
float (*BKE_mesh_vertex_normals_for_write(struct Mesh *mesh))[3];
/**
* Retrieve write access to the poly normal layer, ensuring that it exists and that it is not
* shared. The provided poly normals should be the same as if they were calculated automatically.
* Retrieve write access to the cached polygon normals, ensuring that they are allocated but *not*
* that they are calculated. The provided polygon normals should be the same as if they were
* calculated automatically.
*
* \note In order to clear the dirty flag, this function should be followed by a call to
* #BKE_mesh_poly_normals_clear_dirty. This is separate so that normals are still tagged dirty
@ -413,17 +408,6 @@ float (*BKE_mesh_vertex_normals_for_write(struct Mesh *mesh))[3];
*/
float (*BKE_mesh_poly_normals_for_write(struct Mesh *mesh))[3];
/**
* Free any cached vertex or poly normals. Face corner (loop) normals are also derived data,
* but are not handled with the same method yet, so they are not included. It's important that this
* is called after the mesh changes size, since otherwise cached normal arrays might not be large
* enough (though it may be called indirectly by other functions).
*
* \note Normally it's preferred to call #BKE_mesh_normals_tag_dirty instead,
* but this can be used in specific situations to reset a mesh or reduce memory usage.
*/
void BKE_mesh_clear_derived_normals(struct Mesh *mesh);
/**
* Mark the mesh's vertex normals non-dirty, for when they are calculated or assigned manually.
*/
@ -488,12 +472,6 @@ void BKE_mesh_calc_normals(struct Mesh *me);
* Called after calculating all modifiers.
*/
void BKE_mesh_ensure_normals_for_display(struct Mesh *mesh);
void BKE_mesh_calc_normals_looptri(const struct MVert *mverts,
int numVerts,
const struct MLoop *mloop,
const struct MLoopTri *looptri,
int looptri_num,
float (*r_tri_nors)[3]);
/**
* Define sharp edges as needed to mimic 'autosmooth' from angle threshold.
@ -987,10 +965,10 @@ void BKE_mesh_eval_geometry(struct Depsgraph *depsgraph, struct Mesh *mesh);
/* Draw Cache */
void BKE_mesh_batch_cache_dirty_tag(struct Mesh *me, eMeshBatchDirtyMode mode);
void BKE_mesh_batch_cache_free(struct Mesh *me);
void BKE_mesh_batch_cache_free(void *batch_cache);
extern void (*BKE_mesh_batch_cache_dirty_tag_cb)(struct Mesh *me, eMeshBatchDirtyMode mode);
extern void (*BKE_mesh_batch_cache_free_cb)(struct Mesh *me);
extern void (*BKE_mesh_batch_cache_free_cb)(void *batch_cache);
/* mesh_debug.c */

View File

@ -8,14 +8,12 @@
* This file contains access functions for the Mesh.runtime struct.
*/
//#include "BKE_customdata.h" /* for eCustomDataMask */
#include "BKE_mesh_types.h"
#ifdef __cplusplus
extern "C" {
#endif
struct CustomData;
struct CustomData_MeshMasks;
struct Depsgraph;
struct KeyBlock;
@ -26,31 +24,44 @@ struct Mesh;
struct Object;
struct Scene;
/**
* \brief Free all data (and mutexes) inside the runtime of the given mesh.
*/
void BKE_mesh_runtime_free_data(struct Mesh *mesh);
/** Return the number of derived triangles (looptris). */
int BKE_mesh_runtime_looptri_len(const struct Mesh *mesh);
void BKE_mesh_runtime_looptri_recalc(struct Mesh *mesh);
/**
* \note This function only fills a cache, and therefore the mesh argument can
* be considered logically const. Concurrent access is protected by a mutex.
* \note This is a ported copy of dm_getLoopTriArray(dm).
* Return mesh triangulation data, calculated lazily when necessary necessary.
* See #MLoopTri for further description of mesh triangulation.
*
* \note Prefer #Mesh::looptris() in C++ code.
*/
const struct MLoopTri *BKE_mesh_runtime_looptri_ensure(const struct Mesh *mesh);
bool BKE_mesh_runtime_ensure_edit_data(struct Mesh *mesh);
bool BKE_mesh_runtime_clear_edit_data(struct Mesh *mesh);
bool BKE_mesh_runtime_reset_edit_data(struct Mesh *mesh);
void BKE_mesh_runtime_clear_geometry(struct Mesh *mesh);
void BKE_mesh_runtime_reset_edit_data(struct Mesh *mesh);
/**
* \brief This function clears runtime cache of the given mesh.
* Clear and free any derived caches associated with the mesh geometry data. Examples include BVH
* caches, normals, triangulation, etc. This should be called when replacing a mesh's geometry
* directly or making other large changes to topology. It does not need to be called on new meshes.
*
* Call this function to recalculate runtime data when used.
* For "smaller" changes to meshes like updating positions, consider calling a more specific update
* function like #BKE_mesh_tag_coords_changed.
*
* Also note that some derived caches like #CD_NORMAL and #CD_TANGENT are stored directly in
* #CustomData.
*/
void BKE_mesh_runtime_clear_geometry(struct Mesh *mesh);
/**
* Similar to #BKE_mesh_runtime_clear_geometry, but subtly different in that it also clears
* data-block level features like evaluated data-blocks and edit mode data. They will be
* functionally the same in most cases, but prefer this function if unsure, since it clears
* more data.
*/
void BKE_mesh_runtime_clear_cache(struct Mesh *mesh);
/* This is a copy of DM_verttri_from_looptri(). */
/**
* Convert triangles encoded as face corner indices to triangles encoded as vertex indices.
*/
void BKE_mesh_runtime_verttri_from_looptri(struct MVertTri *r_verttri,
const struct MLoop *mloop,
const struct MLoopTri *looptri,

View File

@ -15,6 +15,10 @@
# include "DNA_customdata_types.h"
# include "BLI_bounds_types.hh"
# include "BLI_math_vec_types.hh"
# include "BLI_shared_cache.hh"
# include "MEM_guardedalloc.h"
struct BVHCache;
@ -84,6 +88,12 @@ struct MeshRuntime {
/** Needed to ensure some thread-safety during render data pre-processing. */
std::mutex render_mutex;
/**
* A cache of bounds shared between data-blocks with unchanged positions. When changing positions
* affect the bounds, the cache is "un-shared" with other geometries. See #SharedCache comments.
*/
SharedCache<Bounds<float3>> bounds_cache;
/** Lazily initialized SoA data from the #edit_mesh field in #Mesh. */
EditMeshData *edit_data = nullptr;
@ -139,8 +149,8 @@ struct MeshRuntime {
* #CustomData because they can be calculated on a `const` mesh, and adding custom data layers on
* a `const` mesh is not thread-safe.
*/
bool vert_normals_dirty = false;
bool poly_normals_dirty = false;
bool vert_normals_dirty = true;
bool poly_normals_dirty = true;
float (*vert_normals)[3] = nullptr;
float (*poly_normals)[3] = nullptr;
@ -151,8 +161,7 @@ struct MeshRuntime {
uint32_t *subsurf_face_dot_tags = nullptr;
MeshRuntime() = default;
/** \warning This does not free all data currently. See #BKE_mesh_runtime_free_data. */
~MeshRuntime() = default;
~MeshRuntime();
MEM_CXX_CLASS_ALLOC_FUNCS("MeshRuntime")
};

View File

@ -861,7 +861,6 @@ void nodeSocketDeclarationsUpdate(struct bNode *node);
/**
* Node Clipboard.
*/
void BKE_node_clipboard_init(const struct bNodeTree *ntree);
void BKE_node_clipboard_clear(void);
void BKE_node_clipboard_free(void);
/**
@ -872,7 +871,6 @@ void BKE_node_clipboard_add_node(struct bNode *node);
void BKE_node_clipboard_add_link(struct bNodeLink *link);
const struct ListBase *BKE_node_clipboard_get_nodes(void);
const struct ListBase *BKE_node_clipboard_get_links(void);
int BKE_node_clipboard_get_type(void);
/**
* Node Instance Hash.

View File

@ -6,6 +6,15 @@
* \ingroup bke
* \brief General operations for point clouds.
*/
#ifdef __cplusplus
# include <mutex>
# include "BLI_bounds_types.hh"
# include "BLI_math_vec_types.hh"
# include "BLI_shared_cache.hh"
#endif
#ifdef __cplusplus
extern "C" {
#endif
@ -22,6 +31,23 @@ struct Scene;
extern const char *POINTCLOUD_ATTR_POSITION;
extern const char *POINTCLOUD_ATTR_RADIUS;
#ifdef __cplusplus
namespace blender::bke {
struct PointCloudRuntime {
/**
* A cache of bounds shared between data-blocks with unchanged positions and radii.
* When data changes affect the bounds, the cache is "un-shared" with other geometries.
* See #SharedCache comments.
*/
mutable SharedCache<Bounds<float3>> bounds_cache;
MEM_CXX_CLASS_ALLOC_FUNCS("PointCloudRuntime");
};
} // namespace blender::bke
#endif
void *BKE_pointcloud_add(struct Main *bmain, const char *name);
void *BKE_pointcloud_add_default(struct Main *bmain, const char *name);
struct PointCloud *BKE_pointcloud_new_nomain(int totpoint);
@ -30,7 +56,6 @@ void BKE_pointcloud_nomain_to_pointcloud(struct PointCloud *pointcloud_src,
bool take_ownership);
struct BoundBox *BKE_pointcloud_boundbox_get(struct Object *ob);
bool BKE_pointcloud_minmax(const struct PointCloud *pointcloud, float r_min[3], float r_max[3]);
bool BKE_pointcloud_attribute_required(const struct PointCloud *pointcloud, const char *name);

View File

@ -63,7 +63,7 @@ typedef struct ShrinkwrapBoundaryData {
/**
* Free boundary data for target project.
*/
void BKE_shrinkwrap_discard_boundary_data(struct Mesh *mesh);
void BKE_shrinkwrap_boundary_data_free(ShrinkwrapBoundaryData *data);
void BKE_shrinkwrap_compute_boundary_data(struct Mesh *mesh);
/* Information about a mesh and BVH tree. */

View File

@ -49,6 +49,9 @@ typedef struct SubsurfRuntimeData {
int stats_totloop;
} SubsurfRuntimeData;
SubdivSettings BKE_subsurf_modifier_settings_init(const struct SubsurfModifierData *smd,
bool use_render_params);
bool BKE_subsurf_modifier_runtime_init(struct SubsurfModifierData *smd, bool use_render_params);
bool BKE_subsurf_modifier_use_custom_loop_normals(const struct SubsurfModifierData *smd,

View File

@ -665,7 +665,6 @@ static void mesh_calc_modifiers(struct Depsgraph *depsgraph,
* constructive modifier is executed, or a deform modifier needs normals
* or certain data layers. */
Mesh *mesh_input = (Mesh *)ob->data;
BKE_mesh_assert_normals_dirty_or_calculated(mesh_input);
Mesh *mesh_final = nullptr;
Mesh *mesh_deform = nullptr;
/* This geometry set contains the non-mesh data that might be generated by modifiers. */
@ -1783,8 +1782,6 @@ Mesh *mesh_get_eval_final(struct Depsgraph *depsgraph,
mesh_eval = BKE_object_get_evaluated_mesh(ob);
}
BKE_mesh_assert_normals_dirty_or_calculated(mesh_eval);
return mesh_eval;
}

View File

@ -52,6 +52,8 @@
#include "BLI_blenlib.h"
#include "BLI_utildefines.h"
#include "DEG_depsgraph.h"
#include "BKE_idtype.h"
#include "BKE_image.h"
#include "BKE_lib_id.h"
@ -84,6 +86,8 @@ void BKE_bpath_foreach_path_id(BPathForeachPathData *bpath_data, ID *id)
ID_BLEND_PATH(bpath_data->bmain, id) :
NULL;
bpath_data->absolute_base_path = absbase;
bpath_data->owner_id = id;
bpath_data->is_path_modified = false;
if ((flag & BKE_BPATH_FOREACH_PATH_SKIP_LINKED) && ID_IS_LINKED(id)) {
return;
@ -107,6 +111,10 @@ void BKE_bpath_foreach_path_id(BPathForeachPathData *bpath_data, ID *id)
}
id_type->foreach_path(id, bpath_data);
if (bpath_data->is_path_modified) {
DEG_id_tag_update(id, ID_RECALC_SOURCE | ID_RECALC_COPY_ON_WRITE);
}
}
void BKE_bpath_foreach_path_main(BPathForeachPathData *bpath_data)
@ -140,6 +148,7 @@ bool BKE_bpath_foreach_path_fixed_process(BPathForeachPathData *bpath_data, char
if (bpath_data->callback_function(bpath_data, path_dst, path_src)) {
BLI_strncpy(path, path_dst, FILE_MAX);
bpath_data->is_path_modified = true;
return true;
}
@ -166,6 +175,7 @@ bool BKE_bpath_foreach_path_dirfile_fixed_process(BPathForeachPathData *bpath_da
if (bpath_data->callback_function(bpath_data, path_dst, (const char *)path_src)) {
BLI_split_dirfile(path_dst, path_dir, path_file, FILE_MAXDIR, FILE_MAXFILE);
bpath_data->is_path_modified = true;
return true;
}
@ -192,6 +202,7 @@ bool BKE_bpath_foreach_path_allocated_process(BPathForeachPathData *bpath_data,
if (bpath_data->callback_function(bpath_data, path_dst, path_src)) {
MEM_freeN(*path);
(*path) = BLI_strdup(path_dst);
bpath_data->is_path_modified = true;
return true;
}

View File

@ -12,8 +12,12 @@ namespace blender::bke::curves::catmull_rom {
int calculate_evaluated_num(const int points_num, const bool cyclic, const int resolution)
{
const int eval_num = resolution * segments_num(points_num, cyclic);
if (cyclic) {
/* Make sure there is a single evaluated point for the single-point curve case. */
return std::max(eval_num, 1);
}
/* If the curve isn't cyclic, one last point is added to the final point. */
return cyclic ? eval_num : eval_num + 1;
return eval_num + 1;
}
/* Adapted from Cycles #catmull_rom_basis_eval function. */

View File

@ -14,7 +14,6 @@
#include "DNA_material_types.h"
#include "DNA_object_types.h"
#include "BLI_bounds.hh"
#include "BLI_index_range.hh"
#include "BLI_listbase.h"
#include "BLI_math_base.h"
@ -94,6 +93,7 @@ static void curves_copy_data(Main * /*bmain*/, ID *id_dst, const ID *id_src, con
dst.runtime = MEM_new<bke::CurvesGeometryRuntime>(__func__);
dst.runtime->type_counts = src.runtime->type_counts;
dst.runtime->bounds_cache = src.runtime->bounds_cache;
curves_dst->batch_cache = nullptr;
}

View File

@ -95,6 +95,7 @@ static void copy_curves_geometry(CurvesGeometry &dst, const CurvesGeometry &src)
/* Though type counts are a cache, they must be copied because they are calculated eagerly. */
dst.runtime->type_counts = src.runtime->type_counts;
dst.runtime->bounds_cache = src.runtime->bounds_cache;
}
CurvesGeometry::CurvesGeometry(const CurvesGeometry &other)
@ -918,20 +919,22 @@ void CurvesGeometry::tag_positions_changed()
this->runtime->tangent_cache_mutex.tag_dirty();
this->runtime->normal_cache_mutex.tag_dirty();
this->runtime->length_cache_mutex.tag_dirty();
this->runtime->bounds_cache.tag_dirty();
}
void CurvesGeometry::tag_topology_changed()
{
this->runtime->position_cache_mutex.tag_dirty();
this->runtime->tangent_cache_mutex.tag_dirty();
this->runtime->normal_cache_mutex.tag_dirty();
this->tag_positions_changed();
this->runtime->offsets_cache_mutex.tag_dirty();
this->runtime->nurbs_basis_cache_mutex.tag_dirty();
this->runtime->length_cache_mutex.tag_dirty();
}
void CurvesGeometry::tag_normals_changed()
{
this->runtime->normal_cache_mutex.tag_dirty();
}
void CurvesGeometry::tag_radii_changed()
{
this->runtime->bounds_cache.tag_dirty();
}
static void translate_positions(MutableSpan<float3> positions, const float3 &translation)
{
@ -1006,25 +1009,28 @@ void CurvesGeometry::transform(const float4x4 &matrix)
this->tag_positions_changed();
}
static std::optional<bounds::MinMaxResult<float3>> curves_bounds(const CurvesGeometry &curves)
{
const Span<float3> positions = curves.positions();
const VArray<float> radii = curves.attributes().lookup_or_default<float>(
ATTR_RADIUS, ATTR_DOMAIN_POINT, 0.0f);
if (!(radii.is_single() && radii.get_internal_single() == 0.0f)) {
return bounds::min_max_with_radii(positions, radii.get_internal_span());
}
return bounds::min_max(positions);
}
bool CurvesGeometry::bounds_min_max(float3 &min, float3 &max) const
{
const std::optional<bounds::MinMaxResult<float3>> bounds = curves_bounds(*this);
if (!bounds) {
if (this->points_num() == 0) {
return false;
}
min = math::min(bounds->min, min);
max = math::max(bounds->max, max);
this->runtime->bounds_cache.ensure([&](Bounds<float3> &r_bounds) {
const Span<float3> positions = this->evaluated_positions();
if (this->attributes().contains("radius")) {
const VArraySpan<float> radii = this->attributes().lookup<float>("radius");
Array<float> evaluated_radii(this->evaluated_points_num());
this->interpolate_to_evaluated(radii, evaluated_radii.as_mutable_span());
r_bounds = *bounds::min_max_with_radii(positions, evaluated_radii.as_span());
}
else {
r_bounds = *bounds::min_max(positions);
}
});
const Bounds<float3> &bounds = this->runtime->bounds_cache.data();
min = math::min(bounds.min, min);
max = math::max(bounds.max, max);
return true;
}

View File

@ -122,7 +122,7 @@ bool BKE_editmesh_cache_calc_minmax(struct BMEditMesh *em,
if (bm->totvert) {
if (emd->vertexCos) {
Span<float3> vert_coords(reinterpret_cast<const float3 *>(emd->vertexCos), bm->totvert);
std::optional<bounds::MinMaxResult<float3>> bounds = bounds::min_max(vert_coords);
std::optional<Bounds<float3>> bounds = bounds::min_max(vert_coords);
BLI_assert(bounds.has_value());
copy_v3_v3(min, math::min(bounds->min, float3(min)));
copy_v3_v3(max, math::max(bounds->max, float3(max)));

View File

@ -315,6 +315,12 @@ static void tag_component_positions_changed(void *owner)
curves.tag_positions_changed();
}
static void tag_component_radii_changed(void *owner)
{
blender::bke::CurvesGeometry &curves = *static_cast<blender::bke::CurvesGeometry *>(owner);
curves.tag_radii_changed();
}
static void tag_component_normals_changed(void *owner)
{
blender::bke::CurvesGeometry &curves = *static_cast<blender::bke::CurvesGeometry *>(owner);
@ -384,7 +390,7 @@ static ComponentAttributeProviders create_attribute_providers_for_curve()
point_access,
make_array_read_attribute<float>,
make_array_write_attribute<float>,
nullptr);
tag_component_radii_changed);
static BuiltinCustomDataLayerProvider id("id",
ATTR_DOMAIN_POINT,

View File

@ -105,6 +105,18 @@ void PointCloudComponent::ensure_owns_direct_data()
namespace blender::bke {
static void tag_component_positions_changed(void *owner)
{
PointCloud &points = *static_cast<PointCloud *>(owner);
points.tag_positions_changed();
}
static void tag_component_radius_changed(void *owner)
{
PointCloud &points = *static_cast<PointCloud *>(owner);
points.tag_radii_changed();
}
/**
* In this function all the attribute providers for a point cloud component are created. Most data
* in this function is statically allocated, because it does not change over time.
@ -135,7 +147,7 @@ static ComponentAttributeProviders create_attribute_providers_for_point_cloud()
point_access,
make_array_read_attribute<float3>,
make_array_write_attribute<float3>,
nullptr);
tag_component_positions_changed);
static BuiltinCustomDataLayerProvider radius("radius",
ATTR_DOMAIN_POINT,
CD_PROP_FLOAT,
@ -146,7 +158,7 @@ static ComponentAttributeProviders create_attribute_providers_for_point_cloud()
point_access,
make_array_read_attribute<float>,
make_array_write_attribute<float>,
nullptr);
tag_component_radius_changed);
static BuiltinCustomDataLayerProvider id("id",
ATTR_DOMAIN_POINT,
CD_PROP_INT32,

View File

@ -217,7 +217,7 @@ bool GeometrySet::compute_boundbox_without_instances(float3 *r_min, float3 *r_ma
using namespace blender;
bool have_minmax = false;
if (const PointCloud *pointcloud = this->get_pointcloud_for_read()) {
have_minmax |= BKE_pointcloud_minmax(pointcloud, *r_min, *r_max);
have_minmax |= pointcloud->bounds_min_max(*r_min, *r_max);
}
if (const Mesh *mesh = this->get_mesh_for_read()) {
have_minmax |= BKE_mesh_wrapper_minmax(mesh, *r_min, *r_max);
@ -227,14 +227,7 @@ bool GeometrySet::compute_boundbox_without_instances(float3 *r_min, float3 *r_ma
}
if (const Curves *curves_id = this->get_curves_for_read()) {
const bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id->geometry);
/* Using the evaluated positions is somewhat arbitrary, but it is probably expected. */
std::optional<bounds::MinMaxResult<float3>> min_max = bounds::min_max(
curves.evaluated_positions());
if (min_max) {
have_minmax = true;
*r_min = math::min(*r_min, min_max->min);
*r_max = math::max(*r_max, min_max->max);
}
have_minmax |= curves.bounds_min_max(*r_min, *r_max);
}
return have_minmax;
}

View File

@ -684,6 +684,15 @@ static void lib_query_unused_ids_tag_recurse(Main *bmain,
return;
}
if (ELEM(GS(id->name), ID_IM)) {
/* Images which have a 'viewer' source (e.g. render results) should not be considered as
* orphaned/unused data. */
Image *image = (Image *)id;
if (image->source == IMA_SRC_VIEWER) {
return;
}
}
/* An ID user is 'valid' (i.e. may affect the 'used'/'not used' status of the ID it uses) if it
* does not match `ignored_usages`, and does match `required_usages`. */
const int ignored_usages = (IDWALK_CB_LOOPBACK | IDWALK_CB_EMBEDDED);
@ -696,11 +705,10 @@ static void lib_query_unused_ids_tag_recurse(Main *bmain,
bool has_valid_from_users = false;
/* Preemptively consider this ID as unused. That way if there is a loop of dependency leading
* back to it, it won't create a fake 'valid user' detection.
* NOTE: This can only be done for a subset of IDs, some types are never 'indirectly unused',
* same for IDs with a fake user. */
if ((id->flag & LIB_FAKEUSER) == 0 && !ELEM(GS(id->name), ID_SCE, ID_WM, ID_SCR, ID_WS, ID_LI)) {
id->tag |= tag;
}
* NOTE: there are some cases (like when fake user is set, or some ID types) which are never
* 'indirectly unused'. However, these have already been checked and early-returned above, so any
* ID reaching this point of the function can be tagged. */
id->tag |= tag;
for (MainIDRelationsEntryItem *id_from_item = id_relations->from_ids; id_from_item != NULL;
id_from_item = id_from_item->next) {
if ((id_from_item->usage_flag & ignored_usages) != 0 ||
@ -727,8 +735,7 @@ static void lib_query_unused_ids_tag_recurse(Main *bmain,
id->tag &= ~tag;
}
else {
/* This ID has no 'valid' users, tag it as unused. */
id->tag |= tag;
/* This ID has no 'valid' users, its 'unused' tag preemptively set above can be kept. */
if (r_num_tagged != NULL) {
r_num_tagged[INDEX_ID_NULL]++;
r_num_tagged[BKE_idtype_idcode_to_index(GS(id->name))]++;

View File

@ -1499,7 +1499,10 @@ Mesh *BKE_mball_polygonize(Depsgraph *depsgraph, Scene *scene, Object *ob)
for (int i = 0; i < mesh->totvert; i++) {
normalize_v3(process.no[i]);
}
mesh->runtime->vert_normals = process.no;
memcpy(BKE_mesh_vertex_normals_for_write(mesh),
process.no,
sizeof(float[3]) * size_t(mesh->totvert));
MEM_freeN(process.no);
BKE_mesh_vertex_normals_clear_dirty(mesh);
mesh->totloop = loop_offset;

View File

@ -91,10 +91,6 @@ static void mesh_init_data(ID *id)
mesh->runtime = new blender::bke::MeshRuntime();
/* A newly created mesh does not have normals, so tag them dirty. This will be cleared
* by #BKE_mesh_vertex_normals_clear_dirty or #BKE_mesh_poly_normals_ensure. */
BKE_mesh_normals_tag_dirty(mesh);
mesh->face_sets_color_seed = BLI_hash_int(PIL_check_seconds_timer_i() & UINT_MAX);
}
@ -128,6 +124,11 @@ static void mesh_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const int
* highly unlikely we want to create a duplicate and not use it for drawing. */
mesh_dst->runtime->is_original_bmesh = false;
/* Share the bounding box cache between the source and destination mesh for improved performance
* when the source is persistent and edits to the destination don't change the bounds. It will be
* "un-shared" as necessary when the positions are changed. */
mesh_dst->runtime->bounds_cache = mesh_src->runtime->bounds_cache;
/* Only do tessface if we have no polys. */
const bool do_tessface = ((mesh_src->totface != 0) && (mesh_src->totpoly == 0));
@ -158,21 +159,12 @@ static void mesh_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const int
mesh_dst->mselect = (MSelect *)MEM_dupallocN(mesh_dst->mselect);
/* Set normal layers dirty. They should be dirty by default on new meshes anyway, but being
* explicit about it is safer. Alternatively normal layers could be copied if they aren't dirty,
* avoiding recomputation in some cases. However, a copied mesh is often changed anyway, so that
* idea is not clearly better. With proper reference counting, all custom data layers could be
* copied as the cost would be much lower. */
BKE_mesh_normals_tag_dirty(mesh_dst);
/* TODO: Do we want to add flag to prevent this? */
if (mesh_src->key && (flag & LIB_ID_COPY_SHAPEKEY)) {
BKE_id_copy_ex(bmain, &mesh_src->key->id, (ID **)&mesh_dst->key, flag);
/* XXX This is not nice, we need to make BKE_id_copy_ex fully re-entrant... */
mesh_dst->key->from = &mesh_dst->id;
}
BKE_mesh_assert_normals_dirty_or_calculated(mesh_dst);
}
void BKE_mesh_free_editmesh(struct Mesh *mesh)
@ -196,7 +188,6 @@ static void mesh_free_data(ID *id)
BKE_mesh_free_editmesh(mesh);
BKE_mesh_runtime_free_data(mesh);
mesh_clear_geometry(mesh);
MEM_SAFE_FREE(mesh->mat);
@ -359,10 +350,6 @@ static void mesh_blend_read_data(BlendDataReader *reader, ID *id)
BLI_endian_switch_uint32_array(tf->col, 4);
}
}
/* We don't expect to load normals from files, since they are derived data. */
BKE_mesh_normals_tag_dirty(mesh);
BKE_mesh_assert_normals_dirty_or_calculated(mesh);
}
static void mesh_blend_read_lib(BlendLibReader *reader, ID *id)
@ -995,8 +982,6 @@ void BKE_mesh_copy_parameters_for_eval(Mesh *me_dst, const Mesh *me_src)
BKE_mesh_copy_parameters(me_dst, me_src);
BKE_mesh_assert_normals_dirty_or_calculated(me_dst);
/* Copy vertex group names. */
BLI_assert(BLI_listbase_is_empty(&me_dst->vertex_group_names));
BKE_defgroup_copy_list(&me_dst->vertex_group_names, &me_src->vertex_group_names);
@ -1523,29 +1508,27 @@ bool BKE_mesh_minmax(const Mesh *me, float r_min[3], float r_max[3])
return false;
}
struct Result {
float3 min;
float3 max;
};
const Span<MVert> verts = me->verts();
me->runtime->bounds_cache.ensure([me](Bounds<float3> &r_bounds) {
const Span<MVert> verts = me->verts();
r_bounds = threading::parallel_reduce(
verts.index_range(),
1024,
Bounds<float3>{float3(FLT_MAX), float3(-FLT_MAX)},
[verts](IndexRange range, const Bounds<float3> &init) {
Bounds<float3> result = init;
for (const int i : range) {
math::min_max(float3(verts[i].co), result.min, result.max);
}
return result;
},
[](const Bounds<float3> &a, const Bounds<float3> &b) {
return Bounds<float3>{math::min(a.min, b.min), math::max(a.max, b.max)};
});
});
const Result minmax = threading::parallel_reduce(
verts.index_range(),
1024,
Result{float3(FLT_MAX), float3(-FLT_MAX)},
[verts](IndexRange range, const Result &init) {
Result result = init;
for (const int i : range) {
math::min_max(float3(verts[i].co), result.min, result.max);
}
return result;
},
[](const Result &a, const Result &b) {
return Result{math::min(a.min, b.min), math::max(a.max, b.max)};
});
copy_v3_v3(r_min, math::min(minmax.min, float3(r_min)));
copy_v3_v3(r_max, math::max(minmax.max, float3(r_max)));
const Bounds<float3> &bounds = me->runtime->bounds_cache.data();
copy_v3_v3(r_min, math::min(bounds.min, float3(r_min)));
copy_v3_v3(r_max, math::max(bounds.max, float3(r_max)));
return true;
}
@ -1832,8 +1815,6 @@ void BKE_mesh_calc_normals_split_ex(Mesh *mesh,
nullptr,
r_lnors_spacearr,
clnors);
BKE_mesh_assert_normals_dirty_or_calculated(mesh);
}
void BKE_mesh_calc_normals_split(Mesh *mesh)
@ -2100,7 +2081,6 @@ void BKE_mesh_split_faces(Mesh *mesh, bool free_loop_normals)
/* Also frees new_verts/edges temp data, since we used its memarena to allocate them. */
BKE_lnor_spacearr_free(&lnors_spacearr);
BKE_mesh_assert_normals_dirty_or_calculated(mesh);
#ifdef VALIDATE_MESH
BKE_mesh_validate(mesh, true, true);
#endif

View File

@ -128,13 +128,13 @@ float (*BKE_mesh_poly_normals_for_write(Mesh *mesh))[3]
void BKE_mesh_vertex_normals_clear_dirty(Mesh *mesh)
{
mesh->runtime->vert_normals_dirty = false;
BKE_mesh_assert_normals_dirty_or_calculated(mesh);
BLI_assert(mesh->runtime->vert_normals || mesh->totvert == 0);
}
void BKE_mesh_poly_normals_clear_dirty(Mesh *mesh)
{
mesh->runtime->poly_normals_dirty = false;
BKE_mesh_assert_normals_dirty_or_calculated(mesh);
BLI_assert(mesh->runtime->poly_normals || mesh->totpoly == 0);
}
bool BKE_mesh_vertex_normals_are_dirty(const Mesh *mesh)
@ -147,25 +147,6 @@ bool BKE_mesh_poly_normals_are_dirty(const Mesh *mesh)
return mesh->runtime->poly_normals_dirty;
}
void BKE_mesh_clear_derived_normals(Mesh *mesh)
{
MEM_SAFE_FREE(mesh->runtime->vert_normals);
MEM_SAFE_FREE(mesh->runtime->poly_normals);
mesh->runtime->vert_normals_dirty = true;
mesh->runtime->poly_normals_dirty = true;
}
void BKE_mesh_assert_normals_dirty_or_calculated(const Mesh *mesh)
{
if (!mesh->runtime->vert_normals_dirty) {
BLI_assert(mesh->runtime->vert_normals || mesh->totvert == 0);
}
if (!mesh->runtime->poly_normals_dirty) {
BLI_assert(mesh->runtime->poly_normals || mesh->totpoly == 0);
}
}
/** \} */
/* -------------------------------------------------------------------- */
@ -463,60 +444,6 @@ void BKE_mesh_calc_normals(Mesh *mesh)
BKE_mesh_vertex_normals_ensure(mesh);
}
void BKE_mesh_calc_normals_looptri(const MVert *mverts,
int numVerts,
const MLoop *mloop,
const MLoopTri *looptri,
int looptri_num,
float (*r_tri_nors)[3])
{
float(*tnorms)[3] = (float(*)[3])MEM_calloc_arrayN(size_t(numVerts), sizeof(*tnorms), "tnorms");
float(*fnors)[3] = (r_tri_nors) ? r_tri_nors :
(float(*)[3])MEM_calloc_arrayN(
size_t(looptri_num), sizeof(*fnors), "meshnormals");
if (!tnorms || !fnors) {
goto cleanup;
}
for (int i = 0; i < looptri_num; i++) {
const MLoopTri *lt = &looptri[i];
float *f_no = fnors[i];
const uint vtri[3] = {
mloop[lt->tri[0]].v,
mloop[lt->tri[1]].v,
mloop[lt->tri[2]].v,
};
normal_tri_v3(f_no, mverts[vtri[0]].co, mverts[vtri[1]].co, mverts[vtri[2]].co);
accumulate_vertex_normals_tri_v3(tnorms[vtri[0]],
tnorms[vtri[1]],
tnorms[vtri[2]],
f_no,
mverts[vtri[0]].co,
mverts[vtri[1]].co,
mverts[vtri[2]].co);
}
/* Following Mesh convention; we use vertex coordinate itself for normal in this case. */
for (int i = 0; i < numVerts; i++) {
const MVert *mv = &mverts[i];
float *no = tnorms[i];
if (UNLIKELY(normalize_v3(no) == 0.0f)) {
normalize_v3_v3(no, mv->co);
}
}
cleanup:
MEM_freeN(tnorms);
if (fnors != r_tri_nors) {
MEM_freeN(fnors);
}
}
void BKE_lnor_spacearr_init(MLoopNorSpaceArray *lnors_spacearr,
const int numLoops,
const char data_type)

View File

@ -31,24 +31,84 @@ using blender::Span;
/** \name Mesh Runtime Struct Utils
* \{ */
void BKE_mesh_runtime_free_data(Mesh *mesh)
namespace blender::bke {
static void edit_data_reset(EditMeshData &edit_data)
{
BKE_mesh_runtime_clear_cache(mesh);
MEM_SAFE_FREE(edit_data.polyCos);
MEM_SAFE_FREE(edit_data.polyNos);
MEM_SAFE_FREE(edit_data.vertexCos);
MEM_SAFE_FREE(edit_data.vertexNos);
}
void BKE_mesh_runtime_clear_cache(Mesh *mesh)
static void free_edit_data(MeshRuntime &mesh_runtime)
{
if (mesh->runtime->mesh_eval != nullptr) {
mesh->runtime->mesh_eval->edit_mesh = nullptr;
BKE_id_free(nullptr, mesh->runtime->mesh_eval);
mesh->runtime->mesh_eval = nullptr;
if (mesh_runtime.edit_data) {
edit_data_reset(*mesh_runtime.edit_data);
MEM_freeN(mesh_runtime.edit_data);
mesh_runtime.edit_data = nullptr;
}
BKE_mesh_runtime_clear_geometry(mesh);
BKE_mesh_batch_cache_free(mesh);
BKE_mesh_runtime_clear_edit_data(mesh);
BKE_mesh_clear_derived_normals(mesh);
}
static void free_mesh_eval(MeshRuntime &mesh_runtime)
{
if (mesh_runtime.mesh_eval != nullptr) {
mesh_runtime.mesh_eval->edit_mesh = nullptr;
BKE_id_free(nullptr, mesh_runtime.mesh_eval);
mesh_runtime.mesh_eval = nullptr;
}
}
static void free_subdiv_ccg(MeshRuntime &mesh_runtime)
{
/* TODO(sergey): Does this really belong here? */
if (mesh_runtime.subdiv_ccg != nullptr) {
BKE_subdiv_ccg_destroy(mesh_runtime.subdiv_ccg);
mesh_runtime.subdiv_ccg = nullptr;
}
}
static void free_bvh_cache(MeshRuntime &mesh_runtime)
{
if (mesh_runtime.bvh_cache) {
bvhcache_free(mesh_runtime.bvh_cache);
mesh_runtime.bvh_cache = nullptr;
}
}
static void free_normals(MeshRuntime &mesh_runtime)
{
MEM_SAFE_FREE(mesh_runtime.vert_normals);
MEM_SAFE_FREE(mesh_runtime.poly_normals);
mesh_runtime.vert_normals_dirty = true;
mesh_runtime.poly_normals_dirty = true;
}
static void free_batch_cache(MeshRuntime &mesh_runtime)
{
if (mesh_runtime.batch_cache) {
BKE_mesh_batch_cache_free(mesh_runtime.batch_cache);
mesh_runtime.batch_cache = nullptr;
}
}
MeshRuntime::~MeshRuntime()
{
free_mesh_eval(*this);
free_subdiv_ccg(*this);
free_bvh_cache(*this);
free_edit_data(*this);
free_batch_cache(*this);
free_normals(*this);
if (this->shrinkwrap_data) {
BKE_shrinkwrap_boundary_data_free(this->shrinkwrap_data);
}
MEM_SAFE_FREE(this->subsurf_face_dot_tags);
MEM_SAFE_FREE(this->looptris.array);
}
} // namespace blender::bke
blender::Span<MLoopTri> Mesh::looptris() const
{
const MLoopTri *looptris = BKE_mesh_runtime_looptri_ensure(this);
@ -90,7 +150,7 @@ static void mesh_ensure_looptri_data(Mesh *mesh)
}
}
void BKE_mesh_runtime_looptri_recalc(Mesh *mesh)
static void recalc_loopris(Mesh *mesh)
{
mesh_ensure_looptri_data(mesh);
BLI_assert(mesh->totpoly == 0 || mesh->runtime->looptris.array_wip != nullptr);
@ -142,8 +202,7 @@ const MLoopTri *BKE_mesh_runtime_looptri_ensure(const Mesh *mesh)
}
else {
/* Must isolate multithreaded tasks while holding a mutex lock. */
blender::threading::isolate_task(
[&]() { BKE_mesh_runtime_looptri_recalc(const_cast<Mesh *>(mesh)); });
blender::threading::isolate_task([&]() { recalc_loopris(const_cast<Mesh *>(mesh)); });
looptri = mesh->runtime->looptris.array;
}
@ -167,76 +226,54 @@ bool BKE_mesh_runtime_ensure_edit_data(struct Mesh *mesh)
if (mesh->runtime->edit_data != nullptr) {
return false;
}
mesh->runtime->edit_data = MEM_cnew<EditMeshData>(__func__);
return true;
}
bool BKE_mesh_runtime_reset_edit_data(Mesh *mesh)
void BKE_mesh_runtime_reset_edit_data(Mesh *mesh)
{
EditMeshData *edit_data = mesh->runtime->edit_data;
if (edit_data == nullptr) {
return false;
using namespace blender::bke;
if (EditMeshData *edit_data = mesh->runtime->edit_data) {
edit_data_reset(*edit_data);
}
MEM_SAFE_FREE(edit_data->polyCos);
MEM_SAFE_FREE(edit_data->polyNos);
MEM_SAFE_FREE(edit_data->vertexCos);
MEM_SAFE_FREE(edit_data->vertexNos);
return true;
}
bool BKE_mesh_runtime_clear_edit_data(Mesh *mesh)
void BKE_mesh_runtime_clear_cache(Mesh *mesh)
{
if (mesh->runtime->edit_data == nullptr) {
return false;
}
BKE_mesh_runtime_reset_edit_data(mesh);
MEM_freeN(mesh->runtime->edit_data);
mesh->runtime->edit_data = nullptr;
return true;
using namespace blender::bke;
free_mesh_eval(*mesh->runtime);
free_batch_cache(*mesh->runtime);
free_edit_data(*mesh->runtime);
BKE_mesh_runtime_clear_geometry(mesh);
}
void BKE_mesh_runtime_clear_geometry(Mesh *mesh)
{
BKE_mesh_tag_coords_changed(mesh);
/* TODO(sergey): Does this really belong here? */
if (mesh->runtime->subdiv_ccg != nullptr) {
BKE_subdiv_ccg_destroy(mesh->runtime->subdiv_ccg);
mesh->runtime->subdiv_ccg = nullptr;
/* Tagging shared caches dirty will free the allocated data if there is only one user. */
free_bvh_cache(*mesh->runtime);
free_normals(*mesh->runtime);
free_subdiv_ccg(*mesh->runtime);
mesh->runtime->bounds_cache.tag_dirty();
if (mesh->runtime->shrinkwrap_data) {
BKE_shrinkwrap_boundary_data_free(mesh->runtime->shrinkwrap_data);
}
BKE_shrinkwrap_discard_boundary_data(mesh);
MEM_SAFE_FREE(mesh->runtime->subsurf_face_dot_tags);
MEM_SAFE_FREE(mesh->runtime->looptris.array);
}
void BKE_mesh_tag_coords_changed(Mesh *mesh)
{
BKE_mesh_normals_tag_dirty(mesh);
free_bvh_cache(*mesh->runtime);
MEM_SAFE_FREE(mesh->runtime->looptris.array);
if (mesh->runtime->bvh_cache) {
bvhcache_free(mesh->runtime->bvh_cache);
mesh->runtime->bvh_cache = nullptr;
}
mesh->runtime->bounds_cache.tag_dirty();
}
void BKE_mesh_tag_coords_changed_uniformly(Mesh *mesh)
{
const bool vert_normals_were_dirty = BKE_mesh_vertex_normals_are_dirty(mesh);
const bool poly_normals_were_dirty = BKE_mesh_poly_normals_are_dirty(mesh);
BKE_mesh_tag_coords_changed(mesh);
/* The normals didn't change, since all verts moved by the same amount. */
if (!vert_normals_were_dirty) {
BKE_mesh_vertex_normals_clear_dirty(mesh);
}
if (!poly_normals_were_dirty) {
BKE_mesh_poly_normals_clear_dirty(mesh);
}
/* The normals and triangulation didn't change, since all verts moved by the same amount. */
free_bvh_cache(*mesh->runtime);
mesh->runtime->bounds_cache.tag_dirty();
}
bool BKE_mesh_is_deformed_only(const Mesh *mesh)
@ -257,7 +294,7 @@ eMeshWrapperType BKE_mesh_wrapper_type(const struct Mesh *mesh)
/* Draw Engine */
void (*BKE_mesh_batch_cache_dirty_tag_cb)(Mesh *me, eMeshBatchDirtyMode mode) = nullptr;
void (*BKE_mesh_batch_cache_free_cb)(Mesh *me) = nullptr;
void (*BKE_mesh_batch_cache_free_cb)(void *batch_cache) = nullptr;
void BKE_mesh_batch_cache_dirty_tag(Mesh *me, eMeshBatchDirtyMode mode)
{
@ -265,11 +302,9 @@ void BKE_mesh_batch_cache_dirty_tag(Mesh *me, eMeshBatchDirtyMode mode)
BKE_mesh_batch_cache_dirty_tag_cb(me, mode);
}
}
void BKE_mesh_batch_cache_free(Mesh *me)
void BKE_mesh_batch_cache_free(void *batch_cache)
{
if (me->runtime->batch_cache) {
BKE_mesh_batch_cache_free_cb(me);
}
BKE_mesh_batch_cache_free_cb(batch_cache);
}
/** \} */

View File

@ -298,7 +298,6 @@ bool BKE_mesh_validate_arrays(Mesh *mesh,
}
const float(*vert_normals)[3] = nullptr;
BKE_mesh_assert_normals_dirty_or_calculated(mesh);
if (!BKE_mesh_vertex_normals_are_dirty(mesh)) {
vert_normals = BKE_mesh_vertex_normals_ensure(mesh);
}
@ -1104,8 +1103,6 @@ bool BKE_mesh_is_valid(Mesh *me)
bool is_valid = true;
bool changed = true;
BKE_mesh_assert_normals_dirty_or_calculated(me);
is_valid &= BKE_mesh_validate_all_customdata(
&me->vdata,
me->totvert,
@ -1141,6 +1138,13 @@ bool BKE_mesh_is_valid(Mesh *me)
do_fixes,
&changed);
if (!me->runtime->vert_normals_dirty) {
BLI_assert(me->runtime->vert_normals || me->totvert == 0);
}
if (!me->runtime->poly_normals_dirty) {
BLI_assert(me->runtime->poly_normals || me->totpoly == 0);
}
BLI_assert(changed == false);
return is_valid;

View File

@ -3779,16 +3779,10 @@ struct bNodeClipboard {
#endif
ListBase links;
int type;
};
static bNodeClipboard node_clipboard = {{nullptr}};
void BKE_node_clipboard_init(const struct bNodeTree *ntree)
{
node_clipboard.type = ntree->type;
}
void BKE_node_clipboard_clear()
{
LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &node_clipboard.links) {
@ -3894,11 +3888,6 @@ const ListBase *BKE_node_clipboard_get_links()
return &node_clipboard.links;
}
int BKE_node_clipboard_get_type()
{
return node_clipboard.type;
}
void BKE_node_clipboard_free()
{
BKE_node_clipboard_validate();

View File

@ -3140,9 +3140,6 @@ void BKE_pbvh_vert_coords_apply(PBVH *pbvh, const float (*vertCos)[3], const int
}
}
/* coordinates are new -- normals should also be updated */
BKE_mesh_calc_normals_looptri(
pbvh->verts, pbvh->totvert, pbvh->mloop, pbvh->looptri, pbvh->totprim, NULL);
for (int a = 0; a < pbvh->totnode; a++) {
BKE_pbvh_node_mark_update(&pbvh->nodes[a]);

View File

@ -14,7 +14,7 @@
#include "BLI_bounds.hh"
#include "BLI_index_range.hh"
#include "BLI_listbase.h"
#include "BLI_math_vec_types.hh"
#include "BLI_math_vector.hh"
#include "BLI_rand.h"
#include "BLI_span.hh"
#include "BLI_string.h"
@ -68,6 +68,8 @@ static void pointcloud_init_data(ID *id)
nullptr,
pointcloud->totpoint,
POINTCLOUD_ATTR_POSITION);
pointcloud->runtime = new blender::bke::PointCloudRuntime();
}
static void pointcloud_copy_data(Main * /*bmain*/, ID *id_dst, const ID *id_src, const int flag)
@ -83,6 +85,9 @@ static void pointcloud_copy_data(Main * /*bmain*/, ID *id_dst, const ID *id_src,
alloc_type,
pointcloud_dst->totpoint);
pointcloud_dst->runtime = new blender::bke::PointCloudRuntime();
pointcloud_dst->runtime->bounds_cache = pointcloud_src->runtime->bounds_cache;
pointcloud_dst->batch_cache = nullptr;
}
@ -93,6 +98,7 @@ static void pointcloud_free_data(ID *id)
BKE_pointcloud_batch_cache_free(pointcloud);
CustomData_free(&pointcloud->pdata, pointcloud->totpoint);
MEM_SAFE_FREE(pointcloud->mat);
delete pointcloud->runtime;
}
static void pointcloud_foreach_id(ID *id, LibraryForeachIDData *data)
@ -139,6 +145,8 @@ static void pointcloud_blend_read_data(BlendDataReader *reader, ID *id)
/* Materials */
BLO_read_pointer_array(reader, (void **)&pointcloud->mat);
pointcloud->runtime = new blender::bke::PointCloudRuntime();
}
static void pointcloud_blend_read_lib(BlendLibReader *reader, ID *id)
@ -277,33 +285,27 @@ void BKE_pointcloud_nomain_to_pointcloud(PointCloud *pointcloud_src,
}
}
static std::optional<blender::bounds::MinMaxResult<float3>> point_cloud_bounds(
const PointCloud &pointcloud)
{
blender::bke::AttributeAccessor attributes = pointcloud.attributes();
blender::VArraySpan<float3> positions = attributes.lookup_or_default<float3>(
POINTCLOUD_ATTR_POSITION, ATTR_DOMAIN_POINT, float3(0));
blender::VArray<float> radii = attributes.lookup_or_default<float>(
POINTCLOUD_ATTR_RADIUS, ATTR_DOMAIN_POINT, 0.0f);
if (!(radii.is_single() && radii.get_internal_single() == 0.0f)) {
return blender::bounds::min_max_with_radii(positions, radii.get_internal_span());
}
return blender::bounds::min_max(positions);
}
bool BKE_pointcloud_minmax(const PointCloud *pointcloud, float r_min[3], float r_max[3])
bool PointCloud::bounds_min_max(blender::float3 &min, blender::float3 &max) const
{
using namespace blender;
const std::optional<bounds::MinMaxResult<float3>> min_max = point_cloud_bounds(*pointcloud);
if (!min_max) {
using namespace blender::bke;
if (this->totpoint == 0) {
return false;
}
copy_v3_v3(r_min, math::min(min_max->min, float3(r_min)));
copy_v3_v3(r_max, math::max(min_max->max, float3(r_max)));
this->runtime->bounds_cache.ensure([&](Bounds<float3> &r_bounds) {
const AttributeAccessor attributes = this->attributes();
const VArraySpan<float3> positions = attributes.lookup<float3>(POINTCLOUD_ATTR_POSITION);
if (attributes.contains(POINTCLOUD_ATTR_RADIUS)) {
const VArraySpan<float> radii = attributes.lookup<float>(POINTCLOUD_ATTR_RADIUS);
r_bounds = *bounds::min_max_with_radii(positions, radii);
}
else {
r_bounds = *bounds::min_max(positions);
}
});
const Bounds<float3> &bounds = this->runtime->bounds_cache.data();
min = math::min(bounds.min, min);
max = math::max(bounds.max, max);
return true;
}
@ -326,7 +328,7 @@ BoundBox *BKE_pointcloud_boundbox_get(Object *ob)
}
else {
const PointCloud *pointcloud = static_cast<PointCloud *>(ob->data);
BKE_pointcloud_minmax(pointcloud, min, max);
pointcloud->bounds_min_max(min, max);
}
BKE_boundbox_init_from_minmax(ob->runtime.bb, min, max);
@ -427,6 +429,16 @@ void BKE_pointcloud_data_update(struct Depsgraph *depsgraph, struct Scene *scene
object->runtime.geometry_set_eval = new GeometrySet(std::move(geometry_set));
}
void PointCloud::tag_positions_changed()
{
this->runtime->bounds_cache.tag_dirty();
}
void PointCloud::tag_radii_changed()
{
this->runtime->bounds_cache.tag_dirty();
}
/* Draw Cache */
void (*BKE_pointcloud_batch_cache_dirty_tag_cb)(PointCloud *pointcloud, int mode) = nullptr;

View File

@ -152,20 +152,14 @@ void BKE_shrinkwrap_free_tree(ShrinkwrapTreeData *data)
free_bvhtree_from_mesh(&data->treeData);
}
void BKE_shrinkwrap_discard_boundary_data(Mesh *mesh)
void BKE_shrinkwrap_boundary_data_free(ShrinkwrapBoundaryData *data)
{
ShrinkwrapBoundaryData *data = mesh->runtime->shrinkwrap_data;
MEM_freeN((void *)data->edge_is_boundary);
MEM_freeN((void *)data->looptri_has_boundary);
MEM_freeN((void *)data->vert_boundary_id);
MEM_freeN((void *)data->boundary_verts);
if (data != nullptr) {
MEM_freeN((void *)data->edge_is_boundary);
MEM_freeN((void *)data->looptri_has_boundary);
MEM_freeN((void *)data->vert_boundary_id);
MEM_freeN((void *)data->boundary_verts);
MEM_freeN(data);
}
mesh->runtime->shrinkwrap_data = nullptr;
MEM_freeN(data);
}
/* Accumulate edge for average boundary edge direction. */
@ -326,8 +320,9 @@ static ShrinkwrapBoundaryData *shrinkwrap_build_boundary_data(Mesh *mesh)
void BKE_shrinkwrap_compute_boundary_data(Mesh *mesh)
{
BKE_shrinkwrap_discard_boundary_data(mesh);
if (mesh->runtime->shrinkwrap_data) {
BKE_shrinkwrap_boundary_data_free(mesh->runtime->shrinkwrap_data);
}
mesh->runtime->shrinkwrap_data = shrinkwrap_build_boundary_data(mesh);
}

View File

@ -1522,6 +1522,12 @@ void BKE_sound_jack_scene_update(Scene *scene, int mode, double time)
void BKE_sound_evaluate(Depsgraph *depsgraph, Main *bmain, bSound *sound)
{
DEG_debug_print_eval(depsgraph, __func__, sound->id.name, sound);
if (sound->id.recalc & ID_RECALC_SOURCE) {
/* Sequencer checks this flag to see if the strip sound is to be updated from the Audaspace
* side. */
sound->id.recalc |= ID_RECALC_AUDIO;
}
if (sound->id.recalc & ID_RECALC_AUDIO) {
BKE_sound_load(bmain, sound);
return;

View File

@ -20,11 +20,12 @@
#include "opensubdiv_capi.h"
bool BKE_subsurf_modifier_runtime_init(SubsurfModifierData *smd, const bool use_render_params)
SubdivSettings BKE_subsurf_modifier_settings_init(const SubsurfModifierData *smd,
const bool use_render_params)
{
const int requested_levels = (use_render_params) ? smd->renderLevels : smd->levels;
SubdivSettings settings;
SubdivSettings settings{};
settings.is_simple = (smd->subdivType == SUBSURF_TYPE_SIMPLE);
settings.is_adaptive = !(smd->flags & eSubsurfModifierFlag_UseRecursiveSubdivision);
settings.level = settings.is_simple ? 1 :
@ -35,6 +36,13 @@ bool BKE_subsurf_modifier_runtime_init(SubsurfModifierData *smd, const bool use_
settings.fvar_linear_interpolation = BKE_subdiv_fvar_interpolation_from_uv_smooth(
smd->uv_smooth);
return settings;
}
bool BKE_subsurf_modifier_runtime_init(SubsurfModifierData *smd, const bool use_render_params)
{
SubdivSettings settings = BKE_subsurf_modifier_settings_init(smd, use_render_params);
SubsurfRuntimeData *runtime_data = (SubsurfRuntimeData *)smd->modifier.runtime;
if (settings.level == 0) {
/* Modifier is effectively disabled, but still update settings if runtime data

View File

@ -10,38 +10,34 @@
#include <optional>
#include "BLI_bounds_types.hh"
#include "BLI_math_vector.hh"
#include "BLI_task.hh"
namespace blender::bounds {
template<typename T> struct MinMaxResult {
T min;
T max;
};
/**
* Find the smallest and largest values element-wise in the span.
*/
template<typename T> static std::optional<MinMaxResult<T>> min_max(Span<T> values)
template<typename T> static std::optional<Bounds<T>> min_max(Span<T> values)
{
if (values.is_empty()) {
return std::nullopt;
}
const MinMaxResult<T> init{values.first(), values.first()};
const Bounds<T> init{values.first(), values.first()};
return threading::parallel_reduce(
values.index_range(),
1024,
init,
[&](IndexRange range, const MinMaxResult<T> &init) {
MinMaxResult<T> result = init;
[&](IndexRange range, const Bounds<T> &init) {
Bounds<T> result = init;
for (const int i : range) {
math::min_max(values[i], result.min, result.max);
}
return result;
},
[](const MinMaxResult<T> &a, const MinMaxResult<T> &b) {
return MinMaxResult<T>{math::min(a.min, b.min), math::max(a.max, b.max)};
[](const Bounds<T> &a, const Bounds<T> &b) {
return Bounds<T>{math::min(a.min, b.min), math::max(a.max, b.max)};
});
}
@ -50,27 +46,27 @@ template<typename T> static std::optional<MinMaxResult<T>> min_max(Span<T> value
* first. The template type T is expected to have an addition operator implemented with RadiusT.
*/
template<typename T, typename RadiusT>
static std::optional<MinMaxResult<T>> min_max_with_radii(Span<T> values, Span<RadiusT> radii)
static std::optional<Bounds<T>> min_max_with_radii(Span<T> values, Span<RadiusT> radii)
{
BLI_assert(values.size() == radii.size());
if (values.is_empty()) {
return std::nullopt;
}
const MinMaxResult<T> init{values.first(), values.first()};
const Bounds<T> init{values.first(), values.first()};
return threading::parallel_reduce(
values.index_range(),
1024,
init,
[&](IndexRange range, const MinMaxResult<T> &init) {
MinMaxResult<T> result = init;
[&](IndexRange range, const Bounds<T> &init) {
Bounds<T> result = init;
for (const int i : range) {
result.min = math::min(values[i] - radii[i], result.min);
result.max = math::max(values[i] + radii[i], result.max);
}
return result;
},
[](const MinMaxResult<T> &a, const MinMaxResult<T> &b) {
return MinMaxResult<T>{math::min(a.min, b.min), math::max(a.max, b.max)};
[](const Bounds<T> &a, const Bounds<T> &b) {
return Bounds<T>{math::min(a.min, b.min), math::max(a.max, b.max)};
});
}

View File

@ -0,0 +1,16 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*/
namespace blender {
template<typename T> struct Bounds {
T min;
T max;
};
} // namespace blender

View File

@ -0,0 +1,69 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_cache_mutex.hh"
namespace blender {
/**
* A `SharedCache` is meant to share lazily computed data between equivalent objects. It allows
* saving unnecessary computation by making a calculated value accessible from any object that
* shares the cache. Unlike `CacheMutex`, the cached data is embedded inside of this object.
*
* When data is copied (copy-on-write before changing a mesh, for example), the cache is shared,
* allowing its calculation on either the source or original to make the result available on both
* objects. As soon as either object is changed in a way that invalidates the cache, the data is
* "un-shared", and they will no-longer influence each other.
*
* One important use case is a typical CoW update loop of a persistent geometry data-block in
* `Main`. Even if bounds are only calculated on the evaluated *copied* geometry, if nothing
* changes them, they only need to be calculated on the first evaluation, because the same
* evaluated bounds are also accessible from the original geometry.
*
* The cache is implemented with a shared pointer, so it is relatively cheap, but to avoid
* unnecessary overhead it should only be used for relatively expensive computations.
*/
template<typename T> class SharedCache {
struct CacheData {
CacheMutex mutex;
T data;
};
std::shared_ptr<CacheData> cache_;
public:
SharedCache()
{
/* The cache should be allocated to trigger sharing of the cached data as early as possible. */
cache_ = std::make_shared<CacheData>();
}
/** Tag the data for recomputation and stop sharing the cache with other objects. */
void tag_dirty()
{
if (cache_.unique()) {
cache_->mutex.tag_dirty();
}
else {
cache_ = std::make_shared<CacheData>();
}
}
/**
* If the cache is dirty, trigger its computation with the provided function which should set
* the proper data.
*/
void ensure(FunctionRef<void(T &data)> compute_cache)
{
cache_->mutex.ensure([&]() { compute_cache(this->cache_->data); });
}
/** Retrieve the cached data. */
const T &data()
{
BLI_assert(cache_->mutex.is_cached());
return cache_->data;
}
};
} // namespace blender

View File

@ -177,6 +177,7 @@ set(SRC
BLI_bitmap_draw_2d.h
BLI_blenlib.h
BLI_bounds.hh
BLI_bounds_types.hh
BLI_boxpack_2d.h
BLI_buffer.h
BLI_cache_mutex.hh
@ -306,6 +307,7 @@ set(SRC
BLI_session_uuid.h
BLI_set.hh
BLI_set_slots.hh
BLI_shared_cache.hh
BLI_simd.h
BLI_smallhash.h
BLI_sort.h

View File

@ -58,6 +58,7 @@
#include "BKE_lib_override.h"
#include "BKE_main.h"
#include "BKE_main_namemap.h"
#include "BKE_mesh.h"
#include "BKE_modifier.h"
#include "BKE_node.h"
#include "BKE_screen.h"
@ -3690,6 +3691,15 @@ void blo_do_versions_300(FileData *fd, Library * /*lib*/, Main *bmain)
}
}
if (!MAIN_VERSION_ATLEAST(bmain, 305, 1)) {
/* Reset edge visibility flag, since the base is meant to be "true" for original meshes. */
LISTBASE_FOREACH (Mesh *, mesh, &bmain->meshes) {
for (MEdge &edge : mesh->edges_for_write()) {
edge.flag |= ME_EDGEDRAW;
}
}
}
/**
* Versioning code until next subversion bump goes here.
*

View File

@ -826,23 +826,6 @@ static void bm_to_mesh_shape(BMesh *bm,
/** \} */
BLI_INLINE void bmesh_quick_edgedraw_flag(MEdge *med, BMEdge *e)
{
/* This is a cheap way to set the edge draw, its not precise and will
* pick the first 2 faces an edge uses.
* The dot comparison is a little arbitrary, but set so that a 5 subdivisions
* ico-sphere won't vanish but 6 subdivisions will (as with pre-bmesh Blender). */
if (/* (med->flag & ME_EDGEDRAW) && */ /* Assume to be true. */
(e->l && (e->l != e->l->radial_next)) &&
(dot_v3v3(e->l->f->no, e->l->radial_next->f->no) > 0.9995f)) {
med->flag &= ~ME_EDGEDRAW;
}
else {
med->flag |= ME_EDGEDRAW;
}
}
template<typename T, typename GetFn>
static void write_fn_to_attribute(blender::bke::MutableAttributeAccessor attributes,
const StringRef attribute_name,
@ -958,6 +941,8 @@ void BM_mesh_bm_to_me(Main *bmain, BMesh *bm, Mesh *me, const struct BMeshToMesh
CustomData_free(&me->ldata, me->totloop);
CustomData_free(&me->pdata, me->totpoly);
BKE_mesh_runtime_clear_geometry(me);
/* Add new custom data. */
me->totvert = bm->totvert;
me->totedge = bm->totedge;
@ -994,10 +979,6 @@ void BM_mesh_bm_to_me(Main *bmain, BMesh *bm, Mesh *me, const struct BMeshToMesh
bool need_hide_poly = false;
bool need_material_index = false;
/* Clear normals on the mesh completely, since the original vertex and polygon count might be
* different than the BMesh's. */
BKE_mesh_clear_derived_normals(me);
i = 0;
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
copy_v3_v3(mvert[i].co, v->co);
@ -1038,8 +1019,6 @@ void BM_mesh_bm_to_me(Main *bmain, BMesh *bm, Mesh *me, const struct BMeshToMesh
/* Copy over custom-data. */
CustomData_from_bmesh_block(&bm->edata, &me->edata, e->head.data, i);
bmesh_quick_edgedraw_flag(&medge[i], e);
i++;
BM_CHECK_ELEMENT(e);
}
@ -1205,9 +1184,6 @@ void BM_mesh_bm_to_me(Main *bmain, BMesh *bm, Mesh *me, const struct BMeshToMesh
/* Topology could be changed, ensure #CD_MDISPS are ok. */
multires_topology_changed(me);
/* To be removed as soon as COW is enabled by default. */
BKE_mesh_runtime_clear_geometry(me);
}
/* NOTE: The function is called from multiple threads with the same input BMesh and different
@ -1219,6 +1195,8 @@ void BM_mesh_bm_to_me_for_eval(BMesh *bm, Mesh *me, const CustomData_MeshMasks *
/* Must be an empty mesh. */
BLI_assert(me->totvert == 0);
BLI_assert(cd_mask_extra == nullptr || (cd_mask_extra->vmask & CD_MASK_SHAPEKEY) == 0);
/* Just in case, clear the derived geometry caches from the input mesh. */
BKE_mesh_runtime_clear_geometry(me);
me->totvert = bm->totvert;
me->totedge = bm->totedge;
@ -1254,10 +1232,6 @@ void BM_mesh_bm_to_me_for_eval(BMesh *bm, Mesh *me, const CustomData_MeshMasks *
MLoop *mloop = loops.data();
uint i, j;
/* Clear normals on the mesh completely, since the original vertex and polygon count might be
* different than the BMesh's. */
BKE_mesh_clear_derived_normals(me);
me->runtime->deformed_only = true;
bke::MutableAttributeAccessor mesh_attributes = me->attributes_for_write();
@ -1316,14 +1290,6 @@ void BM_mesh_bm_to_me_for_eval(BMesh *bm, Mesh *me, const CustomData_MeshMasks *
select_edge_attribute.span[i] = true;
}
/* Handle this differently to editmode switching,
* only enable draw for single user edges rather than calculating angle. */
if ((med->flag & ME_EDGEDRAW) == 0) {
if (eed->l && eed->l == eed->l->radial_next) {
med->flag |= ME_EDGEDRAW;
}
}
CustomData_from_bmesh_block(&bm->edata, &me->edata, eed->head.data, i);
}
bm->elem_index_dirty &= ~BM_EDGE;

View File

@ -2970,6 +2970,11 @@ void DepsgraphRelationBuilder::build_sound(bSound *sound)
build_idproperties(sound->id.properties);
build_animdata(&sound->id);
build_parameters(&sound->id);
const ComponentKey parameters_key(&sound->id, NodeType::PARAMETERS);
const ComponentKey audio_key(&sound->id, NodeType::AUDIO);
add_relation(parameters_key, audio_key, "Parameters -> Audio");
}
void DepsgraphRelationBuilder::build_simulation(Simulation *simulation)

View File

@ -174,7 +174,7 @@ static void eevee_cache_finish(void *vedata)
}
if (g_data->queued_shaders_count > 0) {
SNPRINTF(ved->info, "Compiling Shaders %d", g_data->queued_shaders_count);
SNPRINTF(ved->info, "Compiling Shaders (%d remaining)", g_data->queued_shaders_count);
}
}

View File

@ -38,7 +38,7 @@ void EEVEE_shadows_cube_add(EEVEE_LightsInfo *linfo, EEVEE_Light *evli, Object *
/* Saving light bounds for later. */
BoundSphere *cube_bound = linfo->shadow_bounds + linfo->cube_len;
copy_v3_v3(cube_bound->center, evli->position);
cube_bound->radius = sqrt(1.0f / evli->invsqrdist);
cube_bound->radius = sqrt(1.0f / min_ff(evli->invsqrdist, evli->invsqrdist_volume));
linfo->shadow_cube_light_indices[linfo->cube_len] = linfo->num_light;
evli->shadow_id = linfo->shadow_len++;
@ -87,7 +87,7 @@ bool EEVEE_shadows_cube_setup(EEVEE_LightsInfo *linfo, const EEVEE_Light *evli,
eevee_light_matrix_get(evli, cube_data->shadowmat);
shdw_data->far = max_ff(sqrt(1.0f / evli->invsqrdist), 3e-4);
shdw_data->far = max_ff(sqrt(1.0f / min_ff(evli->invsqrdist, evli->invsqrdist_volume)), 3e-4);
shdw_data->near = min_ff(shdw_data->near, shdw_data->far - 1e-4);
bool update = false;

View File

@ -20,7 +20,7 @@ layout(location = 1) out vec4 volumeExtinction;
layout(location = 2) out vec4 volumeEmissive;
layout(location = 3) out vec4 volumePhase;
int attr_id;
int attr_id = 0;
#ifndef CLEAR
GlobalData init_globals(void)

View File

@ -337,7 +337,7 @@ void Instance::draw_viewport(DefaultFramebufferList *dfbl)
if (materials.queued_shaders_count > 0) {
std::stringstream ss;
ss << "Compiling Shaders " << materials.queued_shaders_count;
ss << "Compiling Shaders (" << materials.queued_shaders_count << " remaining)";
info = ss.str();
}
}

View File

@ -139,6 +139,8 @@ void OVERLAY_edit_uv_init(OVERLAY_Data *vedata)
((is_paint_mode && do_tex_paint_shadows &&
((draw_ctx->object_mode &
(OB_MODE_TEXTURE_PAINT | OB_MODE_EDIT)) != 0)) ||
(is_uv_editor && do_tex_paint_shadows &&
((draw_ctx->object_mode & (OB_MODE_TEXTURE_PAINT)) != 0)) ||
(is_view_mode && do_tex_paint_shadows &&
((draw_ctx->object_mode & (OB_MODE_TEXTURE_PAINT)) != 0)) ||
(do_uv_overlay && (show_modified_uvs)));

View File

@ -42,7 +42,7 @@ void DRW_curve_batch_cache_free(struct Curve *cu);
void DRW_mesh_batch_cache_dirty_tag(struct Mesh *me, eMeshBatchDirtyMode mode);
void DRW_mesh_batch_cache_validate(struct Object *object, struct Mesh *me);
void DRW_mesh_batch_cache_free(struct Mesh *me);
void DRW_mesh_batch_cache_free(void *batch_cache);
void DRW_lattice_batch_cache_dirty_tag(struct Lattice *lt, int mode);
void DRW_lattice_batch_cache_validate(struct Lattice *lt);

View File

@ -201,7 +201,7 @@ static constexpr DRWBatchFlag batches_that_use_buffer(const int buffer_index)
}
static void mesh_batch_cache_discard_surface_batches(MeshBatchCache *cache);
static void mesh_batch_cache_clear(Mesh *me);
static void mesh_batch_cache_clear(MeshBatchCache *cache);
static void mesh_batch_cache_discard_batch(MeshBatchCache *cache, const DRWBatchFlag batch_map)
{
@ -618,7 +618,9 @@ static void mesh_batch_cache_init(Object *object, Mesh *me)
void DRW_mesh_batch_cache_validate(Object *object, Mesh *me)
{
if (!mesh_batch_cache_valid(object, me)) {
mesh_batch_cache_clear(me);
if (me->runtime->batch_cache) {
mesh_batch_cache_clear(static_cast<MeshBatchCache *>(me->runtime->batch_cache));
}
mesh_batch_cache_init(object, me);
}
}
@ -819,12 +821,8 @@ static void mesh_batch_cache_free_subdiv_cache(MeshBatchCache *cache)
}
}
static void mesh_batch_cache_clear(Mesh *me)
static void mesh_batch_cache_clear(MeshBatchCache *cache)
{
MeshBatchCache *cache = static_cast<MeshBatchCache *>(me->runtime->batch_cache);
if (!cache) {
return;
}
FOREACH_MESH_BUFFER_CACHE (cache, mbc) {
mesh_buffer_cache_clear(mbc);
}
@ -850,10 +848,12 @@ static void mesh_batch_cache_clear(Mesh *me)
mesh_batch_cache_free_subdiv_cache(cache);
}
void DRW_mesh_batch_cache_free(Mesh *me)
void DRW_mesh_batch_cache_free(void *batch_cache)
{
mesh_batch_cache_clear(me);
MEM_SAFE_FREE(me->runtime->batch_cache);
if (batch_cache) {
mesh_batch_cache_clear(static_cast<MeshBatchCache *>(batch_cache));
MEM_freeN(batch_cache);
}
}
/** \} */

View File

@ -56,6 +56,12 @@ void ResourceBind::execute() const
case ResourceBind::Type::StorageBuf:
GPU_storagebuf_bind(is_reference ? *storage_buf_ref : storage_buf, slot);
break;
case ResourceBind::Type::UniformAsStorageBuf:
GPU_uniformbuf_bind_as_ssbo(is_reference ? *uniform_buf_ref : uniform_buf, slot);
break;
case ResourceBind::Type::VertexAsStorageBuf:
GPU_vertbuf_bind_as_ssbo(is_reference ? *vertex_buf_ref : vertex_buf, slot);
break;
}
}
@ -266,6 +272,12 @@ std::string ResourceBind::serialize() const
case Type::StorageBuf:
return std::string(".bind_storage_buf") + (is_reference ? "_ref" : "") + "(" +
std::to_string(slot) + ")";
case Type::UniformAsStorageBuf:
return std::string(".bind_uniform_as_ssbo") + (is_reference ? "_ref" : "") + "(" +
std::to_string(slot) + ")";
case Type::VertexAsStorageBuf:
return std::string(".bind_vertbuf_as_ssbo") + (is_reference ? "_ref" : "") + "(" +
std::to_string(slot) + ")";
default:
BLI_assert_unreachable();
return "";

View File

@ -138,6 +138,8 @@ struct ResourceBind {
Image,
UniformBuf,
StorageBuf,
UniformAsStorageBuf,
VertexAsStorageBuf,
} type;
union {
@ -164,6 +166,14 @@ struct ResourceBind {
: slot(slot_), is_reference(false), type(Type::StorageBuf), storage_buf(res){};
ResourceBind(int slot_, GPUStorageBuf **res)
: slot(slot_), is_reference(true), type(Type::StorageBuf), storage_buf_ref(res){};
ResourceBind(int slot_, GPUUniformBuf *res, Type /* type */)
: slot(slot_), is_reference(false), type(Type::UniformAsStorageBuf), uniform_buf(res){};
ResourceBind(int slot_, GPUUniformBuf **res, Type /* type */)
: slot(slot_), is_reference(true), type(Type::UniformAsStorageBuf), uniform_buf_ref(res){};
ResourceBind(int slot_, GPUVertBuf *res, Type /* type */)
: slot(slot_), is_reference(false), type(Type::VertexAsStorageBuf), vertex_buf(res){};
ResourceBind(int slot_, GPUVertBuf **res, Type /* type */)
: slot(slot_), is_reference(true), type(Type::VertexAsStorageBuf), vertex_buf_ref(res){};
ResourceBind(int slot_, draw::Image *res)
: slot(slot_), is_reference(false), type(Type::Image), texture(draw::as_texture(res)){};
ResourceBind(int slot_, draw::Image **res)

View File

@ -1333,10 +1333,6 @@ void DRW_notify_view_update(const DRWUpdateContext *update_ctx)
const bool gpencil_engine_needed = drw_gpencil_engine_needed(depsgraph, v3d);
if (G.is_rendering && U.experimental.use_draw_manager_acquire_lock) {
return;
}
/* XXX Really nasty locking. But else this could
* be executed by the material previews thread
* while rendering a viewport. */

View File

@ -289,6 +289,14 @@ class PassBase {
void bind_ssbo(const char *name, GPUStorageBuf **buffer);
void bind_ssbo(int slot, GPUStorageBuf *buffer);
void bind_ssbo(int slot, GPUStorageBuf **buffer);
void bind_ssbo(const char *name, GPUUniformBuf *buffer);
void bind_ssbo(const char *name, GPUUniformBuf **buffer);
void bind_ssbo(int slot, GPUUniformBuf *buffer);
void bind_ssbo(int slot, GPUUniformBuf **buffer);
void bind_ssbo(const char *name, GPUVertBuf *buffer);
void bind_ssbo(const char *name, GPUVertBuf **buffer);
void bind_ssbo(int slot, GPUVertBuf *buffer);
void bind_ssbo(int slot, GPUVertBuf **buffer);
void bind_ubo(const char *name, GPUUniformBuf *buffer);
void bind_ubo(const char *name, GPUUniformBuf **buffer);
void bind_ubo(int slot, GPUUniformBuf *buffer);
@ -841,6 +849,26 @@ template<class T> inline void PassBase<T>::bind_ssbo(const char *name, GPUStorag
this->bind_ssbo(GPU_shader_get_ssbo(shader_, name), buffer);
}
template<class T> inline void PassBase<T>::bind_ssbo(const char *name, GPUUniformBuf *buffer)
{
this->bind_ssbo(GPU_shader_get_ssbo(shader_, name), buffer);
}
template<class T> inline void PassBase<T>::bind_ssbo(const char *name, GPUUniformBuf **buffer)
{
this->bind_ssbo(GPU_shader_get_ssbo(shader_, name), buffer);
}
template<class T> inline void PassBase<T>::bind_ssbo(const char *name, GPUVertBuf *buffer)
{
this->bind_ssbo(GPU_shader_get_ssbo(shader_, name), buffer);
}
template<class T> inline void PassBase<T>::bind_ssbo(const char *name, GPUVertBuf **buffer)
{
this->bind_ssbo(GPU_shader_get_ssbo(shader_, name), buffer);
}
template<class T> inline void PassBase<T>::bind_ubo(const char *name, GPUUniformBuf *buffer)
{
this->bind_ubo(GPU_shader_get_uniform_block_binding(shader_, name), buffer);
@ -874,6 +902,30 @@ template<class T> inline void PassBase<T>::bind_ssbo(int slot, GPUStorageBuf *bu
create_command(Type::ResourceBind).resource_bind = {slot, buffer};
}
template<class T> inline void PassBase<T>::bind_ssbo(int slot, GPUUniformBuf *buffer)
{
create_command(Type::ResourceBind).resource_bind = {
slot, buffer, ResourceBind::Type::UniformAsStorageBuf};
}
template<class T> inline void PassBase<T>::bind_ssbo(int slot, GPUUniformBuf **buffer)
{
create_command(Type::ResourceBind).resource_bind = {
slot, buffer, ResourceBind::Type::UniformAsStorageBuf};
}
template<class T> inline void PassBase<T>::bind_ssbo(int slot, GPUVertBuf *buffer)
{
create_command(Type::ResourceBind).resource_bind = {
slot, buffer, ResourceBind::Type::VertexAsStorageBuf};
}
template<class T> inline void PassBase<T>::bind_ssbo(int slot, GPUVertBuf **buffer)
{
create_command(Type::ResourceBind).resource_bind = {
slot, buffer, ResourceBind::Type::VertexAsStorageBuf};
}
template<class T> inline void PassBase<T>::bind_ubo(int slot, GPUUniformBuf *buffer)
{
create_command(Type::ResourceBind).resource_bind = {slot, buffer};

View File

@ -20,10 +20,10 @@ namespace blender::draw {
* \{ */
struct MeshExtract_EdgeFac_Data {
uchar *vbo_data;
uint8_t *vbo_data;
bool use_edge_render;
/* Number of loop per edge. */
uchar *edge_loop_count;
uint8_t *edge_loop_count;
};
static float loop_edge_factor_get(const float f_no[3],
@ -59,8 +59,7 @@ static void extract_edge_fac_init(const MeshRenderData *mr,
MeshExtract_EdgeFac_Data *data = static_cast<MeshExtract_EdgeFac_Data *>(tls_data);
if (mr->extract_type == MR_EXTRACT_MESH) {
data->edge_loop_count = static_cast<uchar *>(
MEM_callocN(sizeof(uint32_t) * mr->edge_len, __func__));
data->edge_loop_count = MEM_cnew_array<uint8_t>(mr->edge_len, __func__);
/* HACK(@fclem): Detecting the need for edge render.
* We could have a flag in the mesh instead or check the modifier stack. */

View File

@ -2005,23 +2005,23 @@ static size_t animdata_filter_ds_cachefile(
/* Helper for Mask Editing - mask layers */
static size_t animdata_filter_mask_data(ListBase *anim_data, Mask *mask, const int filter_mode)
{
MaskLayer *masklay_act = BKE_mask_layer_active(mask);
MaskLayer *masklay;
const MaskLayer *masklay_act = BKE_mask_layer_active(mask);
size_t items = 0;
/* loop over layers as the conditions are acceptable */
for (masklay = mask->masklayers.first; masklay; masklay = masklay->next) {
/* only if selected */
if (ANIMCHANNEL_SELOK(SEL_MASKLAY(masklay))) {
/* only if editable */
if (!(filter_mode & ANIMFILTER_FOREDIT) || EDITABLE_MASK(masklay)) {
/* active... */
if (!(filter_mode & ANIMFILTER_ACTIVE) || (masklay_act == masklay)) {
/* add to list */
ANIMCHANNEL_NEW_CHANNEL(masklay, ANIMTYPE_MASKLAYER, mask, NULL);
}
}
LISTBASE_FOREACH (MaskLayer *, masklay, &mask->masklayers) {
if (!ANIMCHANNEL_SELOK(SEL_MASKLAY(masklay))) {
continue;
}
if ((filter_mode & ANIMFILTER_FOREDIT) && !EDITABLE_MASK(masklay)) {
continue;
}
if ((filter_mode & ANIMFILTER_ACTIVE) & (masklay_act != masklay)) {
continue;
}
ANIMCHANNEL_NEW_CHANNEL(masklay, ANIMTYPE_MASKLAYER, mask, NULL);
}
return items;
@ -2033,12 +2033,11 @@ static size_t animdata_filter_mask(Main *bmain,
void *UNUSED(data),
int filter_mode)
{
Mask *mask;
size_t items = 0;
/* For now, grab mask data-blocks directly from main. */
/* XXX: this is not good... */
for (mask = bmain->masks.first; mask; mask = mask->id.next) {
LISTBASE_FOREACH (Mask *, mask, &bmain->masks) {
ListBase tmp_data = {NULL, NULL};
size_t tmp_items = 0;
@ -2048,24 +2047,28 @@ static size_t animdata_filter_mask(Main *bmain,
}
/* add mask animation channels */
BEGIN_ANIMFILTER_SUBCHANNELS (EXPANDED_MASK(mask)) {
tmp_items += animdata_filter_mask_data(&tmp_data, mask, filter_mode);
if (!(filter_mode & ANIMFILTER_FCURVESONLY)) {
BEGIN_ANIMFILTER_SUBCHANNELS (EXPANDED_MASK(mask)) {
tmp_items += animdata_filter_mask_data(&tmp_data, mask, filter_mode);
}
END_ANIMFILTER_SUBCHANNELS;
}
END_ANIMFILTER_SUBCHANNELS;
/* did we find anything? */
if (tmp_items) {
/* include data-expand widget first */
if (filter_mode & ANIMFILTER_LIST_CHANNELS) {
/* add mask data-block as channel too (if for drawing, and it has layers) */
ANIMCHANNEL_NEW_CHANNEL(mask, ANIMTYPE_MASKDATABLOCK, NULL, NULL);
}
/* now add the list of collected channels */
BLI_movelisttolist(anim_data, &tmp_data);
BLI_assert(BLI_listbase_is_empty(&tmp_data));
items += tmp_items;
if (!tmp_items) {
continue;
}
/* include data-expand widget first */
if (filter_mode & ANIMFILTER_LIST_CHANNELS) {
/* add mask data-block as channel too (if for drawing, and it has layers) */
ANIMCHANNEL_NEW_CHANNEL(mask, ANIMTYPE_MASKDATABLOCK, NULL, NULL);
}
/* now add the list of collected channels */
BLI_movelisttolist(anim_data, &tmp_data);
BLI_assert(BLI_listbase_is_empty(&tmp_data));
items += tmp_items;
}
/* return the number of items added to the list */

View File

@ -421,7 +421,6 @@ int ED_mesh_join_objects_exec(bContext *C, wmOperator *op)
* Even though this mesh wont typically have run-time data, the Python API can for e.g.
* create loop-triangle cache here, which is confusing when left in the mesh, see: T90798. */
BKE_mesh_runtime_clear_geometry(me);
BKE_mesh_clear_derived_normals(me);
/* new material indices and material array */
if (totmat) {
@ -689,9 +688,6 @@ int ED_mesh_join_objects_exec(bContext *C, wmOperator *op)
me->ldata = ldata;
me->pdata = pdata;
/* Tag normals dirty because vertex positions could be changed from the original. */
BKE_mesh_normals_tag_dirty(me);
/* old material array */
for (a = 1; a <= ob->totcol; a++) {
ma = ob->mat[a - 1];

View File

@ -157,11 +157,6 @@ static int voxel_remesh_exec(bContext *C, wmOperator *op)
new_mesh = mesh_fixed_poles;
}
if (mesh->flag & ME_REMESH_REPROJECT_VOLUME || mesh->flag & ME_REMESH_REPROJECT_PAINT_MASK ||
mesh->flag & ME_REMESH_REPROJECT_SCULPT_FACE_SETS) {
BKE_mesh_runtime_clear_geometry(mesh);
}
if (mesh->flag & ME_REMESH_REPROJECT_VOLUME) {
BKE_shrinkwrap_remesh_target_project(new_mesh, mesh, ob);
}
@ -175,7 +170,6 @@ static int voxel_remesh_exec(bContext *C, wmOperator *op)
}
if (mesh->flag & ME_REMESH_REPROJECT_VERTEX_COLORS) {
BKE_mesh_runtime_clear_geometry(mesh);
BKE_remesh_reproject_vertex_paint(new_mesh, mesh);
}
@ -900,7 +894,6 @@ static void quadriflow_start_job(void *customdata, bool *stop, bool *do_update,
}
if (qj->preserve_paint_mask) {
BKE_mesh_runtime_clear_geometry(mesh);
BKE_mesh_remesh_reproject_paint_mask(new_mesh, mesh);
}

View File

@ -268,35 +268,35 @@ eScreenDir area_getorientation(ScrArea *sa_a, ScrArea *sa_b)
return SCREEN_DIR_NONE;
}
const vec2s *sa_bl = &sa_a->v1->vec;
const vec2s *sa_tl = &sa_a->v2->vec;
const vec2s *sa_tr = &sa_a->v3->vec;
const vec2s *sa_br = &sa_a->v4->vec;
short left_a = sa_a->v1->vec.x;
short right_a = sa_a->v3->vec.x;
short top_a = sa_a->v3->vec.y;
short bottom_a = sa_a->v1->vec.y;
const vec2s *sb_bl = &sa_b->v1->vec;
const vec2s *sb_tl = &sa_b->v2->vec;
const vec2s *sb_tr = &sa_b->v3->vec;
const vec2s *sb_br = &sa_b->v4->vec;
short left_b = sa_b->v1->vec.x;
short right_b = sa_b->v3->vec.x;
short top_b = sa_b->v3->vec.y;
short bottom_b = sa_b->v1->vec.y;
if (sa_bl->x == sb_br->x && sa_tl->x == sb_tr->x) { /* sa_a to right of sa_b = W */
if ((MIN2(sa_tl->y, sb_tr->y) - MAX2(sa_bl->y, sb_br->y)) > AREAJOINTOLERANCEY) {
return 0;
}
/* How much these areas share a common edge. */
short overlapx = MIN2(right_a, right_b) - MAX2(left_a, left_b);
short overlapy = MIN2(top_a, top_b) - MAX2(bottom_a, bottom_b);
/* Minimum overlap required. */
const short minx = MIN3(AREAJOINTOLERANCEX, right_a - left_a, right_b - left_b);
const short miny = MIN3(AREAJOINTOLERANCEY, top_a - bottom_a, top_b - bottom_b);
if (top_a == bottom_b && overlapx >= minx) {
return 1; /* sa_a to bottom of sa_b = N */
}
else if (sa_tl->y == sb_bl->y && sa_tr->y == sb_br->y) { /* sa_a to bottom of sa_b = N */
if ((MIN2(sa_tr->x, sb_br->x) - MAX2(sa_tl->x, sb_bl->x)) > AREAJOINTOLERANCEX) {
return 1;
}
if (bottom_a == top_b && overlapx >= minx) {
return 3; /* sa_a on top of sa_b = S */
}
else if (sa_tr->x == sb_tl->x && sa_br->x == sb_bl->x) { /* sa_a to left of sa_b = E */
if ((MIN2(sa_tr->y, sb_tl->y) - MAX2(sa_br->y, sb_bl->y)) > AREAJOINTOLERANCEY) {
return 2;
}
if (left_a == right_b && overlapy >= miny) {
return 0; /* sa_a to right of sa_b = W */
}
else if (sa_bl->y == sb_tl->y && sa_br->y == sb_tr->y) { /* sa_a on top of sa_b = S */
if ((MIN2(sa_br->x, sb_tr->x) - MAX2(sa_bl->x, sb_tl->x)) > AREAJOINTOLERANCEX) {
return 3;
}
if (right_a == left_b && overlapy >= miny) {
return 2; /* sa_a to left of sa_b = E */
}
return -1;
@ -360,11 +360,65 @@ static void screen_verts_valign(const wmWindow *win,
}
}
/* Test if two adjoining areas can be aligned by having their screen edges adjusted. */
static bool screen_areas_can_align(bScreen *screen, ScrArea *sa1, ScrArea *sa2, eScreenDir dir)
{
if (dir == SCREEN_DIR_NONE) {
return false;
}
int offset1;
int offset2;
area_getoffsets(sa1, sa2, dir, &offset1, &offset2);
const int tolerance = SCREEN_DIR_IS_HORIZONTAL(dir) ? AREAJOINTOLERANCEY : AREAJOINTOLERANCEX;
if ((abs(offset1) >= tolerance) || (abs(offset2) >= tolerance)) {
/* Misalignment is too great. */
return false;
}
/* Areas that are _smaller_ than minimum sizes, sharing an edge to be moved. See T100772. */
if (SCREEN_DIR_IS_VERTICAL(dir)) {
const short xmin = MIN2(sa1->v1->vec.x, sa2->v1->vec.x);
const short xmax = MAX2(sa1->v3->vec.x, sa2->v3->vec.x);
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
if (area == sa1 || area == sa2) {
continue;
}
if (area->v3->vec.x - area->v1->vec.x < tolerance &&
(area->v1->vec.x == xmin || area->v3->vec.x == xmax)) {
/* There is a narrow vertical area sharing an edge of the combined bounds. */
return false;
}
}
}
else {
const short ymin = MIN2(sa1->v1->vec.y, sa2->v1->vec.y);
const short ymax = MAX2(sa1->v3->vec.y, sa2->v3->vec.y);
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
if (area == sa1 || area == sa2) {
continue;
}
if (area->v3->vec.y - area->v1->vec.y < tolerance &&
(area->v1->vec.y == ymin || area->v3->vec.y == ymax)) {
/* There is a narrow horizontal area sharing an edge of the combined bounds. */
return false;
}
}
}
return true;
}
/* Adjust all screen edges to allow joining two areas. 'dir' value is like area_getorientation().
*/
static void screen_areas_align(
static bool screen_areas_align(
bContext *C, bScreen *screen, ScrArea *sa1, ScrArea *sa2, const eScreenDir dir)
{
if (!screen_areas_can_align(screen, sa1, sa2, dir)) {
return false;
}
wmWindow *win = CTX_wm_window(C);
if (SCREEN_DIR_IS_HORIZONTAL(dir)) {
@ -393,28 +447,20 @@ static void screen_areas_align(
screen_verts_halign(win, screen, sa2->v1->vec.x, left);
screen_verts_halign(win, screen, sa2->v3->vec.x, right);
}
return true;
}
/* Simple join of two areas without any splitting. Will return false if not possible. */
static bool screen_area_join_aligned(bContext *C, bScreen *screen, ScrArea *sa1, ScrArea *sa2)
{
const eScreenDir dir = area_getorientation(sa1, sa2);
if (dir == SCREEN_DIR_NONE) {
/* Ensure that the area edges are exactly aligned. */
if (!screen_areas_align(C, screen, sa1, sa2, dir)) {
return false;
}
int offset1;
int offset2;
area_getoffsets(sa1, sa2, dir, &offset1, &offset2);
int tolerance = SCREEN_DIR_IS_HORIZONTAL(dir) ? AREAJOINTOLERANCEY : AREAJOINTOLERANCEX;
if ((abs(offset1) >= tolerance) || (abs(offset2) >= tolerance)) {
return false;
}
/* Align areas if they are not. */
screen_areas_align(C, screen, sa1, sa2, dir);
if (dir == SCREEN_DIR_W) { /* sa1 to right of sa2 = West. */
sa1->v1 = sa2->v1; /* BL */
sa1->v2 = sa2->v2; /* TL */

View File

@ -29,6 +29,8 @@
#include "BKE_report.h"
#include "BKE_screen.h"
#include "BLT_translation.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "RNA_prototypes.h"
@ -164,7 +166,8 @@ static int screenshot_invoke(bContext *C, wmOperator *op, const wmEvent *event)
}
/* extension is added by 'screenshot_check' after */
char filepath[FILE_MAX] = "//screen";
char filepath[FILE_MAX];
BLI_snprintf(filepath, FILE_MAX, "//%s", DATA_("screen"));
const char *blendfile_path = BKE_main_blendfile_path_from_global();
if (blendfile_path[0] != '\0') {
BLI_strncpy(filepath, blendfile_path, sizeof(filepath));

View File

@ -18,6 +18,8 @@
#include "BLI_gsqueue.h"
#include "BLI_math.h"
#include "BLI_task.h"
#include "BLI_task.hh"
#include "BLI_timeit.hh"
#include "BLI_utildefines.h"
#include "DNA_brush_types.h"
@ -69,6 +71,8 @@
#include "bmesh.h"
using blender::MutableSpan;
/* -------------------------------------------------------------------- */
/** \name Sculpt PBVH Abstraction API
*
@ -3682,48 +3686,46 @@ static void do_brush_action(Sculpt *sd,
}
/* Flush displacement from deformed PBVH vertex to original mesh. */
static void sculpt_flush_pbvhvert_deform(Object *ob, PBVHVertexIter *vd)
static void sculpt_flush_pbvhvert_deform(const SculptSession &ss,
const PBVHVertexIter &vd,
MutableSpan<MVert> verts)
{
SculptSession *ss = ob->sculpt;
Mesh *me = static_cast<Mesh *>(ob->data);
float disp[3], newco[3];
int index = vd->vert_indices[vd->i];
int index = vd.vert_indices[vd.i];
sub_v3_v3v3(disp, vd->co, ss->deform_cos[index]);
mul_m3_v3(ss->deform_imats[index], disp);
add_v3_v3v3(newco, disp, ss->orig_cos[index]);
sub_v3_v3v3(disp, vd.co, ss.deform_cos[index]);
mul_m3_v3(ss.deform_imats[index], disp);
add_v3_v3v3(newco, disp, ss.orig_cos[index]);
copy_v3_v3(ss->deform_cos[index], vd->co);
copy_v3_v3(ss->orig_cos[index], newco);
copy_v3_v3(ss.deform_cos[index], vd.co);
copy_v3_v3(ss.orig_cos[index], newco);
MVert *verts = BKE_mesh_verts_for_write(me);
if (!ss->shapekey_active) {
if (!ss.shapekey_active) {
copy_v3_v3(verts[index].co, newco);
}
}
static void sculpt_combine_proxies_task_cb(void *__restrict userdata,
const int n,
const TaskParallelTLS *__restrict /*tls*/)
static void sculpt_combine_proxies_node(Object &object,
Sculpt &sd,
const bool use_orco,
PBVHNode &node)
{
SculptThreadedTaskData *data = static_cast<SculptThreadedTaskData *>(userdata);
SculptSession *ss = data->ob->sculpt;
Sculpt *sd = data->sd;
Object *ob = data->ob;
const bool use_orco = data->use_proxies_orco;
SculptSession *ss = object.sculpt;
PBVHVertexIter vd;
PBVHProxyNode *proxies;
int proxy_count;
float(*orco)[3] = nullptr;
if (use_orco && !ss->bm) {
orco = SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_COORDS)->co;
orco = SCULPT_undo_push_node(&object, &node, SCULPT_UNDO_COORDS)->co;
}
BKE_pbvh_node_get_proxies(data->nodes[n], &proxies, &proxy_count);
int proxy_count;
PBVHProxyNode *proxies;
BKE_pbvh_node_get_proxies(&node, &proxies, &proxy_count);
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
Mesh &mesh = *static_cast<Mesh *>(object.data);
MutableSpan<MVert> verts = mesh.verts_for_write();
PBVHVertexIter vd;
BKE_pbvh_vertex_iter_begin (ss->pbvh, &node, vd, PBVH_ITER_UNIQUE) {
float val[3];
if (use_orco) {
@ -3742,23 +3744,22 @@ static void sculpt_combine_proxies_task_cb(void *__restrict userdata,
add_v3_v3(val, proxies[p].co[vd.i]);
}
SCULPT_clip(sd, ss, vd.co, val);
SCULPT_clip(&sd, ss, vd.co, val);
if (ss->deform_modifiers_active) {
sculpt_flush_pbvhvert_deform(ob, &vd);
sculpt_flush_pbvhvert_deform(*ss, vd, verts);
}
}
BKE_pbvh_vertex_iter_end;
BKE_pbvh_node_free_proxies(data->nodes[n]);
BKE_pbvh_node_free_proxies(&node);
}
static void sculpt_combine_proxies(Sculpt *sd, Object *ob)
{
using namespace blender;
SculptSession *ss = ob->sculpt;
Brush *brush = BKE_paint_brush(&sd->paint);
PBVHNode **nodes;
int totnode;
if (!ss->cache->supports_gravity && sculpt_tool_is_proxy_used(brush->sculpt_tool)) {
/* First line is tools that don't support proxies. */
@ -3774,37 +3775,33 @@ static void sculpt_combine_proxies(Sculpt *sd, Object *ob)
SCULPT_TOOL_BOUNDARY,
SCULPT_TOOL_POSE);
int totnode;
PBVHNode **nodes;
BKE_pbvh_gather_proxies(ss->pbvh, &nodes, &totnode);
SculptThreadedTaskData data{};
data.sd = sd;
data.ob = ob;
data.brush = brush;
data.nodes = nodes;
data.use_proxies_orco = use_orco;
threading::parallel_for(IndexRange(totnode), 1, [&](IndexRange range) {
for (const int i : range) {
sculpt_combine_proxies_node(*ob, *sd, use_orco, *nodes[i]);
}
});
TaskParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
BLI_task_parallel_range(0, totnode, &data, sculpt_combine_proxies_task_cb, &settings);
MEM_SAFE_FREE(nodes);
}
void SCULPT_combine_transform_proxies(Sculpt *sd, Object *ob)
{
using namespace blender;
SculptSession *ss = ob->sculpt;
PBVHNode **nodes;
int totnode;
PBVHNode **nodes;
BKE_pbvh_gather_proxies(ss->pbvh, &nodes, &totnode);
SculptThreadedTaskData data{};
data.sd = sd;
data.ob = ob;
data.nodes = nodes;
data.use_proxies_orco = false;
TaskParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
BLI_task_parallel_range(0, totnode, &data, sculpt_combine_proxies_task_cb, &settings);
threading::parallel_for(IndexRange(totnode), 1, [&](IndexRange range) {
for (const int i : range) {
sculpt_combine_proxies_node(*ob, *sd, false, *nodes[i]);
}
});
MEM_SAFE_FREE(nodes);
}
@ -3837,34 +3834,10 @@ static void sculpt_update_keyblock(Object *ob)
}
}
static void SCULPT_flush_stroke_deform_task_cb(void *__restrict userdata,
const int n,
const TaskParallelTLS *__restrict /*tls*/)
{
SculptThreadedTaskData *data = static_cast<SculptThreadedTaskData *>(userdata);
SculptSession *ss = data->ob->sculpt;
Object *ob = data->ob;
float(*vertCos)[3] = data->vertCos;
PBVHVertexIter vd;
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
sculpt_flush_pbvhvert_deform(ob, &vd);
if (!vertCos) {
continue;
}
int index = vd.vert_indices[vd.i];
copy_v3_v3(vertCos[index], ss->orig_cos[index]);
}
BKE_pbvh_vertex_iter_end;
}
void SCULPT_flush_stroke_deform(Sculpt *sd, Object *ob, bool is_proxy_used)
void SCULPT_flush_stroke_deform(Sculpt * /*sd*/, Object *ob, bool is_proxy_used)
{
using namespace blender;
SculptSession *ss = ob->sculpt;
Brush *brush = BKE_paint_brush(&sd->paint);
if (is_proxy_used && ss->deform_modifiers_active) {
/* This brushes aren't using proxies, so sculpt_combine_proxies() wouldn't propagate needed
@ -3886,16 +3859,24 @@ void SCULPT_flush_stroke_deform(Sculpt *sd, Object *ob, bool is_proxy_used)
BKE_pbvh_search_gather(ss->pbvh, nullptr, nullptr, &nodes, &totnode);
SculptThreadedTaskData data{};
data.sd = sd;
data.ob = ob;
data.brush = brush;
data.nodes = nodes;
data.vertCos = vertCos;
MutableSpan<MVert> verts = me->verts_for_write();
TaskParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
BLI_task_parallel_range(0, totnode, &data, SCULPT_flush_stroke_deform_task_cb, &settings);
threading::parallel_for(IndexRange(totnode), 1, [&](IndexRange range) {
for (const int i : range) {
PBVHVertexIter vd;
BKE_pbvh_vertex_iter_begin (ss->pbvh, nodes[i], vd, PBVH_ITER_UNIQUE) {
sculpt_flush_pbvhvert_deform(*ss, vd, verts);
if (!vertCos) {
continue;
}
int index = vd.vert_indices[vd.i];
copy_v3_v3(vertCos[index], ss->orig_cos[index]);
}
BKE_pbvh_vertex_iter_end;
}
});
if (vertCos) {
SCULPT_vertcos_to_key(ob, ss->shapekey_active, vertCos);
@ -5300,12 +5281,14 @@ void SCULPT_update_object_bounding_box(Object *ob)
void SCULPT_flush_update_step(bContext *C, SculptUpdateType update_flags)
{
using namespace blender;
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
Object *ob = CTX_data_active_object(C);
SculptSession *ss = ob->sculpt;
ARegion *region = CTX_wm_region(C);
MultiresModifierData *mmd = ss->multires.modifier;
RegionView3D *rv3d = CTX_wm_region_view3d(C);
Mesh *mesh = static_cast<Mesh *>(ob->data);
if (rv3d) {
/* Mark for faster 3D viewport redraws. */
@ -5364,6 +5347,17 @@ void SCULPT_flush_update_step(bContext *C, SculptUpdateType update_flags)
ED_region_tag_redraw_partial(region, &r, true);
}
}
if (update_flags & SCULPT_UPDATE_COORDS && !ss->shapekey_active) {
if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) {
/* When sculpting and changing the positions of a mesh, tag them as changed and update. */
BKE_mesh_tag_coords_changed(mesh);
/* Update the mesh's bounds eagerly since the PBVH already has that information. */
mesh->runtime->bounds_cache.ensure([&](Bounds<float3> &r_bounds) {
BKE_pbvh_bounding_box(ob->sculpt->pbvh, r_bounds.min, r_bounds.max);
});
}
}
}
void SCULPT_flush_update_done(const bContext *C, Object *ob, SculptUpdateType update_flags)

View File

@ -220,7 +220,6 @@ static int sculpt_symmetrize_exec(bContext *C, wmOperator *op)
BKE_mesh_mirror_apply_mirror_on_axis(bmain, mesh, sd->symmetrize_direction, dist);
ED_sculpt_undo_geometry_end(ob);
BKE_mesh_normals_tag_dirty(mesh);
BKE_mesh_batch_cache_dirty_tag(ob->data, BKE_MESH_BATCH_DIRTY_ALL);
break;

View File

@ -503,6 +503,11 @@ void ED_node_shader_default(const bContext *C, ID *id)
ma->nodetree = ntreeCopyTree(bmain, ma_default->nodetree);
ma->nodetree->owner_id = &ma->id;
LISTBASE_FOREACH (bNode *, node_iter, &ma->nodetree->nodes) {
BLI_strncpy(node_iter->name, DATA_(node_iter->name), NODE_MAXSTR);
nodeUniqueName(ma->nodetree, node_iter);
}
BKE_ntree_update_main_tree(bmain, ma->nodetree, nullptr);
}
else if (ELEM(GS(id->name), ID_WO, ID_LA)) {
@ -2237,7 +2242,6 @@ static int node_clipboard_copy_exec(bContext *C, wmOperator * /*op*/)
/* clear current clipboard */
BKE_node_clipboard_clear();
BKE_node_clipboard_init(ntree);
Map<const bNode *, bNode *> node_map;
Map<const bNodeSocket *, bNodeSocket *> socket_map;
@ -2326,11 +2330,6 @@ static int node_clipboard_paste_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
if (BKE_node_clipboard_get_type() != ntree->type) {
BKE_report(op->reports, RPT_ERROR, "Clipboard nodes are an incompatible type");
return OPERATOR_CANCELLED;
}
/* only warn */
if (is_clipboard_valid == false) {
BKE_report(op->reports,
@ -2338,34 +2337,6 @@ static int node_clipboard_paste_exec(bContext *C, wmOperator *op)
"Some nodes references could not be restored, will be left empty");
}
/* make sure all clipboard nodes would be valid in the target tree */
bool all_nodes_valid = true;
LISTBASE_FOREACH (bNode *, node, clipboard_nodes_lb) {
const char *disabled_hint = nullptr;
if (!node->typeinfo->poll_instance ||
!node->typeinfo->poll_instance(node, ntree, &disabled_hint)) {
all_nodes_valid = false;
if (disabled_hint) {
BKE_reportf(op->reports,
RPT_ERROR,
"Cannot add node %s into node tree %s:\n %s",
node->name,
ntree->id.name + 2,
disabled_hint);
}
else {
BKE_reportf(op->reports,
RPT_ERROR,
"Cannot add node %s into node tree %s",
node->name,
ntree->id.name + 2);
}
}
}
if (!all_nodes_valid) {
return OPERATOR_CANCELLED;
}
ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C));
/* deselect old nodes */
@ -2383,11 +2354,32 @@ static int node_clipboard_paste_exec(bContext *C, wmOperator *op)
Map<const bNode *, bNode *> node_map;
Map<const bNodeSocket *, bNodeSocket *> socket_map;
/* copy nodes from clipboard */
/* copy valid nodes from clipboard */
LISTBASE_FOREACH (bNode *, node, clipboard_nodes_lb) {
bNode *new_node = bke::node_copy_with_mapping(
ntree, *node, LIB_ID_COPY_DEFAULT, true, socket_map);
node_map.add_new(node, new_node);
const char *disabled_hint = nullptr;
if (node->typeinfo->poll_instance &&
node->typeinfo->poll_instance(node, ntree, &disabled_hint)) {
bNode *new_node = bke::node_copy_with_mapping(
ntree, *node, LIB_ID_COPY_DEFAULT, true, socket_map);
node_map.add_new(node, new_node);
}
else {
if (disabled_hint) {
BKE_reportf(op->reports,
RPT_ERROR,
"Cannot add node %s into node tree %s: %s",
node->name,
ntree->id.name + 2,
disabled_hint);
}
else {
BKE_reportf(op->reports,
RPT_ERROR,
"Cannot add node %s into node tree %s",
node->name,
ntree->id.name + 2);
}
}
}
for (bNode *new_node : node_map.values()) {
@ -2402,15 +2394,19 @@ static int node_clipboard_paste_exec(bContext *C, wmOperator *op)
}
}
/* Add links between existing nodes. */
LISTBASE_FOREACH (bNodeLink *, link, clipboard_links_lb) {
bNodeLink *new_link = nodeAddLink(ntree,
node_map.lookup(link->fromnode),
socket_map.lookup(link->fromsock),
node_map.lookup(link->tonode),
socket_map.lookup(link->tosock));
new_link->multi_input_socket_index = link->multi_input_socket_index;
const bNode *fromnode = link->fromnode;
const bNode *tonode = link->tonode;
if (node_map.lookup_key_ptr(fromnode) && node_map.lookup_key_ptr(tonode)) {
bNodeLink *new_link = nodeAddLink(ntree,
node_map.lookup(fromnode),
socket_map.lookup(link->fromsock),
node_map.lookup(tonode),
socket_map.lookup(link->tosock));
new_link->multi_input_socket_index = link->multi_input_socket_index;
}
}
ntree->ensure_topology_cache();
for (bNode *new_node : node_map.values()) {

View File

@ -197,10 +197,14 @@ static void attribute_search_exec_fn(bContext *C, void *data_v, void *item_v)
/* Relink all node links to the newly active output socket. */
bNodeSocket *output_socket = bke::node_find_enabled_output_socket(*node, "Attribute");
LISTBASE_FOREACH (bNodeLink *, link, &node_tree->links) {
if (link->fromnode == node) {
link->fromsock = output_socket;
BKE_ntree_update_tag_link_changed(node_tree);
if (link->fromnode != node) {
continue;
}
if (!STREQ(link->fromsock->name, "Attribute")) {
continue;
}
link->fromsock = output_socket;
BKE_ntree_update_tag_link_changed(node_tree);
}
}
BKE_ntree_update_tag_node_property(node_tree, node);

View File

@ -30,7 +30,6 @@
#include "BLT_translation.h"
#include "BKE_cdderivedmesh.h"
#include "BKE_context.h"
#include "BKE_customdata.h"
#include "BKE_editmesh.h"
@ -42,7 +41,9 @@
#include "BKE_mesh.h"
#include "BKE_report.h"
#include "BKE_scene.h"
#include "BKE_subsurf.h"
#include "BKE_subdiv.h"
#include "BKE_subdiv_mesh.h"
#include "BKE_subdiv_modifier.h"
#include "DEG_depsgraph.h"
@ -569,6 +570,29 @@ static void texface_from_original_index(const Scene *scene,
}
}
static Mesh *subdivide_edit_mesh(const Object *object,
const BMEditMesh *em,
const SubsurfModifierData *smd)
{
Mesh *me_from_em = BKE_mesh_from_bmesh_for_eval_nomain(em->bm, NULL, object->data);
BKE_mesh_ensure_default_orig_index_customdata(me_from_em);
SubdivSettings settings = BKE_subsurf_modifier_settings_init(smd, false);
if (settings.level == 1) {
return me_from_em;
}
SubdivToMeshSettings mesh_settings;
mesh_settings.resolution = (1 << smd->levels) + 1;
mesh_settings.use_optimal_display = (smd->flags & eSubsurfModifierFlag_ControlEdges);
Subdiv *subdiv = BKE_subdiv_update_from_mesh(NULL, &settings, me_from_em);
Mesh *result = BKE_subdiv_to_mesh(subdiv, &mesh_settings, me_from_em);
BKE_id_free(NULL, me_from_em);
BKE_subdiv_free(subdiv);
return result;
}
/**
* Unwrap handle initialization for subsurf aware-unwrapper.
* The many modifications required to make the original function(see above)
@ -580,28 +604,11 @@ static ParamHandle *construct_param_handle_subsurfed(const Scene *scene,
const UnwrapOptions *options,
UnwrapResultInfo *result_info)
{
/* index pointers */
MPoly *mpoly;
MLoop *mloop;
MEdge *edge;
int i;
/* pointers to modifier data for unwrap control */
ModifierData *md;
SubsurfModifierData *smd_real;
/* Modifier initialization data, will control what type of subdivision will happen. */
SubsurfModifierData smd = {{NULL}};
/* Used to hold subsurfed Mesh */
DerivedMesh *derivedMesh, *initialDerived;
/* holds original indices for subsurfed mesh */
const int *origVertIndices, *origEdgeIndices, *origPolyIndices;
/* Holds vertices of subsurfed mesh */
MVert *subsurfedVerts;
MEdge *subsurfedEdges;
MPoly *subsurfedPolys;
MLoop *subsurfedLoops;
/* Number of vertices and faces for subsurfed mesh. */
int numOfEdges, numOfFaces;
/* holds a map to editfaces for every subsurfed MFace.
* These will be used to get hidden/ selected flags etc. */
@ -629,44 +636,34 @@ static ParamHandle *construct_param_handle_subsurfed(const Scene *scene,
smd.levels = smd_real->levels;
smd.subdivType = smd_real->subdivType;
smd.flags = smd_real->flags;
smd.quality = smd_real->quality;
{
Mesh *me_from_em = BKE_mesh_from_bmesh_for_eval_nomain(em->bm, NULL, ob->data);
initialDerived = CDDM_from_mesh(me_from_em);
derivedMesh = subsurf_make_derived_from_derived(
initialDerived, &smd, scene, NULL, SUBSURF_IN_EDIT_MODE);
Mesh *subdiv_mesh = subdivide_edit_mesh(ob, em, &smd);
initialDerived->release(initialDerived);
BKE_id_free(NULL, me_from_em);
}
const MVert *subsurfedVerts = BKE_mesh_verts(subdiv_mesh);
const MEdge *subsurfedEdges = BKE_mesh_edges(subdiv_mesh);
const MPoly *subsurfedPolys = BKE_mesh_polys(subdiv_mesh);
const MLoop *subsurfedLoops = BKE_mesh_loops(subdiv_mesh);
/* get the derived data */
subsurfedVerts = derivedMesh->getVertArray(derivedMesh);
subsurfedEdges = derivedMesh->getEdgeArray(derivedMesh);
subsurfedPolys = derivedMesh->getPolyArray(derivedMesh);
subsurfedLoops = derivedMesh->getLoopArray(derivedMesh);
const int *origVertIndices = CustomData_get_layer(&subdiv_mesh->vdata, CD_ORIGINDEX);
const int *origEdgeIndices = CustomData_get_layer(&subdiv_mesh->edata, CD_ORIGINDEX);
const int *origPolyIndices = CustomData_get_layer(&subdiv_mesh->pdata, CD_ORIGINDEX);
origVertIndices = derivedMesh->getVertDataArray(derivedMesh, CD_ORIGINDEX);
origEdgeIndices = derivedMesh->getEdgeDataArray(derivedMesh, CD_ORIGINDEX);
origPolyIndices = derivedMesh->getPolyDataArray(derivedMesh, CD_ORIGINDEX);
numOfEdges = derivedMesh->getNumEdges(derivedMesh);
numOfFaces = derivedMesh->getNumPolys(derivedMesh);
faceMap = MEM_mallocN(numOfFaces * sizeof(BMFace *), "unwrap_edit_face_map");
faceMap = MEM_mallocN(subdiv_mesh->totpoly * sizeof(BMFace *), "unwrap_edit_face_map");
BM_mesh_elem_index_ensure(em->bm, BM_VERT);
BM_mesh_elem_table_ensure(em->bm, BM_EDGE | BM_FACE);
/* map subsurfed faces to original editFaces */
for (i = 0; i < numOfFaces; i++) {
for (int i = 0; i < subdiv_mesh->totpoly; i++) {
faceMap[i] = BM_face_at_index(em->bm, origPolyIndices[i]);
}
edgeMap = MEM_mallocN(numOfEdges * sizeof(BMEdge *), "unwrap_edit_edge_map");
edgeMap = MEM_mallocN(subdiv_mesh->totedge * sizeof(BMEdge *), "unwrap_edit_edge_map");
/* map subsurfed edges to original editEdges */
for (i = 0; i < numOfEdges; i++) {
for (int i = 0; i < subdiv_mesh->totedge; i++) {
/* not all edges correspond to an old edge */
edgeMap[i] = (origEdgeIndices[i] != ORIGINDEX_NONE) ?
BM_edge_at_index(em->bm, origEdgeIndices[i]) :
@ -674,7 +671,8 @@ static ParamHandle *construct_param_handle_subsurfed(const Scene *scene,
}
/* Prepare and feed faces to the solver */
for (i = 0, mpoly = subsurfedPolys; i < numOfFaces; i++, mpoly++) {
for (int i = 0; i < subdiv_mesh->totpoly; i++) {
const MPoly *mpoly = &subsurfedPolys[i];
ParamKey key, vkeys[4];
bool pin[4], select[4];
const float *co[4];
@ -693,7 +691,7 @@ static ParamHandle *construct_param_handle_subsurfed(const Scene *scene,
}
}
mloop = &subsurfedLoops[mpoly->loopstart];
const MLoop *mloop = &subsurfedLoops[mpoly->loopstart];
/* We will not check for v4 here. Sub-surface faces always have 4 vertices. */
BLI_assert(mpoly->totloop == 4);
@ -744,8 +742,9 @@ static ParamHandle *construct_param_handle_subsurfed(const Scene *scene,
}
/* these are calculated from original mesh too */
for (edge = subsurfedEdges, i = 0; i < numOfEdges; i++, edge++) {
for (int i = 0; i < subdiv_mesh->totedge; i++) {
if ((edgeMap[i] != NULL) && BM_elem_flag_test(edgeMap[i], BM_ELEM_SEAM)) {
const MEdge *edge = &subsurfedEdges[i];
ParamKey vkeys[2];
vkeys[0] = (ParamKey)edge->v1;
vkeys[1] = (ParamKey)edge->v2;
@ -761,7 +760,7 @@ static ParamHandle *construct_param_handle_subsurfed(const Scene *scene,
/* cleanup */
MEM_freeN(faceMap);
MEM_freeN(edgeMap);
derivedMesh->release(derivedMesh);
BKE_id_free(NULL, subdiv_mesh);
return handle;
}

View File

@ -797,7 +797,6 @@ void BlenderStrokeRenderer::GenerateStrokeMesh(StrokeGroup *group, bool hasTex)
} // loop over strokes
BKE_object_materials_test(freestyle_bmain, object_mesh, (ID *)mesh);
BKE_mesh_normals_tag_dirty(mesh);
#if 0 // XXX
BLI_assert(mesh->totvert == vertex_index);

View File

@ -153,7 +153,7 @@ struct NodeState {
/**
* Set to true once the node is done running for the first time.
*/
bool had_initialization = true;
bool had_initialization = false;
/**
* Nodes with side effects should always be executed when their required inputs have been
* computed.
@ -306,7 +306,20 @@ class Executor {
this->set_always_unused_graph_inputs();
this->set_defaulted_graph_outputs();
this->schedule_side_effect_nodes(current_task);
/* Retrieve and tag side effect nodes. */
Vector<const FunctionNode *> side_effect_nodes;
if (self_.side_effect_provider_ != nullptr) {
side_effect_nodes = self_.side_effect_provider_->get_nodes_with_side_effects(*context_);
for (const FunctionNode *node : side_effect_nodes) {
const int node_index = node->index_in_graph();
NodeState &node_state = *node_states_[node_index];
node_state.has_side_effects = true;
}
}
this->initialize_static_value_usages(side_effect_nodes);
this->schedule_side_effect_nodes(side_effect_nodes, current_task);
}
this->schedule_newly_requested_outputs(current_task);
@ -346,15 +359,6 @@ class Executor {
node_state.inputs = allocator.construct_array<InputState>(node_inputs.size());
node_state.outputs = allocator.construct_array<OutputState>(node_outputs.size());
for (const int i : node_outputs.index_range()) {
OutputState &output_state = node_state.outputs[i];
const OutputSocket &output_socket = *node_outputs[i];
output_state.potential_target_sockets = output_socket.targets().size();
if (output_state.potential_target_sockets == 0) {
output_state.usage = ValueUsage::Unused;
}
}
}
void destruct_node_state(const Node &node, NodeState &node_state)
@ -425,19 +429,95 @@ class Executor {
}
}
void schedule_side_effect_nodes(CurrentTask &current_task)
/**
* Determines which nodes might be executed and which are unreachable. The set of reachable nodes
* can dynamically depend on the side effect nodes.
*
* Most importantly, this function initializes `InputState.usage` and
* `OutputState.potential_target_sockets`.
*/
void initialize_static_value_usages(const Span<const FunctionNode *> side_effect_nodes)
{
if (self_.side_effect_provider_ != nullptr) {
const Vector<const FunctionNode *> side_effect_nodes =
self_.side_effect_provider_->get_nodes_with_side_effects(*context_);
for (const FunctionNode *node : side_effect_nodes) {
NodeState &node_state = *node_states_[node->index_in_graph()];
node_state.has_side_effects = true;
this->with_locked_node(*node, node_state, current_task, [&](LockedNode &locked_node) {
this->schedule_node(locked_node, current_task);
});
const Span<const Node *> all_nodes = self_.graph_.nodes();
/* Used for a search through all nodes that outputs depend on. */
Stack<const Node *> reachable_nodes_to_check;
Array<bool> reachable_node_flags(all_nodes.size(), false);
/* Graph outputs are always reachable. */
for (const InputSocket *socket : self_.graph_outputs_) {
const Node &node = socket->node();
const int node_index = node.index_in_graph();
if (!reachable_node_flags[node_index]) {
reachable_node_flags[node_index] = true;
reachable_nodes_to_check.push(&node);
}
}
/* Side effect nodes are always reachable. */
for (const FunctionNode *node : side_effect_nodes) {
const int node_index = node->index_in_graph();
reachable_node_flags[node_index] = true;
reachable_nodes_to_check.push(node);
}
/* Tag every node that reachable nodes depend on using depth-first-search. */
while (!reachable_nodes_to_check.is_empty()) {
const Node &node = *reachable_nodes_to_check.pop();
for (const InputSocket *input_socket : node.inputs()) {
const OutputSocket *origin_socket = input_socket->origin();
if (origin_socket != nullptr) {
const Node &origin_node = origin_socket->node();
const int origin_node_index = origin_node.index_in_graph();
if (!reachable_node_flags[origin_node_index]) {
reachable_node_flags[origin_node_index] = true;
reachable_nodes_to_check.push(&origin_node);
}
}
}
}
for (const int node_index : reachable_node_flags.index_range()) {
const Node &node = *all_nodes[node_index];
NodeState &node_state = *node_states_[node_index];
const bool node_is_reachable = reachable_node_flags[node_index];
if (node_is_reachable) {
for (const int output_index : node.outputs().index_range()) {
const OutputSocket &output_socket = node.output(output_index);
OutputState &output_state = node_state.outputs[output_index];
int use_count = 0;
for (const InputSocket *target_socket : output_socket.targets()) {
const Node &target_node = target_socket->node();
const bool target_is_reachable = reachable_node_flags[target_node.index_in_graph()];
/* Only count targets that are reachable. */
if (target_is_reachable) {
use_count++;
}
}
output_state.potential_target_sockets = use_count;
if (use_count == 0) {
output_state.usage = ValueUsage::Unused;
}
}
}
else {
/* Inputs of unreachable nodes are unused. */
for (InputState &input_state : node_state.inputs) {
input_state.usage = ValueUsage::Unused;
}
}
}
}
void schedule_side_effect_nodes(const Span<const FunctionNode *> side_effect_nodes,
CurrentTask &current_task)
{
for (const FunctionNode *node : side_effect_nodes) {
NodeState &node_state = *node_states_[node->index_in_graph()];
this->with_locked_node(*node, node_state, current_task, [&](LockedNode &locked_node) {
this->schedule_node(locked_node, current_task);
});
}
}
void forward_newly_provided_inputs(CurrentTask &current_task)
@ -638,7 +718,7 @@ class Executor {
return;
}
if (node_state.had_initialization) {
if (!node_state.had_initialization) {
/* Initialize storage. */
node_state.storage = fn.init_storage(allocator);
@ -670,7 +750,7 @@ class Executor {
}
}
node_state.had_initialization = false;
node_state.had_initialization = true;
}
for (const int input_index : node_state.inputs.index_range()) {

View File

@ -37,16 +37,21 @@ static void calculate_result_offsets(const bke::CurvesGeometry &src_curves,
const IndexRange src_segments = curve_dst_offsets(src_points, curve_i);
MutableSpan<int> point_offsets = dst_point_offsets.slice(src_segments);
MutableSpan<int> point_counts = point_offsets.drop_back(1);
cuts.materialize_compressed(src_points, point_counts);
for (int &count : point_counts) {
/* Make sure the number of cuts is greater than zero and add one for the existing point. */
count = std::max(count, 0) + 1;
if (src_points.size() == 1) {
point_counts.first() = 1;
}
if (!cyclic[curve_i]) {
/* The last point only has a segment to be subdivided if the curve isn't cyclic. */
point_counts.last() = 1;
else {
cuts.materialize_compressed(src_points, point_counts);
for (int &count : point_counts) {
/* Make sure there at least one cut, and add one for the existing point. */
count = std::max(count, 0) + 1;
}
if (!cyclic[curve_i]) {
/* The last point only has a segment to be subdivided if the curve isn't cyclic. */
point_counts.last() = 1;
}
}
bke::curves::accumulate_counts_to_offsets(point_offsets);

View File

@ -3927,9 +3927,8 @@ void GEO_uv_parametrizer_face_add(ParamHandle *phandle,
const bool *pin,
const bool *select)
{
param_assert(phash_lookup(phandle->hash_faces, key) == nullptr);
BLI_assert(nverts >= 3);
param_assert(phandle->state == PHANDLE_STATE_ALLOCATED);
param_assert(ELEM(nverts, 3, 4));
if (nverts > 4) {
/* ngon */

View File

@ -43,6 +43,8 @@ void GPU_uniformbuf_bind_as_ssbo(GPUUniformBuf *ubo, int slot);
void GPU_uniformbuf_unbind(GPUUniformBuf *ubo);
void GPU_uniformbuf_unbind_all(void);
void GPU_uniformbuf_clear_to_zero(GPUUniformBuf *ubo);
#define GPU_UBO_BLOCK_NAME "node_tree"
#define GPU_ATTRIBUTE_UBO_BLOCK_NAME "unf_attrs"
#define GPU_LAYER_ATTRIBUTE_UBO_BLOCK_NAME "drw_layer_attrs"

View File

@ -233,4 +233,9 @@ void GPU_uniformbuf_unbind_all()
/* FIXME */
}
void GPU_uniformbuf_clear_to_zero(GPUUniformBuf *ubo)
{
unwrap(ubo)->clear_to_zero();
}
/** \} */

View File

@ -38,6 +38,7 @@ class UniformBuf {
virtual ~UniformBuf();
virtual void update(const void *data) = 0;
virtual void clear_to_zero() = 0;
virtual void bind(int slot) = 0;
virtual void bind_as_ssbo(int slot) = 0;
virtual void unbind() = 0;

View File

@ -81,6 +81,14 @@ void MTLUniformBuf::update(const void *data)
}
}
void MTLUniformBuf::clear_to_zero()
{
/* TODO(fclem): Avoid another allocation and just do the clear on the GPU if possible. */
void *clear_data = calloc(size_in_bytes_);
this->update(clear_data);
free(clear_data);
}
void MTLUniformBuf::bind(int slot)
{
if (slot < 0) {

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