diff --git a/intern/ghost/GHOST_C-api.h b/intern/ghost/GHOST_C-api.h index 8d09d337e02..951ca6999f5 100644 --- a/intern/ghost/GHOST_C-api.h +++ b/intern/ghost/GHOST_C-api.h @@ -343,6 +343,18 @@ extern GHOST_TSuccess GHOST_SetCustomCursorShape(GHOST_WindowHandle windowhandle const int size[2], const int hot_spot[2], bool canInvertColor); +/** + * Set a cursor "generator", allowing the GHOST back-end to dynamically + * generate cursors at different sizes as needed, depending on the monitor DPI. + * + * \param cursor_generator: An object which generates cursors. + * Ownership is transferred to GHOST which is responsible for calling it's free method. + * + * The capability flag: #GHOST_kCapabilityCursorGenerator should be checked, + * otherwise this call is a no-op. + */ +extern GHOST_TSuccess GHOST_SetCustomCursorGenerator(GHOST_WindowHandle windowhandle, + GHOST_CursorGenerator *cursor_generator); extern GHOST_TSuccess GHOST_GetCursorBitmap(GHOST_WindowHandle windowhandle, GHOST_CursorBitmapRef *bitmap); diff --git a/intern/ghost/GHOST_IWindow.hh b/intern/ghost/GHOST_IWindow.hh index b0e78134488..7be92220fbf 100644 --- a/intern/ghost/GHOST_IWindow.hh +++ b/intern/ghost/GHOST_IWindow.hh @@ -332,6 +332,14 @@ class GHOST_IWindow { const int hot_spot[2], bool canInvertColor) = 0; + /** + * Set the cursor generator. + * + * \param cursor_generator: An object which generates cursors. + * Ownership is transferred to GHOST which is responsible for calling it's free method. + */ + virtual GHOST_TSuccess setCustomCursorGenerator(GHOST_CursorGenerator *cursor_generator) = 0; + virtual GHOST_TSuccess getCursorBitmap(GHOST_CursorBitmapRef *bitmap) = 0; /** diff --git a/intern/ghost/GHOST_Types.h b/intern/ghost/GHOST_Types.h index 7040bb7ded3..1ea35d6fced 100644 --- a/intern/ghost/GHOST_Types.h +++ b/intern/ghost/GHOST_Types.h @@ -69,6 +69,45 @@ typedef struct { int hot_spot[2]; } GHOST_CursorBitmapRef; +/** + * Pass this as an argument to GHOST so each ghost back-end + * can generate cursors on demand. + */ +typedef struct GHOST_CursorGenerator { + /** + * The main cursor generation callback. + * + * \note only supports RGBA cursors. + * + * \param cursor_generator: Pass in to allow accessing the user argument. + * \param cursor_size: The cursor size to generate. + * \param cursor_size_max: The maximum dimension (width or height). + * \param r_bitmap_size: The bitmap width & height in pixels. + * The generator must guarantee the resulting size (dimensions written to `r_bitmap_size`) + * never exceeds `cursor_size_max`. + * \param r_hot_spot: The cursor hot-spot. + * \return the bitmap data or null if it could not be generated. + * Must be at least: `sizeof(uint8_t[4]) * r_bitmap_size[0] * r_bitmap_size[1]` allocated bytes. + */ + uint8_t *(*generate_fn)(const struct GHOST_CursorGenerator *cursor_generator, + int cursor_size, + int cursor_size_max, + uint8_t *(*alloc_fn)(size_t size), + int r_bitmap_size[2], + int r_hot_spot[2]); + /** + * Called once GHOST has finished with this object, + * Typically this would free `user_data`. + */ + void (*free_fn)(struct GHOST_CursorGenerator *cursor_generator); + /** + * Implementation specific data used for rasterization + * (could contain SVG data for example). + */ + GHOST_TUserDataPtr user_data; + +} GHOST_CursorGenerator; + typedef enum { GHOST_gpuStereoVisual = (1 << 0), GHOST_gpuDebugContext = (1 << 1), @@ -133,6 +172,10 @@ typedef enum { * to be temporary as our intention is to implement on all platforms. */ GHOST_kCapabilityCursorRGBA = (1 << 10), + /** + * Setting cursors via #GHOST_SetCursorGenerator is supported. + */ + GHOST_kCapabilityCursorGenerator = (1 << 11), } GHOST_TCapabilityFlag; @@ -145,7 +188,8 @@ typedef enum { GHOST_kCapabilityClipboardPrimary | GHOST_kCapabilityGPUReadFrontBuffer | \ GHOST_kCapabilityClipboardImage | GHOST_kCapabilityDesktopSample | GHOST_kCapabilityInputIME | \ GHOST_kCapabilityTrackpadPhysicalDirection | GHOST_kCapabilityWindowDecorationStyles | \ - GHOST_kCapabilityKeyboardHyperKey | GHOST_kCapabilityCursorRGBA) + GHOST_kCapabilityKeyboardHyperKey | GHOST_kCapabilityCursorRGBA | \ + GHOST_kCapabilityCursorGenerator) /* Xtilt and Ytilt represent how much the pen is tilted away from * vertically upright in either the X or Y direction, with X and Y the diff --git a/intern/ghost/intern/GHOST_C-api.cc b/intern/ghost/intern/GHOST_C-api.cc index e76703e5e9c..d5da44f4eba 100644 --- a/intern/ghost/intern/GHOST_C-api.cc +++ b/intern/ghost/intern/GHOST_C-api.cc @@ -305,6 +305,13 @@ GHOST_TSuccess GHOST_SetCustomCursorShape(GHOST_WindowHandle windowhandle, return window->setCustomCursorShape(bitmap, mask, size, hot_spot, canInvertColor); } +GHOST_TSuccess GHOST_SetCustomCursorGenerator(GHOST_WindowHandle windowhandle, + GHOST_CursorGenerator *cursor_generator) +{ + GHOST_IWindow *window = (GHOST_IWindow *)windowhandle; + return window->setCustomCursorGenerator(cursor_generator); +} + GHOST_TSuccess GHOST_GetCursorBitmap(GHOST_WindowHandle windowhandle, GHOST_CursorBitmapRef *bitmap) { diff --git a/intern/ghost/intern/GHOST_SystemCocoa.mm b/intern/ghost/intern/GHOST_SystemCocoa.mm index 440830a355b..ea4e9d649ac 100644 --- a/intern/ghost/intern/GHOST_SystemCocoa.mm +++ b/intern/ghost/intern/GHOST_SystemCocoa.mm @@ -979,7 +979,9 @@ GHOST_TCapabilityFlag GHOST_SystemCocoa::getCapabilities() const * it's possible another modifier could be optionally used in it's place. */ GHOST_kCapabilityKeyboardHyperKey | /* No support yet for RGBA mouse cursors. */ - GHOST_kCapabilityCursorRGBA)); + GHOST_kCapabilityCursorRGBA | + /* No support yet for dynamic cursor generation. */ + GHOST_kCapabilityCursorGenerator)); } /* -------------------------------------------------------------------- diff --git a/intern/ghost/intern/GHOST_SystemHeadless.hh b/intern/ghost/intern/GHOST_SystemHeadless.hh index b44f1252363..0662191097f 100644 --- a/intern/ghost/intern/GHOST_SystemHeadless.hh +++ b/intern/ghost/intern/GHOST_SystemHeadless.hh @@ -74,7 +74,9 @@ class GHOST_SystemHeadless : public GHOST_System { /* Wrap. */ GHOST_kCapabilityKeyboardHyperKey | /* Wrap. */ - GHOST_kCapabilityCursorRGBA) + GHOST_kCapabilityCursorRGBA | + /* Wrap. */ + GHOST_kCapabilityCursorGenerator) ); } diff --git a/intern/ghost/intern/GHOST_SystemSDL.cc b/intern/ghost/intern/GHOST_SystemSDL.cc index 677657d787c..3a8683cad1e 100644 --- a/intern/ghost/intern/GHOST_SystemSDL.cc +++ b/intern/ghost/intern/GHOST_SystemSDL.cc @@ -799,7 +799,9 @@ GHOST_TCapabilityFlag GHOST_SystemSDL::getCapabilities() const /* No support for a Hyper modifier key. */ GHOST_kCapabilityKeyboardHyperKey | /* No support yet for RGBA mouse cursors. */ - GHOST_kCapabilityCursorRGBA)); + GHOST_kCapabilityCursorRGBA | + /* No support yet for dynamic cursor generation. */ + GHOST_kCapabilityCursorGenerator)); } char *GHOST_SystemSDL::getClipboard(bool /*selection*/) const diff --git a/intern/ghost/intern/GHOST_SystemWayland.cc b/intern/ghost/intern/GHOST_SystemWayland.cc index 691a47c575c..bef9b97c6f3 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cc +++ b/intern/ghost/intern/GHOST_SystemWayland.cc @@ -471,13 +471,16 @@ struct GWL_Cursor { void *custom_data = nullptr; /** The size of `custom_data` in bytes. */ size_t custom_data_size = 0; + + /** The current displayed size, use to check if the cursor need to be re-generated. */ + int custom_cursor_scale = 0; + /** - * The size of the cursor (when looking up a cursor theme). - * This must be scaled by the maximum output scale when passing to wl_cursor_theme_load. + * The size of the cursor in logical pixels. + * This must be scaled by the maximum output scale when calculating the physical pixels. * See #update_cursor_scale. */ int theme_size = 0; - int custom_scale = 1; }; /** \} */ @@ -705,7 +708,7 @@ struct GWL_SeatStatePointer { /** Outputs on which the cursor is visible. */ std::unordered_set outputs; - int theme_scale = 1; + int buffer_scale = 1; /** * The serial of the last used pointer or tablet. @@ -1905,6 +1908,11 @@ static void gwl_registry_entry_update_all(GWL_Display *display, const int interf /** \name Private Utility Functions * \{ */ +static uint32_t round_up_uint(const uint32_t x, const uint32_t multiple) +{ + return ((x + multiple - 1) / multiple) * multiple; +} + #ifdef WITH_GHOST_WAYLAND_LIBDECOR static const char *strchr_or_end(const char *str, const char ch) { @@ -2693,8 +2701,6 @@ static void cursor_buffer_set_surface_impl(const wl_cursor_image *wl_image, const int32_t image_size_y = int32_t(wl_image->height); GHOST_ASSERT((image_size_x % scale) == 0 && (image_size_y % scale) == 0, "The size must be a multiple of the scale!"); - (void)image_size_x; - (void)image_size_y; wl_surface_set_buffer_scale(wl_surface, scale); wl_surface_attach(wl_surface, buffer, 0, 0); @@ -2831,6 +2837,24 @@ static std::optional gwl_seat_cursor_find_wl_sh return std::nullopt; } +/** + * Currently account for all cursors as the buffer is shared. + * + * Note that a per pointing device buffer would be better, + * this is more of a low priority to-do. + */ +static int gwl_seat_cursor_buffer_scale_calc(const GWL_Seat *seat) +{ + int scale = 1; + if (seat->wl.pointer) { + scale = std::max(scale, seat->pointer.buffer_scale); + } + if (seat->wp.tablet_seat) { + scale = std::max(scale, seat->tablet.buffer_scale); + } + return scale; +} + /** * Show the buffer defined by #gwl_seat_cursor_buffer_set without changing anything else, * so #gwl_seat_cursor_buffer_hide can be used to display it again. @@ -2847,8 +2871,9 @@ static void gwl_seat_cursor_buffer_show(GWL_Seat *seat) seat->cursor.shape.device, seat->pointer.serial, seat->cursor.shape.enum_id); } else { - /* TODO: support scale for custom cursors. */ - const int scale = 1; + /* TODO: use `seat->pointer.buffer_scale`, give each device it's own buffer. */ + const int scale = gwl_seat_cursor_buffer_scale_calc(seat); + const int32_t hotspot_x = int32_t(cursor->wl.image.hotspot_x) / scale; const int32_t hotspot_y = int32_t(cursor->wl.image.hotspot_y) / scale; wl_pointer_set_cursor( @@ -2857,8 +2882,8 @@ static void gwl_seat_cursor_buffer_show(GWL_Seat *seat) } if (!seat->wp.tablet_tools.empty()) { - /* TODO: support scale for custom cursors. */ - const int scale = 1; + /* TODO: use `seat->tablet.buffer_scale`, give each device it's own buffer. */ + const int scale = gwl_seat_cursor_buffer_scale_calc(seat); const int32_t hotspot_x = int32_t(cursor->wl.image.hotspot_x) / scale; const int32_t hotspot_y = int32_t(cursor->wl.image.hotspot_y) / scale; for (zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : seat->wp.tablet_tools) { @@ -2905,8 +2930,7 @@ static void gwl_seat_cursor_buffer_set(const GWL_Seat *seat, const GWL_Cursor *cursor = &seat->cursor; const bool visible = (cursor->visible && cursor->is_hardware); - /* TODO: support scale for custom cursors. */ - const int scale = 1; + const int scale = gwl_seat_cursor_buffer_scale_calc(seat); /* This is a requirement of WAYLAND, when this isn't the case, * it causes Blender's window to close intermittently. */ @@ -3688,58 +3712,36 @@ static const wl_buffer_listener cursor_buffer_listener = { static CLG_LogRef LOG_WL_CURSOR_SURFACE = {"ghost.wl.handle.cursor_surface"}; #define LOG (&LOG_WL_CURSOR_SURFACE) -static bool update_cursor_scale(GWL_Cursor &cursor, - wl_shm *shm, - GWL_SeatStatePointer *seat_state_pointer, - wl_surface *wl_surface_cursor) +static bool update_cursor_scale(GWL_Seat *seat, + GWL_Cursor &cursor, + GWL_SeatStatePointer *seat_state_pointer) { - /* TODO: do cursor scaling correctly. */ - (void)cursor; - (void)shm; - (void)seat_state_pointer; - (void)wl_surface_cursor; -#if 0 + /* NOTE: currently there is no special handling for fractional scaling. + * This could be supported however the default behavior of generating a cursor + * rounded up to the nearest integer scaling works fairly well. */ int scale = 0; for (const GWL_Output *output : seat_state_pointer->outputs) { - int output_scale_floor = output->scale; - - /* It's important to round down in the case of fractional scale, - * otherwise the cursor can be scaled down to be unusably small. - * This is especially a problem when: - * - The cursor theme has one size (24px for the default cursor). - * - The fractional scaling is set just above 1 (typically 125%). - * - * In this case the `output->scale` is rounded up to 2 and a larger cursor is requested. - * It's assumed a large cursor is available but that's not always the case. - * When only a smaller cursor is available it's still assumed to be large, - * fractional scaling causes the cursor to be scaled down making it ~10px. see #105895. */ - if (output_scale_floor > 1 && output->has_scale_fractional) { - output_scale_floor = std::max(1, output->scale_fractional / FRACTIONAL_DENOMINATOR); - } - - scale = std::max(output_scale_floor, scale); + scale = std::max(scale, output->scale); + } + if (scale > 0 && seat_state_pointer->buffer_scale != scale) { + seat_state_pointer->buffer_scale = scale; } - if (scale > 0 && seat_state_pointer->theme_scale != scale) { - seat_state_pointer->theme_scale = scale; - if (!cursor.is_custom) { - if (wl_surface_cursor) { - wl_surface_set_buffer_scale(wl_surface_cursor, scale); + scale = gwl_seat_cursor_buffer_scale_calc(seat); + + if (scale > 0 && cursor.custom_cursor_scale != scale) { + cursor.custom_cursor_scale = scale; + if (cursor.is_custom) { + wl_surface *wl_surface_focus = seat_state_pointer->wl.surface_window; + if (wl_surface_focus) { + GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus); + if (win) { + win->cursor_shape_refresh(); + } } } - wl_cursor_theme_destroy(cursor.wl.theme); - cursor.wl.theme = wl_cursor_theme_load( - (cursor.theme_name.empty() ? nullptr : cursor.theme_name.c_str()), - scale * cursor.theme_size, - shm); - if (cursor.wl.theme_cursor) { - cursor.wl.theme_cursor = wl_cursor_theme_get_cursor(cursor.wl.theme, - cursor.wl.theme_cursor_name); - } - return true; } -#endif return false; } @@ -3756,7 +3758,7 @@ static void cursor_surface_handle_enter(void *data, wl_surface *wl_surface, wl_o seat, wl_surface); const GWL_Output *reg_output = ghost_wl_output_user_data(wl_output); seat_state_pointer->outputs.insert(reg_output); - update_cursor_scale(seat->cursor, seat->system->wl_shm_get(), seat_state_pointer, wl_surface); + update_cursor_scale(seat, seat->cursor, seat_state_pointer); } static void cursor_surface_handle_leave(void *data, wl_surface *wl_surface, wl_output *wl_output) @@ -3772,7 +3774,7 @@ static void cursor_surface_handle_leave(void *data, wl_surface *wl_surface, wl_o seat, wl_surface); const GWL_Output *reg_output = ghost_wl_output_user_data(wl_output); seat_state_pointer->outputs.erase(reg_output); - update_cursor_scale(seat->cursor, seat->system->wl_shm_get(), seat_state_pointer, wl_surface); + update_cursor_scale(seat, seat->cursor, seat_state_pointer); } static void cursor_surface_handle_preferred_buffer_scale(void * /*data*/, @@ -8614,11 +8616,89 @@ GHOST_TSuccess GHOST_SystemWayland::cursor_shape_check(const GHOST_TStandardCurs return GHOST_kSuccess; } -GHOST_TSuccess GHOST_SystemWayland::cursor_shape_custom_set(const uint8_t *bitmap, - const uint8_t *mask, - const int size[2], - const int hot_spot[2], - const bool /*canInvertColor*/) +static wl_buffer *ghost_wl_buffer_from_cursor_generator(const GHOST_CursorGenerator &cg, + wl_shm *shm, + void **buffer_data_p, + size_t *buffer_data_size_p, + const int cursor_size, + const int cursor_size_max, + const int scale, + int r_bitmap_size[2], + int r_hot_spot[2]) +{ + if (*buffer_data_p) { + munmap(*buffer_data_p, *buffer_data_size_p); + *buffer_data_p = nullptr; + *buffer_data_size_p = 0; /* Not needed, but the value is no longer meaningful. */ + } + + int bitmap_size_src[2]; + int hot_spot[2]; + + uint8_t *bitmap_src = cg.generate_fn( + &cg, + cursor_size, + cursor_size_max, + [](size_t size) -> uint8_t * { return new uint8_t[size]; }, + bitmap_size_src, + hot_spot); + + if (bitmap_src == nullptr) { + return nullptr; + } + + /* There is no need to adjust the hot-spot when resizing. */ + int bitmap_size_dst[2] = { + int(round_up_uint(bitmap_size_src[0], scale)), + int(round_up_uint(bitmap_size_src[1], scale)), + }; + + wl_buffer *buffer = ghost_wl_buffer_create_for_image( + shm, bitmap_size_dst, WL_SHM_FORMAT_ARGB8888, buffer_data_p, buffer_data_size_p); + + if (buffer != nullptr) { + const bool is_trivial_copy = (bitmap_size_src[0] == bitmap_size_dst[0]) && + (bitmap_size_src[1] == bitmap_size_dst[1]); + /* NOTE: the copy could be skipped in trivial cases. + * Since it's such a small amount of data it hardly seems worth it. */ + if (is_trivial_copy) { + /* RGBA color, direct copy. */ + const uint32_t *px_src = reinterpret_cast(bitmap_src); + uint32_t *px_dst = static_cast(*buffer_data_p); + for (int y = 0; y < bitmap_size_src[1]; y++) { + for (int x = 0; x < bitmap_size_src[0]; x++) { + *px_dst++ = *px_src++; + } + } + } + else { + /* RGBA color, copy into an expanded buffer. */ + const uint32_t *px_src = reinterpret_cast(bitmap_src); + uint32_t *px_dst = static_cast(*buffer_data_p); + for (int y = 0; y < bitmap_size_dst[1]; y++) { + for (int x = 0; x < bitmap_size_dst[0]; x++) { + if (x >= bitmap_size_src[0] || y >= bitmap_size_src[1]) { + *px_dst++ = 0x0; + } + else { + *px_dst++ = *px_src++; + } + } + } + } + + r_bitmap_size[0] = bitmap_size_dst[0]; + r_bitmap_size[1] = bitmap_size_dst[1]; + + r_hot_spot[0] = hot_spot[0]; + r_hot_spot[1] = hot_spot[1]; + } + + delete[] bitmap_src; + return buffer; +} + +GHOST_TSuccess GHOST_SystemWayland::cursor_shape_custom_set(const GHOST_CursorGenerator &cg) { /* Caller needs to lock `server_mutex`. */ GWL_Seat *seat = gwl_display_seat_active_get(display_); @@ -8643,115 +8723,44 @@ GHOST_TSuccess GHOST_SystemWayland::cursor_shape_custom_set(const uint8_t *bitma } } - GWL_Cursor *cursor = &seat->cursor; - if (cursor->custom_data) { - munmap(cursor->custom_data, cursor->custom_data_size); - cursor->custom_data = nullptr; - cursor->custom_data_size = 0; /* Not needed, but the value is no longer meaningful. */ - } + /* Generate the buffer. */ + GWL_Cursor &cursor = seat->cursor; - wl_buffer *buffer = ghost_wl_buffer_create_for_image(display_->wl.shm, - size, - WL_SHM_FORMAT_ARGB8888, - &cursor->custom_data, - &cursor->custom_data_size); - if (buffer == nullptr) { + int bitmap_size[2] = {0, 0}; + int hot_spot[2] = {0, 0}; + + const int scale = gwl_seat_cursor_buffer_scale_calc(seat); + const int cursor_size = seat->cursor.theme_size * scale; + /* NOTE: Regarding the maximum cursor size. + * - 256 seems to be a hardware limit. + * - Twice the cursor size to allow "text" cursor to display wider than a typical cursor + * without being *very* large - as that looks strange. + */ + const int cursor_size_max = std::min(256, cursor_size * 2); + + wl_buffer *buffer = ghost_wl_buffer_from_cursor_generator(cg, + display_->wl.shm, + &cursor.custom_data, + &cursor.custom_data_size, + cursor_size, + cursor_size_max, + scale, + bitmap_size, + hot_spot); + if (UNLIKELY(buffer == nullptr)) { return GHOST_kFailure; } - wl_buffer_add_listener(buffer, &cursor_buffer_listener, cursor); + wl_buffer_add_listener(buffer, &cursor_buffer_listener, &cursor); - if (mask) { - /* Monochrome & mask (expand to RGBA). */ - static constexpr uint32_t black = 0xFF000000; - static constexpr uint32_t white = 0xFFFFFFFF; - static constexpr uint32_t transparent = 0x00000000; + cursor.visible = true; + cursor.is_custom = true; - uint8_t datab = 0, maskb = 0; - uint32_t *px_dst = static_cast(cursor->custom_data); - - for (int y = 0; y < size[1]; y++) { - for (int x = 0; x < size[0]; x++) { - if ((x % 8) == 0) { - datab = *bitmap++; - maskb = *mask++; - - /* Reverse bit order. */ - datab = uint8_t((datab * 0x0202020202ULL & 0x010884422010ULL) % 1023); - maskb = uint8_t((maskb * 0x0202020202ULL & 0x010884422010ULL) % 1023); - } - - if (maskb & 0x80) { - *px_dst++ = (datab & 0x80) ? white : black; - } - else { - *px_dst++ = (datab & 0x80) ? white : transparent; - } - datab <<= 1; - maskb <<= 1; - } - } - } - else { - /* RGBA color (direct copy). */ - const uint32_t *px_src = reinterpret_cast(bitmap); - uint32_t *px_dst = static_cast(cursor->custom_data); - for (int y = 0; y < size[1]; y++) { - for (int x = 0; x < size[0]; x++) { - *px_dst++ = *px_src++; - } - } - } - - cursor->visible = true; - cursor->is_custom = true; - - /* Calculate the cursor size to use based on the theme setting. */ - { - - /* WARNING: Weak logic, if we can't use vector cursors - ideally the custom cursor - * function would receive multiple sizes which WAYLAND could then switch between - * as it does with themes. The following logic is fairly weak but works perfectly - * when all outputs have the same scale. - * - * There is nothing preventing multiple sized cursors from being passed in, - * it's just a matter of refactoring and adding support to WAYLAND. */ - - /* Get the lowest scale so in the case of mixed-scale-outputs, - * the cursor will be too big on some of the outputs instead of too small. - * - * Note that getting the min/max scale for all outputs be made into an function - * however it's bad practice because it means the cursor size will be wrong - * when there are multiple outputs with different scale. - * So this is not something to encouraged. */ - int output_scale = -1; - for (const GWL_Output *output : display_->outputs) { - output_scale = (output_scale == -1) ? output->scale : std::min(output_scale, output->scale); - } - if (output_scale == -1) { - output_scale = 1; - } - - const int custom_size = std::max(size[0], size[1]); - const int target_size = seat->cursor.theme_size * output_scale; - - cursor->custom_scale = std::max(1, (output_scale * custom_size) / target_size); - /* It would make more sense to adjust the buffer size instead of the scale. - * In practice with custom cursors of 16x16, 24x24 & 32x32 its only likely to cause - * problems with odd-scaling (HI-DPI scale of 300% or 500% for example). - * In these cases the custom cursor will be a little too large. */ - while ((cursor->custom_scale > 1) && - !((size[0] % cursor->custom_scale) == 0 && (size[1] % cursor->custom_scale) == 0)) - { - cursor->custom_scale -= 1; - } - } - - cursor->wl.buffer = buffer; - cursor->wl.image.width = uint32_t(size[0]); - cursor->wl.image.height = uint32_t(size[1]); - cursor->wl.image.hotspot_x = uint32_t(hot_spot[0]); - cursor->wl.image.hotspot_y = uint32_t(hot_spot[1]); + cursor.wl.buffer = buffer; + cursor.wl.image.width = uint32_t(bitmap_size[0]); + cursor.wl.image.height = uint32_t(bitmap_size[1]); + cursor.wl.image.hotspot_x = uint32_t(hot_spot[0]); + cursor.wl.image.hotspot_y = uint32_t(hot_spot[1]); gwl_seat_cursor_buffer_set_current(seat); @@ -9372,21 +9381,11 @@ void GHOST_SystemWayland::output_scale_update(GWL_Output *output) } for (GWL_Seat *seat : display_->seats) { if (seat->pointer.outputs.count(output)) { - update_cursor_scale(seat->cursor, - seat->system->wl_shm_get(), - &seat->pointer, - seat->cursor.wl.surface_cursor); + update_cursor_scale(seat, seat->cursor, &seat->pointer); } if (seat->tablet.outputs.count(output)) { - for (zwp_tablet_tool_v2 *zwp_tablet_tool_v2 : seat->wp.tablet_tools) { - GWL_TabletTool *tablet_tool = static_cast( - zwp_tablet_tool_v2_get_user_data(zwp_tablet_tool_v2)); - update_cursor_scale(seat->cursor, - seat->system->wl_shm_get(), - &seat->tablet, - tablet_tool->wl.surface_cursor); - } + update_cursor_scale(seat, seat->cursor, &seat->tablet); } } } diff --git a/intern/ghost/intern/GHOST_SystemWayland.hh b/intern/ghost/intern/GHOST_SystemWayland.hh index e1bba3a8c50..ce2cba7e3d4 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.hh +++ b/intern/ghost/intern/GHOST_SystemWayland.hh @@ -233,11 +233,7 @@ class GHOST_SystemWayland : public GHOST_System { GHOST_TSuccess cursor_shape_check(GHOST_TStandardCursor cursorShape); - GHOST_TSuccess cursor_shape_custom_set(const uint8_t *bitmap, - const uint8_t *mask, - const int size[2], - const int hot_spot[2], - bool canInvertColor); + GHOST_TSuccess cursor_shape_custom_set(const GHOST_CursorGenerator &cg); GHOST_TSuccess cursor_bitmap_get(GHOST_CursorBitmapRef *bitmap); diff --git a/intern/ghost/intern/GHOST_SystemWin32.cc b/intern/ghost/intern/GHOST_SystemWin32.cc index e03bd12d540..41ff8309ac4 100644 --- a/intern/ghost/intern/GHOST_SystemWin32.cc +++ b/intern/ghost/intern/GHOST_SystemWin32.cc @@ -616,7 +616,9 @@ GHOST_TCapabilityFlag GHOST_SystemWin32::getCapabilities() const GHOST_kCapabilityClipboardPrimary | /* WIN32 doesn't define a Hyper modifier key, * it's possible another modifier could be optionally used in it's place. */ - GHOST_kCapabilityKeyboardHyperKey)); + GHOST_kCapabilityKeyboardHyperKey | + /* No support yet for cursors generated on demand. */ + GHOST_kCapabilityCursorGenerator)); } GHOST_TSuccess GHOST_SystemWin32::init() diff --git a/intern/ghost/intern/GHOST_SystemX11.cc b/intern/ghost/intern/GHOST_SystemX11.cc index aa6ec7f83e8..e571592a0c2 100644 --- a/intern/ghost/intern/GHOST_SystemX11.cc +++ b/intern/ghost/intern/GHOST_SystemX11.cc @@ -1829,7 +1829,9 @@ GHOST_TCapabilityFlag GHOST_SystemX11::getCapabilities() const /* No support for window decoration styles. */ GHOST_kCapabilityWindowDecorationStyles | /* No support yet for RGBA mouse cursors. */ - GHOST_kCapabilityCursorRGBA)); + GHOST_kCapabilityCursorRGBA | + /* No support yet for dynamic cursor generation. */ + GHOST_kCapabilityCursorGenerator)); } void GHOST_SystemX11::addDirtyWindow(GHOST_WindowX11 *bad_wind) diff --git a/intern/ghost/intern/GHOST_Window.cc b/intern/ghost/intern/GHOST_Window.cc index 6c3ac75d90a..50113d3f70c 100644 --- a/intern/ghost/intern/GHOST_Window.cc +++ b/intern/ghost/intern/GHOST_Window.cc @@ -247,6 +247,15 @@ GHOST_TSuccess GHOST_Window::setCustomCursorShape(const uint8_t *bitmap, return GHOST_kFailure; } +GHOST_TSuccess GHOST_Window::setCustomCursorGenerator(GHOST_CursorGenerator *cursor_generator) +{ + if (setWindowCustomCursorGenerator(cursor_generator)) { + m_cursorShape = GHOST_kStandardCursorCustom; + return GHOST_kSuccess; + } + return GHOST_kFailure; +} + GHOST_TSuccess GHOST_Window::getCursorBitmap(GHOST_CursorBitmapRef * /*bitmap*/) { /* Sub-classes may override. */ diff --git a/intern/ghost/intern/GHOST_Window.hh b/intern/ghost/intern/GHOST_Window.hh index 28147784c27..c1736dbb26f 100644 --- a/intern/ghost/intern/GHOST_Window.hh +++ b/intern/ghost/intern/GHOST_Window.hh @@ -119,6 +119,9 @@ class GHOST_Window : public GHOST_IWindow { const int hot_spot[2], bool canInvertColor) override; + /** \copydoc #GHOST_IWindow::setCustomCursorGenerator */ + GHOST_TSuccess setCustomCursorGenerator(GHOST_CursorGenerator *cursor_generator) override; + GHOST_TSuccess getCursorBitmap(GHOST_CursorBitmapRef *bitmap) override; /** \copydoc #GHOST_IWindow::getCursorVisibility */ @@ -292,6 +295,12 @@ class GHOST_Window : public GHOST_IWindow { const int size[2], const int hot_size[2], bool canInvertColor) = 0; + /** \copydoc #GHOST_IWindow::setWindowCustomCursorGenerator */ + virtual GHOST_TSuccess setWindowCustomCursorGenerator(GHOST_CursorGenerator *cursor_generator) + { + cursor_generator->free_fn(cursor_generator); + return GHOST_kFailure; + }; GHOST_TSuccess releaseNativeHandles(); diff --git a/intern/ghost/intern/GHOST_WindowWayland.cc b/intern/ghost/intern/GHOST_WindowWayland.cc index 5474392a142..efc8bce3a8a 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.cc +++ b/intern/ghost/intern/GHOST_WindowWayland.cc @@ -281,79 +281,28 @@ int gwl_window_scale_int_from(const GWL_WindowScaleParams &scale_params, int val /** \name Internal #GWL_WindowCursorCustomShape * \{ */ -struct GWL_WindowCursorCustomShape { - uint8_t *bitmap = nullptr; - uint8_t *mask = nullptr; - int32_t hot_spot[2] = {0, 0}; - int32_t size[2] = {0, 0}; - bool can_invert_color = false; -}; - -static void gwl_window_cursor_custom_free(GWL_WindowCursorCustomShape &ccs) +static void gwl_window_cursor_custom_free(GHOST_CursorGenerator *cg) { - if (ccs.bitmap) { - free(ccs.bitmap); - } - if (ccs.mask) { - free(ccs.mask); - } + cg->free_fn(cg); } -static void gwl_window_cursor_custom_clear(GWL_WindowCursorCustomShape &ccs) +static void gwl_window_cursor_custom_clear(GHOST_CursorGenerator **cg) { - gwl_window_cursor_custom_free(ccs); - ccs = GWL_WindowCursorCustomShape{}; + if (*cg == nullptr) { + return; + } + gwl_window_cursor_custom_free(*cg); + *cg = nullptr; } -static void gwl_window_cursor_custom_store(GWL_WindowCursorCustomShape &ccs, - const uint8_t *bitmap, - const uint8_t *mask, - const int32_t size[2], - const int32_t hot_spot[2], - bool can_invert_color) -{ - gwl_window_cursor_custom_clear(ccs); - - if (mask) { - /* Monochrome bitmap (with mask). */ - /* The width is divided by 8, rounding up. */ - const size_t bitmap_size = sizeof(uint8_t) * ((size[0] + 7) / 8) * size[1]; - - if (bitmap) { - ccs.bitmap = static_cast(malloc(bitmap_size)); - memcpy(ccs.bitmap, bitmap, bitmap_size); - } - ccs.mask = static_cast(malloc(bitmap_size)); - memcpy(ccs.mask, mask, bitmap_size); - } - else { - /* RGBA bitmap (mask is alpha). */ - const size_t bitmap_size = sizeof(uint32_t) * size[0] * size[1]; - if (bitmap) { - ccs.bitmap = static_cast(malloc(bitmap_size)); - memcpy(ccs.bitmap, bitmap, bitmap_size); - } - ccs.mask = nullptr; - } - - ccs.size[0] = size[0]; - ccs.size[1] = size[1]; - - ccs.hot_spot[0] = hot_spot[0]; - ccs.hot_spot[1] = hot_spot[1]; - - ccs.can_invert_color = can_invert_color; -} - -static GHOST_TSuccess gwl_window_cursor_custom_load(const GWL_WindowCursorCustomShape &ccs, +static GHOST_TSuccess gwl_window_cursor_custom_load(const GHOST_CursorGenerator &cg, GHOST_SystemWayland *system) { - return system->cursor_shape_custom_set( - ccs.bitmap, ccs.mask, ccs.size, ccs.hot_spot, ccs.can_invert_color); + return system->cursor_shape_custom_set(cg); } static GHOST_TSuccess gwl_window_cursor_shape_refresh(GHOST_TStandardCursor shape, - const GWL_WindowCursorCustomShape &ccs, + const GHOST_CursorGenerator *cg, GHOST_SystemWayland *system) { #ifdef USE_EVENT_BACKGROUND_THREAD @@ -361,7 +310,7 @@ static GHOST_TSuccess gwl_window_cursor_shape_refresh(GHOST_TStandardCursor shap #endif if (shape == GHOST_kStandardCursorCustom) { - const GHOST_TSuccess ok = gwl_window_cursor_custom_load(ccs, system); + const GHOST_TSuccess ok = gwl_window_cursor_custom_load(*cg, system); if (ok == GHOST_kSuccess) { return ok; } @@ -510,7 +459,7 @@ struct GWL_Window { std::mutex frame_pending_mutex; #endif - GWL_WindowCursorCustomShape cursor_custom_shape; + GHOST_CursorGenerator *cursor_generator = nullptr; std::string title; @@ -989,7 +938,7 @@ static void gwl_window_pending_actions_handle(GWL_Window *win) } if (actions[PENDING_WINDOW_CURSOR_SHAPE_REFRESH]) { gwl_window_cursor_shape_refresh( - win->ghost_window->getCursorShape(), win->cursor_custom_shape, win->ghost_system); + win->ghost_window->getCursorShape(), win->cursor_generator, win->ghost_system); } } @@ -2200,7 +2149,9 @@ GHOST_WindowWayland::~GHOST_WindowWayland() * This is not fool-proof though, hence the call to #window_surface_unref, see: #99078. */ wl_display_flush(system_->wl_display_get()); - gwl_window_cursor_custom_free(window_->cursor_custom_shape); + if (window_->cursor_generator) { + gwl_window_cursor_custom_free(window_->cursor_generator); + } delete window_; } @@ -2254,7 +2205,7 @@ GHOST_TSuccess GHOST_WindowWayland::setWindowCursorShape(GHOST_TStandardCursor s const bool is_active = this == static_cast( system_->getWindowManager()->getActiveWindow()); - gwl_window_cursor_custom_clear(window_->cursor_custom_shape); + gwl_window_cursor_custom_clear(&window_->cursor_generator); m_cursorShape = shape; GHOST_TSuccess ok; @@ -2298,46 +2249,48 @@ bool GHOST_WindowWayland::getCursorGrabUseSoftwareDisplay() return system_->cursor_grab_use_software_display_get(m_cursorGrab); } +GHOST_TSuccess GHOST_WindowWayland::setWindowCustomCursorGenerator( + GHOST_CursorGenerator *cursor_generator) +{ + /* Before this, all logic is just setting up the cursor. */ +#ifdef USE_EVENT_BACKGROUND_THREAD + std::lock_guard lock_server_guard{*system_->server_mutex}; +#endif + m_cursorShape = GHOST_kStandardCursorCustom; + if (window_->cursor_generator) { + gwl_window_cursor_custom_free(window_->cursor_generator); + } + window_->cursor_generator = cursor_generator; + + GHOST_TSuccess success = cursor_shape_refresh(); + + /* Let refresh handle applying the changes. */ + if (success == GHOST_kSuccess) { + wl_display *display = system_->wl_display_get(); + /* For the cursor to display when the event queue isn't being handled. */ + wl_display_flush(display); +#ifdef USE_CURSOR_IMMEDIATE_DISPATCH + wl_display_dispatch_pending(display); +#endif + } + return success; +} + GHOST_TSuccess GHOST_WindowWayland::setWindowCustomCursorShape(const uint8_t *bitmap, const uint8_t *mask, const int size[2], const int hot_spot[2], const bool canInvertColor) { -#ifdef USE_EVENT_BACKGROUND_THREAD - std::lock_guard lock_server_guard{*system_->server_mutex}; -#endif + /* This is no longer needed as all cursors are generated on demand. */ + GHOST_ASSERT(false, "All cursors must be generated!"); + (void)bitmap; + (void)mask; + (void)size; + (void)hot_spot; + (void)canInvertColor; - const bool is_active = this == static_cast( - system_->getWindowManager()->getActiveWindow()); - - gwl_window_cursor_custom_store( - window_->cursor_custom_shape, bitmap, mask, size, hot_spot, canInvertColor); - m_cursorShape = GHOST_kStandardCursorCustom; - - GHOST_TSuccess ok; - if (is_active) { - ok = gwl_window_cursor_custom_load(window_->cursor_custom_shape, system_); - GHOST_TSuccess ok_test = ok; - if (ok == GHOST_kFailure) { - /* Failed, try again with the default cursor. */ - m_cursorShape = GHOST_kStandardCursorDefault; - ok_test = system_->cursor_shape_set(m_cursorShape); - } - if (ok_test == GHOST_kSuccess) { - wl_display *display = system_->wl_display_get(); - /* For the cursor to display when the event queue isn't being handled. */ - wl_display_flush(display); -#ifdef USE_CURSOR_IMMEDIATE_DISPATCH - wl_display_dispatch_pending(display); -#endif - } - } - else { - /* Set later when activating the window. */ - ok = GHOST_kSuccess; - } - return ok; + return GHOST_kFailure; } GHOST_TSuccess GHOST_WindowWayland::getCursorBitmap(GHOST_CursorBitmapRef *bitmap) @@ -2714,7 +2667,7 @@ GHOST_TSuccess GHOST_WindowWayland::cursor_shape_refresh() return GHOST_kSuccess; } #endif - return gwl_window_cursor_shape_refresh(m_cursorShape, window_->cursor_custom_shape, system_); + return gwl_window_cursor_shape_refresh(m_cursorShape, window_->cursor_generator, system_); } void GHOST_WindowWayland::outputs_changed_update_scale_tag() diff --git a/intern/ghost/intern/GHOST_WindowWayland.hh b/intern/ghost/intern/GHOST_WindowWayland.hh index f85da382372..f317a981f9a 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.hh +++ b/intern/ghost/intern/GHOST_WindowWayland.hh @@ -95,6 +95,8 @@ class GHOST_WindowWayland : public GHOST_Window { GHOST_TSuccess setWindowCursorShape(GHOST_TStandardCursor shape) override; + GHOST_TSuccess setWindowCustomCursorGenerator(GHOST_CursorGenerator *cursor_generator) override; + GHOST_TSuccess setWindowCustomCursorShape(const uint8_t *bitmap, const uint8_t *mask, const int size[2], diff --git a/source/blender/windowmanager/WM_api.hh b/source/blender/windowmanager/WM_api.hh index 836b87c55f6..31b7332d572 100644 --- a/source/blender/windowmanager/WM_api.hh +++ b/source/blender/windowmanager/WM_api.hh @@ -203,6 +203,8 @@ enum eWM_CapabilitiesFlag { WM_CAPABILITY_KEYBOARD_HYPER_KEY = (1 << 9), /** Support for RGBA Cursors. */ WM_CAPABILITY_CURSOR_RGBA = (1 << 10), + /** Support on demand cursor generation. */ + WM_CAPABILITY_CURSOR_GENERATOR = (1 << 11), /** The initial value, indicates the value needs to be set by inspecting GHOST. */ WM_CAPABILITY_INITIALIZED = (1u << 31), }; diff --git a/source/blender/windowmanager/intern/wm_cursors.cc b/source/blender/windowmanager/intern/wm_cursors.cc index bd22447ab33..6d0496a1e72 100644 --- a/source/blender/windowmanager/intern/wm_cursors.cc +++ b/source/blender/windowmanager/intern/wm_cursors.cc @@ -278,6 +278,47 @@ static void cursor_rgba_to_xbm_32(const uint8_t *rgba, } } +static bool window_set_custom_cursor_generator(wmWindow *win, const BCursor &cursor) +{ + GHOST_CursorGenerator *cursor_generator = MEM_callocN(__func__); + cursor_generator->generate_fn = [](const GHOST_CursorGenerator *cursor_generator, + const int cursor_size, + const int cursor_size_max, + uint8_t *(*alloc_fn)(size_t size), + int r_bitmap_size[2], + int r_hot_spot[2]) -> uint8_t * { + const BCursor &cursor = *(const BCursor *)(cursor_generator->user_data); + /* Currently SVG uses the `cursor_size` as the maximum. */ + UNUSED_VARS(cursor_size_max); + + int bitmap_size[2]; + uint8_t *bitmap_rgba = cursor_bitmap_from_svg( + cursor.svg_source, cursor_size, alloc_fn, bitmap_size); + + if (UNLIKELY(bitmap_rgba == nullptr)) { + return nullptr; + } + + r_bitmap_size[0] = bitmap_size[0]; + r_bitmap_size[1] = bitmap_size[1]; + + r_hot_spot[0] = int(cursor.hotspot[0] * (bitmap_size[0] - 1)); + r_hot_spot[1] = int(cursor.hotspot[1] * (bitmap_size[1] - 1)); + + return bitmap_rgba; + }; + + cursor_generator->user_data = (void *)&cursor; + cursor_generator->free_fn = [](GHOST_CursorGenerator *cursor_generator) { + MEM_freeN(cursor_generator); + }; + + GHOST_TSuccess success = GHOST_SetCustomCursorGenerator( + static_cast(win->ghostwin), cursor_generator); + + return (success == GHOST_kSuccess) ? true : false; +} + static bool window_set_custom_cursor_pixmap(wmWindow *win, const BCursor &cursor) { /* Option to force use of 1bpp XBitMap cursors is needed for testing. */ @@ -333,7 +374,9 @@ static bool window_set_custom_cursor_pixmap(wmWindow *win, const BCursor &cursor static bool window_set_custom_cursor(wmWindow *win, const BCursor &cursor) { - /* Keep this wrapper until other types are supported, see: !141597. */ + if (WM_capabilities_flag() & WM_CAPABILITY_CURSOR_GENERATOR) { + return window_set_custom_cursor_generator(win, cursor); + } return window_set_custom_cursor_pixmap(win, cursor); } @@ -749,6 +792,60 @@ static uint8_t *cursor_bitmap_from_text(const std::string &text, return bitmap_rgba; } +static bool wm_cursor_text_generator(wmWindow *win, const std::string &text, int font_id) +{ + struct WMCursorText { + std::string text; + int font_id; + }; + + GHOST_CursorGenerator *cursor_generator = MEM_callocN(__func__); + cursor_generator->generate_fn = [](const GHOST_CursorGenerator *cursor_generator, + const int cursor_size, + const int cursor_size_max, + uint8_t *(*alloc_fn)(size_t size), + int r_bitmap_size[2], + int r_hot_spot[2]) -> uint8_t * { + const WMCursorText &cursor_text = *(const WMCursorText *)(cursor_generator->user_data); + + int bitmap_size[2]; + uint8_t *bitmap_rgba = cursor_bitmap_from_text(cursor_text.text, + cursor_size, + cursor_size_max, + cursor_text.font_id, + alloc_fn, + bitmap_size); + + if (UNLIKELY(bitmap_rgba == nullptr)) { + return nullptr; + } + + r_bitmap_size[0] = bitmap_size[0]; + r_bitmap_size[1] = bitmap_size[1]; + + r_hot_spot[0] = bitmap_size[0] / 2; + r_hot_spot[1] = bitmap_size[1] / 2; + + return bitmap_rgba; + }; + + WMCursorText *cursor_text = MEM_new(__func__); + cursor_text->text = text; + cursor_text->font_id = font_id; + + cursor_generator->user_data = (void *)cursor_text; + cursor_generator->free_fn = [](GHOST_CursorGenerator *cursor_generator) { + const WMCursorText *cursor_text = (WMCursorText *)(cursor_generator->user_data); + MEM_delete(cursor_text); + MEM_freeN(cursor_generator); + }; + + GHOST_TSuccess success = GHOST_SetCustomCursorGenerator( + static_cast(win->ghostwin), cursor_generator); + + return (success == GHOST_kSuccess) ? true : false; +} + static bool wm_cursor_text_pixmap(wmWindow *win, const std::string &text, int font_id) { const int cursor_size = wm_cursor_size(win); @@ -788,7 +885,9 @@ static bool wm_cursor_text_pixmap(wmWindow *win, const std::string &text, int fo static bool wm_cursor_text(wmWindow *win, const std::string &text, int font_id) { - /* Keep this wrapper until other types are supported, see: !141597. */ + if (WM_capabilities_flag() & WM_CAPABILITY_CURSOR_GENERATOR) { + return wm_cursor_text_generator(win, text, font_id); + } return wm_cursor_text_pixmap(win, text, font_id); } diff --git a/source/blender/windowmanager/intern/wm_window.cc b/source/blender/windowmanager/intern/wm_window.cc index 839fc87aade..3ff6a95fdaa 100644 --- a/source/blender/windowmanager/intern/wm_window.cc +++ b/source/blender/windowmanager/intern/wm_window.cc @@ -2235,6 +2235,9 @@ eWM_CapabilitiesFlag WM_capabilities_flag() if (ghost_flag & GHOST_kCapabilityCursorRGBA) { flag |= WM_CAPABILITY_CURSOR_RGBA; } + if (ghost_flag & GHOST_kCapabilityCursorGenerator) { + flag |= WM_CAPABILITY_CURSOR_GENERATOR; + } return flag; }