diff --git a/Common/gfx/allegrobitmap.h b/Common/gfx/allegrobitmap.h index fd8b192ee85..c50767a2a50 100644 --- a/Common/gfx/allegrobitmap.h +++ b/Common/gfx/allegrobitmap.h @@ -125,6 +125,12 @@ class Bitmap return (GetColorDepth() + 7) / 8; } + // Length of the scanline in bytes + inline int GetPitch() const + { + return GetBPP() * GetWidth(); + } + // CHECKME: probably should not be exposed, see comment to GetData() inline int GetDataSize() const { diff --git a/Engine/ac/draw.cpp b/Engine/ac/draw.cpp index 425cdf9f1ff..6c9704bdac3 100644 --- a/Engine/ac/draw.cpp +++ b/Engine/ac/draw.cpp @@ -1625,8 +1625,8 @@ static void apply_tint_or_light(ObjTexture &actsp, int light_level, // * if transformation is necessary - writes into dst and returns dst; // * if no transformation is necessary - simply returns src; // Used for software render mode only. -static Bitmap *transform_sprite(Bitmap *src, bool src_has_alpha, std::unique_ptr &dst, - const Size dst_sz, GraphicFlip flip = Common::kFlip_None) +Common::Bitmap *transform_sprite(Common::Bitmap *src, bool src_has_alpha, std::unique_ptr &dst, + Size dst_sz, Common::GraphicFlip flip) { if ((src->GetSize() == dst_sz) && (flip == kFlip_None)) return src; // No transform: return source image @@ -1680,7 +1680,7 @@ static Bitmap *transform_sprite(Bitmap *src, bool src_has_alpha, std::unique_ptr static bool scale_and_flip_sprite(ObjTexture &actsp, int sppic, int width, int height, bool hmirror) { Bitmap *src = spriteset[sppic]; - Bitmap *result = transform_sprite(src, (game.SpriteInfos[sppic].Flags & SPF_ALPHACHANNEL) != 0, + Bitmap const *result = transform_sprite(src, (game.SpriteInfos[sppic].Flags & SPF_ALPHACHANNEL) != 0, actsp.Bmp, Size(width, height), hmirror ? kFlip_Horizontal : kFlip_None); return result != src; } @@ -2711,6 +2711,7 @@ void construct_game_screen_overlay(bool draw_mouse) { gfxDriver->DrawSprite(AGSE_POSTSCREENDRAW, 0, nullptr); } + if(usetup.mouse_hardware_cursor) update_hardware_cursor_graphic(); // Add mouse cursor pic, and global screen tint effect if (play.screen_is_faded_out == 0) @@ -2904,7 +2905,7 @@ void render_graphics(IDriverDependantBitmap *extraBitmap, int extraX, int extraY gfxDriver->DrawSprite(extraX, extraY, extraBitmap); gfxDriver->EndSpriteBatch(); } - construct_game_screen_overlay(true); + construct_game_screen_overlay(!usetup.mouse_hardware_cursor); render_to_screen(); if (!play.screen_is_faded_out) { diff --git a/Engine/ac/draw.h b/Engine/ac/draw.h index 046e25df4d1..df39672a5f1 100644 --- a/Engine/ac/draw.h +++ b/Engine/ac/draw.h @@ -99,6 +99,10 @@ void invalidate_rect(int x1, int y1, int x2, int y2, bool in_room); void mark_current_background_dirty(); +// Generates a transformed sprite, using src image and parameters +Common::Bitmap *transform_sprite(Common::Bitmap *src, bool src_has_alpha, std::unique_ptr &dst, + Size dst_sz, Common::GraphicFlip flip = Common::kFlip_None); + // Avoid freeing and reallocating the memory if possible Common::Bitmap *recycle_bitmap(Common::Bitmap *bimp, int coldep, int wid, int hit, bool make_transparent = false); void recycle_bitmap(std::unique_ptr &bimp, int coldep, int wid, int hit, bool make_transparent = false); diff --git a/Engine/ac/gamesetup.cpp b/Engine/ac/gamesetup.cpp index e16f74161a4..d800c596d2c 100644 --- a/Engine/ac/gamesetup.cpp +++ b/Engine/ac/gamesetup.cpp @@ -30,6 +30,7 @@ GameSetup::GameSetup() mouse_speed_def = kMouseSpeed_CurrentDisplay; touch_emulate_mouse = kTouchMouse_OneFingerDrag; touch_motion_relative = false; + mouse_hardware_cursor = false; RenderAtScreenRes = false; Supersampling = 1; clear_cache_on_room_change = false; diff --git a/Engine/ac/gamesetup.h b/Engine/ac/gamesetup.h index 84a8f896e6e..51a07690bc1 100644 --- a/Engine/ac/gamesetup.h +++ b/Engine/ac/gamesetup.h @@ -95,6 +95,7 @@ struct GameSetup // touch control abs/relative mode bool touch_motion_relative; // + bool mouse_hardware_cursor; bool RenderAtScreenRes; // render sprites at screen resolution, as opposed to native one int Supersampling; size_t SpriteCacheSize = DefSpriteCacheSize; // in KB diff --git a/Engine/ac/mouse.cpp b/Engine/ac/mouse.cpp index 8cd908004a9..e9cc07d72da 100644 --- a/Engine/ac/mouse.cpp +++ b/Engine/ac/mouse.cpp @@ -32,7 +32,9 @@ #include "ac/spritecache.h" #include "gfx/graphicsdriver.h" #include "gfx/gfxfilter.h" +#include "gfx/gfx_util.h" #include "platform/base/agsplatformdriver.h" +#include using namespace AGS::Common; using namespace AGS::Engine; @@ -56,6 +58,16 @@ Bitmap *blank_mouse_cursor = nullptr; eAGSMouseButton simulatedClick = kMouseNone; +struct HardwareCursor { + int prev_cur_spriteslot = -1; + int last_spriteslot = -1; + SDL_Surface *sdl_surface = nullptr; + std::unique_ptr bitmap = nullptr; + SDL_Cursor *sdl_cursor = nullptr; + const Point base_point = Point(32, 32); + Point prev_point = Point(1, 1); +} hcur; + // The Mouse:: functions are static so the script doesn't pass // in an object parameter void Mouse_SetVisible(int isOn) { @@ -391,7 +403,48 @@ void update_cached_mouse_cursor() mouseCursor = gfxDriver->CreateDDBFromBitmap(mousecurs[0], alpha_blend_cursor != 0); } +void update_hardware_cursor_graphic() { + int width, height, hotspot_x, hotspot_y; + Bitmap* cur_bitmap; + Size sz; + if(hcur.prev_point != GameScaling.Scale(hcur.base_point)) { + hcur.prev_point = GameScaling.Scale(hcur.base_point); + hcur.last_spriteslot = -1; + } + + if(play.mouse_cursor_hidden || hcur.prev_cur_spriteslot <= 0) { + SDL_ShowCursor(0); + hcur.last_spriteslot = -1; + return; + } + + if(hcur.last_spriteslot == hcur.prev_cur_spriteslot) return; + hcur.last_spriteslot = hcur.prev_cur_spriteslot; + + cur_bitmap = mousecurs[0]; + width = GameScaling.X.ScaleDistance(cur_bitmap->GetWidth()); + height = GameScaling.Y.ScaleDistance(cur_bitmap->GetHeight()); + hotspot_x = GameScaling.X.ScaleDistance(game.mcurs[cur_cursor].hotx); + hotspot_y = GameScaling.Y.ScaleDistance(game.mcurs[cur_cursor].hoty); + sz = Size(width, height); + + if(hcur.bitmap) hcur.bitmap->Destroy(); + + hcur.bitmap.reset (BitmapHelper::CreateBitmap(width, height, cur_bitmap->GetColorDepth())); + + Bitmap* res_bmp = transform_sprite(cur_bitmap, alpha_blend_cursor != 0, hcur.bitmap, sz, GraphicFlip::kFlip_None); + + if(hcur.sdl_cursor) SDL_FreeCursor(hcur.sdl_cursor); + if(hcur.sdl_surface) SDL_FreeSurface(hcur.sdl_surface); + + hcur.sdl_surface = GfxUtil::CreateSDL_SurfaceFromBitmap(res_bmp); + hcur.sdl_cursor = SDL_CreateColorCursor(hcur.sdl_surface, hotspot_x, hotspot_y); + SDL_SetCursor(hcur.sdl_cursor); + SDL_ShowCursor(1); +} + void set_new_cursor_graphic (int spriteslot) { + hcur.prev_cur_spriteslot = spriteslot; mousecurs[0] = spriteset[spriteslot]; // It looks like spriteslot 0 can be used in games with version 2.72 and lower. diff --git a/Engine/ac/mouse.h b/Engine/ac/mouse.h index f66463e919c..d6c97430902 100644 --- a/Engine/ac/mouse.h +++ b/Engine/ac/mouse.h @@ -56,6 +56,7 @@ void update_script_mouse_coords(); void update_inv_cursor(int invnum); void update_cached_mouse_cursor(); void set_new_cursor_graphic (int spriteslot); +void update_hardware_cursor_graphic(); int find_next_enabled_cursor(int startwith); int find_previous_enabled_cursor(int startwith); diff --git a/Engine/gfx/gfx_util.cpp b/Engine/gfx/gfx_util.cpp index 1ce158674ee..877295082ba 100644 --- a/Engine/gfx/gfx_util.cpp +++ b/Engine/gfx/gfx_util.cpp @@ -15,6 +15,7 @@ #include "core/platform.h" #include "gfx/gfx_util.h" #include "gfx/blender.h" +#include "SDL_surface.h" namespace AGS { @@ -41,6 +42,28 @@ Bitmap *ConvertBitmap(Bitmap *src, int dst_color_depth) return src; } +SDL_Surface* CreateSDL_SurfaceFromBitmap(Bitmap* src) +{ + Uint32 rmask, gmask, bmask, amask; + if(src->GetColorDepth() == 32) + { +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + rmask = 0x0000ff00; + gmask = 0x00ff0000; + bmask = 0xff000000; + amask = 0x000000ff; +#else + rmask = 0x00ff0000; + gmask = 0x0000ff00; + bmask = 0x000000ff; + amask = 0xff000000; +#endif + } + + SDL_Surface* surface = SDL_CreateRGBSurfaceFrom((void *) src->GetData(), src->GetWidth(), src->GetHeight(), src->GetColorDepth(), src->GetPitch(), rmask, gmask, bmask, amask); + return surface; +} + typedef BLENDER_FUNC PfnBlenderCb; diff --git a/Engine/gfx/gfx_util.h b/Engine/gfx/gfx_util.h index 924e3cc755d..bb545cba5c2 100644 --- a/Engine/gfx/gfx_util.h +++ b/Engine/gfx/gfx_util.h @@ -27,6 +27,8 @@ #include "gfx/bitmap.h" #include "gfx/gfx_def.h" +struct SDL_Surface; + namespace AGS { namespace Engine @@ -39,6 +41,9 @@ namespace GfxUtil // Creates a COPY of the source bitmap, converted to the given format. Bitmap *ConvertBitmap(Bitmap *src, int dst_color_depth); + // Creates a COPY of the source bitmap, as an SDL Surface + SDL_Surface* CreateSDL_SurfaceFromBitmap(Bitmap* src); + // Considers the given information about source and destination surfaces, // then draws a bimtap over another either using requested blending mode, // or fallbacks to common "magic pink" transparency mode; diff --git a/Engine/main/config.cpp b/Engine/main/config.cpp index df74648db23..6095e1d5e4d 100644 --- a/Engine/main/config.cpp +++ b/Engine/main/config.cpp @@ -371,6 +371,7 @@ void apply_config(const ConfigTree &cfg) mouse_str = CfgReadString(cfg, "mouse", "speed_def", "current_display"); usetup.mouse_speed_def = StrUtil::ParseEnum( mouse_str, CstrArr{ "absolute", "current_display" }, usetup.mouse_speed_def); + usetup.mouse_hardware_cursor = CfgReadBoolInt(cfg, "mouse", "hardware_cursor"); // Touch options usetup.touch_emulate_mouse = StrUtil::ParseEnum( diff --git a/Engine/main/main.cpp b/Engine/main/main.cpp index c405cc8eaa2..6e973ddf60a 100644 --- a/Engine/main/main.cpp +++ b/Engine/main/main.cpp @@ -174,6 +174,7 @@ void main_print_help() { " --log-stdout=+mgs:debug\n" " --log-file=all:warn\n" " --log-file-path=PATH Define custom path for the log file\n" + " --mouse-hardware-cursor Draw game cursor using hardware accelerated method\n" //--------------------------------------------------------------------------------| #if AGS_PLATFORM_OS_WINDOWS " --no-message-box Disable alerts as modal message boxes\n" @@ -336,6 +337,8 @@ static int main_process_cmdline(ConfigTree &cfg, int argc, char *argv[]) cfg["language"]["translation"] = ""; else if (ags_stricmp(arg, "--background") == 0) cfg["override"]["multitasking"] = "1"; + else if (ags_stricmp(arg, "--mouse-hardware-cursor") == 0) + cfg["mouse"]["hardware_cursor"] = "1"; else if (ags_stricmp(arg, "--fps") == 0) cfg["misc"]["show_fps"] = "1"; else if (ags_stricmp(arg, "--test") == 0) debug_flags |= DBG_DEBUGMODE;