[Scummvm-git-logs] scummvm master -> ebe2096af75e02e50b95ea45a57d864eb13eeef8

dreammaster noreply at scummvm.org
Mon May 2 00:23:56 UTC 2022


This automated email contains information about 33 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
2df62bdd55 AGS: Only redraw gui controls when they are marked as changed
a476850123 AGS: Calculate visible graphic size of gui controls for textures
9fff1dc1fa AGS: Separate sprite lists and batch lists in renderers
2b96f0ff67 AGS: Sprite batches may have parents
2f558ae1ed AGS: Fixed 8-bit sprites unexpectedly converted on load
d09117334d AGS: Removed obscure hack in DrawSpriteWithTransparency()
fec8ab3183 AGS: Simplified DrawSpriteWithTransparency() a little
d52e2fb954 AGS: Gui control textures are rendered as gui sub-batches
211abe3744 AGS: Implemented HasAlphaChannel() in GuiButton and GuiSlider
d7375f0772 AGS: Fixed GUIObject::SetVisible(), should MarkChanged
50b2b1af05 AGS: Replaced OnControlPositionChanged() with MarkControlsChanged()
53e1c22185 AGS: Fixes for the updated sprite batch system
6ef504bba2 AGS: Store gui draw order in a std::vector
d8d593079a AGS: Don't MarkChanged guis changing Transparency or Visible flag
ec170ebd2f AGS: More strict MarkChanged use for gui controls, on changes only
fbd328b0ae AGS: On changed or deleted dynamic sprite, also update sliders
b9a9b00974 AGS: Graphic renderers use correct alpha as sprite transparency
8b027e0d82 AGS: Support sprite batch's alpha
8319217b7a AGS: Added GUIControl.Transparency
3b20393a17 AGS: Implemented gui control transparency in software mode
b2d5b6e586 AGS: Implemented Overlay.Width & Height working as scaling
684463c903 AGS: Implemented readonly Overlay.GraphicWidth & GraphicHeight
a94adb5968 AGS: Fixed TextBox duplicating letter input
598acd02b8 AGS: Updated build version (3.6.0.23)
f840223533 AGS: Fixed GUI's background image with alpha channel
87994139f6 AGS: Tidied gui drawing code a little
3b91a1124f AGS: Made internal BitmapFlip enum match the script, for convenience
d3f8627678 AGS: Refactored scale_and_flip_sprite() into transform_sprite()
8ac8c037c9 AGS: Use transform_sprite() with overlays for consistency
d9da1e3759 AGS: Don't try rendering gui controls with zero size
3be37608f0 AGS: Fixed potential division by zero in GUISlider
4cd0ca9a27 AGS: Removed DrawAsSeparateCharSprite walk-behind method
ebe2096af7 AGS: Call unload_game_file() in quit()


Commit: 2df62bdd5510a64a0bf8c3106fbd4c9101a63abc
    https://github.com/scummvm/scummvm/commit/2df62bdd5510a64a0bf8c3106fbd4c9101a63abc
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:19-07:00

Commit Message:
AGS: Only redraw gui controls when they are marked as changed

>From upstream 27a6bd6a13494cd670fb6749e5bd663f088487ea

Changed paths:
    engines/ags/engine/ac/button.cpp
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/inv_window.cpp
    engines/ags/engine/ac/label.cpp
    engines/ags/engine/ac/listbox.cpp
    engines/ags/engine/ac/slider.cpp
    engines/ags/engine/ac/textbox.cpp
    engines/ags/engine/gui/gui_engine.cpp
    engines/ags/shared/gui/gui_button.cpp
    engines/ags/shared/gui/gui_inv.cpp
    engines/ags/shared/gui/gui_label.cpp
    engines/ags/shared/gui/gui_listbox.cpp
    engines/ags/shared/gui/gui_main.cpp
    engines/ags/shared/gui/gui_main.h
    engines/ags/shared/gui/gui_object.cpp
    engines/ags/shared/gui/gui_object.h
    engines/ags/shared/gui/gui_slider.cpp
    engines/ags/shared/gui/gui_textbox.cpp


diff --git a/engines/ags/engine/ac/button.cpp b/engines/ags/engine/ac/button.cpp
index 6f5fe9feb6d..8c3c8b0680f 100644
--- a/engines/ags/engine/ac/button.cpp
+++ b/engines/ags/engine/ac/button.cpp
@@ -50,7 +50,7 @@ void UpdateButtonState(const AnimatingGUIButton &abtn) {
 	_GP(guibuts)[abtn.buttonid].CurrentImage = _GP(guibuts)[abtn.buttonid].Image;
 	_GP(guibuts)[abtn.buttonid].PushedImage = 0;
 	_GP(guibuts)[abtn.buttonid].MouseOverImage = 0;
-	_GP(guibuts)[abtn.buttonid].NotifyParentChanged();
+	_GP(guibuts)[abtn.buttonid].MarkChanged();
 }
 
 void Button_AnimateEx(GUIButton *butt, int view, int loop, int speed, int repeat, int blocking, int direction, int sframe) {
@@ -138,7 +138,7 @@ void Button_SetFont(GUIButton *butt, int newFont) {
 
 	if (butt->Font != newFont) {
 		butt->Font = newFont;
-		butt->NotifyParentChanged();
+		butt->MarkChanged();
 	}
 }
 
@@ -174,7 +174,7 @@ void Button_SetMouseOverGraphic(GUIButton *guil, int slotn) {
 		guil->CurrentImage = slotn;
 	guil->MouseOverImage = slotn;
 
-	guil->NotifyParentChanged();
+	guil->MarkChanged();
 	FindAndRemoveButtonAnimation(guil->ParentId, guil->Id);
 }
 
@@ -197,7 +197,7 @@ void Button_SetNormalGraphic(GUIButton *guil, int slotn) {
 		guil->Height = _GP(game).SpriteInfos[slotn].Height;
 	}
 
-	guil->NotifyParentChanged();
+	guil->MarkChanged();
 	FindAndRemoveButtonAnimation(guil->ParentId, guil->Id);
 }
 
@@ -212,7 +212,7 @@ void Button_SetPushedGraphic(GUIButton *guil, int slotn) {
 		guil->CurrentImage = slotn;
 	guil->PushedImage = slotn;
 
-	guil->NotifyParentChanged();
+	guil->MarkChanged();
 	FindAndRemoveButtonAnimation(guil->ParentId, guil->Id);
 }
 
@@ -223,7 +223,7 @@ int Button_GetTextColor(GUIButton *butt) {
 void Button_SetTextColor(GUIButton *butt, int newcol) {
 	if (butt->TextColor != newcol) {
 		butt->TextColor = newcol;
-		butt->NotifyParentChanged();
+		butt->MarkChanged();
 	}
 }
 
@@ -317,7 +317,7 @@ int Button_GetTextAlignment(GUIButton *butt) {
 void Button_SetTextAlignment(GUIButton *butt, int align) {
 	if (butt->TextAlignment != align) {
 		butt->TextAlignment = (FrameAlignment)align;
-		butt->NotifyParentChanged();
+		butt->MarkChanged();
 	}
 }
 
diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 62025f7069e..93c7066f64f 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -447,7 +447,7 @@ void clear_drawobj_cache() {
 		_GP(guibgbmp)[i] = nullptr;
 	}
 
-	for (uint i = 0; i < _GP(guiobjbg).size(); ++i) {
+	for (size_t i = 0; i < _GP(guiobjbg).size(); ++i) {
 		delete _GP(guiobjbg)[i];
 		_GP(guiobjbg)[i] = nullptr;
 		if (_GP(guiobjbmp)[i])
@@ -1963,16 +1963,19 @@ void draw_gui_controls(GUIMain &gui) {
 	int draw_index = _GP(guiobjbmpref)[gui.ID];
 	for (int i = 0; i < gui.GetControlCount(); ++i, ++draw_index) {
 		GUIObject *obj = gui.GetControl(i);
+		if (!obj->IsVisible() ||
+			(!obj->IsEnabled() && (GUI::Options.DisabledStyle == kGuiDis_Blackout)))
+			continue;
+		if (!obj->HasChanged())
+			continue;
+		obj->ClearChanged();
+
 		if (_GP(guiobjbg)[draw_index] == nullptr ||
 			_GP(guiobjbg)[draw_index]->GetSize() != Size(obj->Width, obj->Height)) {
 			recreate_drawobj_bitmap(_GP(guiobjbg)[draw_index], _GP(guiobjbmp)[draw_index],
 				obj->Width, obj->Height);
 		}
 
-		if (!obj->IsVisible() ||
-			(!obj->IsEnabled() && (GUI::Options.DisabledStyle == kGuiDis_Blackout)))
-			continue;
-
 		_GP(guiobjbg)[draw_index]->ClearTransparent();
 		obj->Draw(_GP(guiobjbg)[draw_index]);
 
@@ -2027,43 +2030,45 @@ void draw_gui_and_overlays() {
 		{
 			for (aa = 0; aa < _GP(game).numgui; aa++) {
 				if (!_GP(guis)[aa].IsDisplayed()) continue; // not on screen
-				if (!_GP(guis)[aa].HasChanged()) continue; // no changes: no need to update image
+				if (!_GP(guis)[aa].HasChanged() && !_GP(guis)[aa].HasControlsChanged()) continue; // no changes: no need to update image
 				if (_GP(guis)[aa].Transparency == 255) continue; // 100% transparent
 
-				_GP(guis)[aa].ClearChanged();
 				if (_GP(guibg)[aa] == nullptr ||
 					_GP(guibg)[aa]->GetSize() != Size(_GP(guis)[aa].Width, _GP(guis)[aa].Height)) {
 					recreate_drawobj_bitmap(_GP(guibg)[aa], _GP(guibgbmp)[aa], _GP(guis)[aa].Width, _GP(guis)[aa].Height);
 				}
 
 				_G(eip_guinum) = aa;
-				_G(our_eip) = 370;
-				_GP(guibg)[aa]->ClearTransparent();
 				_G(our_eip) = 372;
-				if (draw_controls_as_textures) {
-					_GP(guis)[aa].DrawSelf(_GP(guibg)[aa]);
-					draw_gui_controls(_GP(guis)[aa]);
-				} else {
-					_GP(guis)[aa].DrawWithControls(_GP(guibg)[aa]);
-				}
-				_G(our_eip) = 373;
-
-				bool isAlpha = false;
-				if (_GP(guis)[aa].HasAlphaChannel()) {
-					isAlpha = true;
-
-					if ((_GP(game).options[OPT_NEWGUIALPHA] == kGuiAlphaRender_Legacy) && (_GP(guis)[aa].BgImage > 0)) {
-						// old-style (pre-3.0.2) GUI alpha rendering
-						repair_alpha_channel(_GP(guibg)[aa], _GP(spriteset)[_GP(guis)[aa].BgImage]);
+				const bool draw_with_controls = !draw_controls_as_textures;
+				if (_GP(guis)[aa].HasChanged() || (draw_with_controls && _GP(guis)[aa].HasControlsChanged())) {
+					_GP(guibg)[aa]->ClearTransparent();
+					if (draw_with_controls)
+						_GP(guis)[aa].DrawWithControls(_GP(guibg)[aa]);
+					else
+						_GP(guis)[aa].DrawSelf(_GP(guibg)[aa]);
+
+					const bool is_alpha = _GP(guis)[aa].HasAlphaChannel();
+					if (is_alpha) {
+						if ((_GP(game).options[OPT_NEWGUIALPHA] == kGuiAlphaRender_Legacy) && (_GP(guis)[aa].BgImage > 0)) {
+							// old-style (pre-3.0.2) GUI alpha rendering
+							repair_alpha_channel(_GP(guibg)[aa], _GP(spriteset)[_GP(guis)[aa].BgImage]);
+						}
 					}
+
+					if (_GP(guibgbmp)[aa])
+						_G(gfxDriver)->UpdateDDBFromBitmap(_GP(guibgbmp)[aa], _GP(guibg)[aa], is_alpha);
+					else
+						_GP(guibgbmp)[aa] = _G(gfxDriver)->CreateDDBFromBitmap(_GP(guibg)[aa], is_alpha);
 				}
 
-				if (_GP(guibgbmp)[aa] != nullptr) {
-					_G(gfxDriver)->UpdateDDBFromBitmap(_GP(guibgbmp)[aa], _GP(guibg)[aa], isAlpha);
-				} else {
-					_GP(guibgbmp)[aa] = _G(gfxDriver)->CreateDDBFromBitmap(_GP(guibg)[aa], isAlpha);
+				_G(our_eip) = 373;
+				if (!draw_with_controls && _GP(guis)[aa].HasControlsChanged()) {
+					draw_gui_controls(_GP(guis)[aa]);
 				}
 				_G(our_eip) = 374;
+
+				_GP(guis)[aa].ClearChanged();
 			}
 		}
 		_G(our_eip) = 38;
diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index 6cfdf84f8b2..edd0feeaa00 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -1372,7 +1372,7 @@ void game_sprite_updated(int sprnum) {
 	// gui buttons
 	for (size_t i = 0; i < (size_t)_G(numguibuts); ++i) {
 		if (_GP(guibuts)[i].CurrentImage == sprnum) {
-			_GP(guibuts)[i].NotifyParentChanged();
+			_GP(guibuts)[i].MarkChanged();
 		}
 	}
 }
@@ -1411,7 +1411,7 @@ void game_sprite_deleted(int sprnum) {
 
 		if (_GP(guibuts)[i].CurrentImage == sprnum) {
 			_GP(guibuts)[i].CurrentImage = 0;
-			_GP(guibuts)[i].NotifyParentChanged();
+			_GP(guibuts)[i].MarkChanged();
 		}
 	}
 	// views
diff --git a/engines/ags/engine/ac/inv_window.cpp b/engines/ags/engine/ac/inv_window.cpp
index 5ef33f83540..0a4f388465d 100644
--- a/engines/ags/engine/ac/inv_window.cpp
+++ b/engines/ags/engine/ac/inv_window.cpp
@@ -64,7 +64,7 @@ void InvWindow_SetCharacterToUse(GUIInvWindow *guii, CharacterInfo *chaa) {
 	// reset to top of list
 	guii->TopItem = 0;
 
-	guii->NotifyParentChanged();
+	guii->MarkChanged();
 }
 
 CharacterInfo *InvWindow_GetCharacterToUse(GUIInvWindow *guii) {
@@ -95,7 +95,7 @@ int InvWindow_GetItemHeight(GUIInvWindow *guii) {
 void InvWindow_SetTopItem(GUIInvWindow *guii, int topitem) {
 	if (guii->TopItem != topitem) {
 		guii->TopItem = topitem;
-		guii->NotifyParentChanged();
+		guii->MarkChanged();
 	}
 }
 
@@ -119,7 +119,7 @@ void InvWindow_ScrollDown(GUIInvWindow *guii) {
 	if ((_G(charextra)[guii->GetCharacterId()].invorder_count) >
 	        (guii->TopItem + (guii->ColCount * guii->RowCount))) {
 		guii->TopItem += guii->ColCount;
-		guii->NotifyParentChanged();
+		guii->MarkChanged();
 	}
 }
 
@@ -129,7 +129,7 @@ void InvWindow_ScrollUp(GUIInvWindow *guii) {
 		if (guii->TopItem < 0)
 			guii->TopItem = 0;
 
-		guii->NotifyParentChanged();
+		guii->MarkChanged();
 	}
 }
 
diff --git a/engines/ags/engine/ac/label.cpp b/engines/ags/engine/ac/label.cpp
index 0d78cef55a0..d7e4f88411b 100644
--- a/engines/ags/engine/ac/label.cpp
+++ b/engines/ags/engine/ac/label.cpp
@@ -57,7 +57,7 @@ int Label_GetTextAlignment(GUILabel *labl) {
 void Label_SetTextAlignment(GUILabel *labl, int align) {
 	if (labl->TextAlignment != align) {
 		labl->TextAlignment = (HorAlignment)align;
-		labl->NotifyParentChanged();
+		labl->MarkChanged();
 	}
 }
 
@@ -68,7 +68,7 @@ int Label_GetColor(GUILabel *labl) {
 void Label_SetColor(GUILabel *labl, int colr) {
 	if (labl->TextColor != colr) {
 		labl->TextColor = colr;
-		labl->NotifyParentChanged();
+		labl->MarkChanged();
 	}
 }
 
@@ -82,7 +82,7 @@ void Label_SetFont(GUILabel *guil, int fontnum) {
 
 	if (fontnum != guil->Font) {
 		guil->Font = fontnum;
-		guil->NotifyParentChanged();
+		guil->MarkChanged();
 	}
 }
 
diff --git a/engines/ags/engine/ac/listbox.cpp b/engines/ags/engine/ac/listbox.cpp
index 3b708c6f78c..0e2667cfdb0 100644
--- a/engines/ags/engine/ac/listbox.cpp
+++ b/engines/ags/engine/ac/listbox.cpp
@@ -272,7 +272,7 @@ int ListBox_GetSelectedBackColor(GUIListBox *listbox) {
 void ListBox_SetSelectedBackColor(GUIListBox *listbox, int colr) {
 	if (listbox->SelectedBgColor != colr) {
 		listbox->SelectedBgColor = colr;
-		listbox->NotifyParentChanged();
+		listbox->MarkChanged();
 	}
 }
 
@@ -283,7 +283,7 @@ int ListBox_GetSelectedTextColor(GUIListBox *listbox) {
 void ListBox_SetSelectedTextColor(GUIListBox *listbox, int colr) {
 	if (listbox->SelectedTextColor != colr) {
 		listbox->SelectedTextColor = colr;
-		listbox->NotifyParentChanged();
+		listbox->MarkChanged();
 	}
 }
 
@@ -294,7 +294,7 @@ int ListBox_GetTextAlignment(GUIListBox *listbox) {
 void ListBox_SetTextAlignment(GUIListBox *listbox, int align) {
 	if (listbox->TextAlignment != align) {
 		listbox->TextAlignment = (HorAlignment)align;
-		listbox->NotifyParentChanged();
+		listbox->MarkChanged();
 	}
 }
 
@@ -305,7 +305,7 @@ int ListBox_GetTextColor(GUIListBox *listbox) {
 void ListBox_SetTextColor(GUIListBox *listbox, int colr) {
 	if (listbox->TextColor != colr) {
 		listbox->TextColor = colr;
-		listbox->NotifyParentChanged();
+		listbox->MarkChanged();
 	}
 }
 
@@ -328,7 +328,7 @@ void ListBox_SetSelectedIndex(GUIListBox *guisl, int newsel) {
 			if (newsel >= guisl->TopItem + guisl->VisibleItemCount)
 				guisl->TopItem = (newsel - guisl->VisibleItemCount) + 1;
 		}
-		guisl->NotifyParentChanged();
+		guisl->MarkChanged();
 	}
 
 }
@@ -344,7 +344,7 @@ void ListBox_SetTopItem(GUIListBox *guisl, int item) {
 		quit("!ListBoxSetTopItem: tried to set top to beyond top or bottom of list");
 
 	guisl->TopItem = item;
-	guisl->NotifyParentChanged();
+	guisl->MarkChanged();
 }
 
 int ListBox_GetRowCount(GUIListBox *listbox) {
@@ -354,14 +354,14 @@ int ListBox_GetRowCount(GUIListBox *listbox) {
 void ListBox_ScrollDown(GUIListBox *listbox) {
 	if (listbox->TopItem + listbox->VisibleItemCount < listbox->ItemCount) {
 		listbox->TopItem++;
-		listbox->NotifyParentChanged();
+		listbox->MarkChanged();
 	}
 }
 
 void ListBox_ScrollUp(GUIListBox *listbox) {
 	if (listbox->TopItem > 0) {
 		listbox->TopItem--;
-		listbox->NotifyParentChanged();
+		listbox->MarkChanged();
 	}
 }
 
diff --git a/engines/ags/engine/ac/slider.cpp b/engines/ags/engine/ac/slider.cpp
index 01f5feacab1..a9663d276f5 100644
--- a/engines/ags/engine/ac/slider.cpp
+++ b/engines/ags/engine/ac/slider.cpp
@@ -40,7 +40,7 @@ void Slider_SetMax(GUISlider *guisl, int valn) {
 		if (guisl->MinValue > guisl->MaxValue)
 			quit("!Slider.Max: minimum cannot be greater than maximum");
 
-		guisl->NotifyParentChanged();
+		guisl->MarkChanged();
 	}
 
 }
@@ -59,7 +59,7 @@ void Slider_SetMin(GUISlider *guisl, int valn) {
 		if (guisl->MinValue > guisl->MaxValue)
 			quit("!Slider.Min: minimum cannot be greater than maximum");
 
-		guisl->NotifyParentChanged();
+		guisl->MarkChanged();
 	}
 
 }
@@ -74,7 +74,7 @@ void Slider_SetValue(GUISlider *guisl, int valn) {
 
 	if (valn != guisl->Value) {
 		guisl->Value = valn;
-		guisl->NotifyParentChanged();
+		guisl->MarkChanged();
 	}
 }
 
@@ -89,7 +89,7 @@ int Slider_GetBackgroundGraphic(GUISlider *guisl) {
 void Slider_SetBackgroundGraphic(GUISlider *guisl, int newImage) {
 	if (newImage != guisl->BgImage) {
 		guisl->BgImage = newImage;
-		guisl->NotifyParentChanged();
+		guisl->MarkChanged();
 	}
 }
 
@@ -100,7 +100,7 @@ int Slider_GetHandleGraphic(GUISlider *guisl) {
 void Slider_SetHandleGraphic(GUISlider *guisl, int newImage) {
 	if (newImage != guisl->HandleImage) {
 		guisl->HandleImage = newImage;
-		guisl->NotifyParentChanged();
+		guisl->MarkChanged();
 	}
 }
 
@@ -111,7 +111,7 @@ int Slider_GetHandleOffset(GUISlider *guisl) {
 void Slider_SetHandleOffset(GUISlider *guisl, int newOffset) {
 	if (newOffset != guisl->HandleOffset) {
 		guisl->HandleOffset = newOffset;
-		guisl->NotifyParentChanged();
+		guisl->MarkChanged();
 	}
 }
 
diff --git a/engines/ags/engine/ac/textbox.cpp b/engines/ags/engine/ac/textbox.cpp
index 6750315bf17..a47629dfcfd 100644
--- a/engines/ags/engine/ac/textbox.cpp
+++ b/engines/ags/engine/ac/textbox.cpp
@@ -44,7 +44,7 @@ void TextBox_GetText(GUITextBox *texbox, char *buffer) {
 void TextBox_SetText(GUITextBox *texbox, const char *newtex) {
 	if (texbox->Text != newtex) {
 		texbox->Text = newtex;
-		texbox->NotifyParentChanged();
+		texbox->MarkChanged();
 	}
 }
 
@@ -55,7 +55,7 @@ int TextBox_GetTextColor(GUITextBox *guit) {
 void TextBox_SetTextColor(GUITextBox *guit, int colr) {
 	if (guit->TextColor != colr) {
 		guit->TextColor = colr;
-		guit->NotifyParentChanged();
+		guit->MarkChanged();
 	}
 }
 
@@ -69,7 +69,7 @@ void TextBox_SetFont(GUITextBox *guit, int fontnum) {
 
 	if (guit->Font != fontnum) {
 		guit->Font = fontnum;
-		guit->NotifyParentChanged();
+		guit->MarkChanged();
 	}
 }
 
@@ -80,7 +80,7 @@ bool TextBox_GetShowBorder(GUITextBox *guit) {
 void TextBox_SetShowBorder(GUITextBox *guit, bool on) {
 	if (guit->IsBorderShown() != on) {
 		guit->SetShowBorder(on);
-		guit->NotifyParentChanged();
+		guit->MarkChanged();
 	}
 }
 
diff --git a/engines/ags/engine/gui/gui_engine.cpp b/engines/ags/engine/gui/gui_engine.cpp
index 5f968a2c8e5..3b3e2088a70 100644
--- a/engines/ags/engine/gui/gui_engine.cpp
+++ b/engines/ags/engine/gui/gui_engine.cpp
@@ -101,8 +101,17 @@ bool GUIObject::IsClickable() const {
 	return (Flags & kGUICtrl_Clickable) != 0;
 }
 
-void GUIObject::NotifyParentChanged() {
-	_GP(guis)[ParentId].MarkChanged();
+void GUIObject::MarkChanged() {
+	_hasChanged = true;
+	_GP(guis)[ParentId].MarkControlsChanged();
+}
+
+bool GUIObject::HasChanged() const {
+	return _hasChanged;
+}
+
+void GUIObject::ClearChanged() {
+	_hasChanged = false;
 }
 
 void GUILabel::PrepareTextToDraw() {
diff --git a/engines/ags/shared/gui/gui_button.cpp b/engines/ags/shared/gui/gui_button.cpp
index eb6784995c3..9a00df229ae 100644
--- a/engines/ags/shared/gui/gui_button.cpp
+++ b/engines/ags/shared/gui/gui_button.cpp
@@ -122,7 +122,7 @@ void GUIButton::SetClipImage(bool on) {
 		Flags |= kGUICtrl_Clip;
 	else
 		Flags &= ~kGUICtrl_Clip;
-	NotifyParentChanged();
+	MarkChanged();
 }
 
 void GUIButton::SetText(const String &text) {
@@ -142,14 +142,14 @@ void GUIButton::SetText(const String &text) {
 
 	// TODO: find a way to remove this bogus limitation ("New Button" is a valid Text too)
 	_unnamed = _text.Compare("New Button") == 0;
-	NotifyParentChanged();
+	MarkChanged();
 }
 
 
 bool GUIButton::OnMouseDown() {
 	int new_image = (PushedImage > 0) ? PushedImage : CurrentImage;
 	if (CurrentImage != new_image || !IsImageButton())
-		NotifyParentChanged();
+		MarkChanged();
 	CurrentImage = new_image;
 	IsPushed = true;
 	return false;
@@ -160,7 +160,7 @@ void GUIButton::OnMouseEnter() {
 		(MouseOverImage > 0) ? MouseOverImage : Image;
 	if ((CurrentImage != new_image) || (IsPushed && !IsImageButton())) {
 		CurrentImage = new_image;
-		NotifyParentChanged();
+		MarkChanged();
 	}
 	IsMouseOver = true;
 }
@@ -168,7 +168,7 @@ void GUIButton::OnMouseEnter() {
 void GUIButton::OnMouseLeave() {
 	if ((CurrentImage != Image) || (IsPushed && !IsImageButton())) {
 		CurrentImage = Image;
-		NotifyParentChanged();
+		MarkChanged();
 	}
 	IsMouseOver = false;
 }
@@ -185,7 +185,7 @@ void GUIButton::OnMouseUp() {
 
 	if ((CurrentImage != new_image) || (IsPushed && !IsImageButton())) {
 		CurrentImage = new_image;
-		NotifyParentChanged();
+		MarkChanged();
 	}
 	IsPushed = false;
 }
diff --git a/engines/ags/shared/gui/gui_inv.cpp b/engines/ags/shared/gui/gui_inv.cpp
index 21bdbd9f55a..ef387aa61d7 100644
--- a/engines/ags/shared/gui/gui_inv.cpp
+++ b/engines/ags/shared/gui/gui_inv.cpp
@@ -57,7 +57,7 @@ void GUIInvWindow::OnMouseUp() {
 
 void GUIInvWindow::OnResized() {
 	CalculateNumCells();
-	NotifyParentChanged();
+	MarkChanged();
 }
 
 void GUIInvWindow::WriteToFile(Stream *out) const {
diff --git a/engines/ags/shared/gui/gui_label.cpp b/engines/ags/shared/gui/gui_label.cpp
index 481bb3e5214..a396d040b9b 100644
--- a/engines/ags/shared/gui/gui_label.cpp
+++ b/engines/ags/shared/gui/gui_label.cpp
@@ -75,7 +75,7 @@ void GUILabel::SetText(const String &text) {
 	Text = text;
 	// Check for macros within text
 	_textMacro = GUI::FindLabelMacros(Text);
-	NotifyParentChanged();
+	MarkChanged();
 }
 
 // TODO: replace string serialization with StrUtil::ReadString and WriteString
diff --git a/engines/ags/shared/gui/gui_listbox.cpp b/engines/ags/shared/gui/gui_listbox.cpp
index 3557d018ea0..6926552c1f1 100644
--- a/engines/ags/shared/gui/gui_listbox.cpp
+++ b/engines/ags/shared/gui/gui_listbox.cpp
@@ -80,7 +80,7 @@ int GUIListBox::AddItem(const String &text) {
 	Items.push_back(text);
 	SavedGameIndex.push_back(-1);
 	ItemCount++;
-	NotifyParentChanged();
+	MarkChanged();
 	return ItemCount - 1;
 }
 
@@ -90,7 +90,7 @@ void GUIListBox::Clear() {
 	ItemCount = 0;
 	SelectedItem = 0;
 	TopItem = 0;
-	NotifyParentChanged();
+	MarkChanged();
 }
 
 void GUIListBox::Draw(Bitmap *ds, int x, int y) {
@@ -177,7 +177,7 @@ int GUIListBox::InsertItem(int index, const String &text) {
 		SelectedItem++;
 
 	ItemCount++;
-	NotifyParentChanged();
+	MarkChanged();
 	return ItemCount - 1;
 }
 
@@ -193,7 +193,7 @@ void GUIListBox::RemoveItem(int index) {
 		SelectedItem--;
 	if (SelectedItem >= ItemCount)
 		SelectedItem = -1;
-	NotifyParentChanged();
+	MarkChanged();
 }
 
 void GUIListBox::SetShowArrows(bool on) {
@@ -201,7 +201,7 @@ void GUIListBox::SetShowArrows(bool on) {
 		ListBoxFlags |= kListBox_ShowArrows;
 	else
 		ListBoxFlags &= ~kListBox_ShowArrows;
-	NotifyParentChanged();
+	MarkChanged();
 }
 
 void GUIListBox::SetShowBorder(bool on) {
@@ -209,7 +209,7 @@ void GUIListBox::SetShowBorder(bool on) {
 		ListBoxFlags |= kListBox_ShowBorder;
 	else
 		ListBoxFlags &= ~kListBox_ShowBorder;
-	NotifyParentChanged();
+	MarkChanged();
 }
 
 void GUIListBox::SetSvgIndex(bool on) {
@@ -222,13 +222,13 @@ void GUIListBox::SetSvgIndex(bool on) {
 void GUIListBox::SetFont(int font) {
 	Font = font;
 	UpdateMetrics();
-	NotifyParentChanged();
+	MarkChanged();
 }
 
 void GUIListBox::SetItemText(int index, const String &text) {
 	if (index >= 0 && index < ItemCount) {
 		Items[index] = text;
-		NotifyParentChanged();
+		MarkChanged();
 	}
 }
 
@@ -241,7 +241,7 @@ bool GUIListBox::OnMouseDown() {
 			top_item = TopItem + 1;
 		if (TopItem != top_item) {
 			TopItem = top_item;
-			NotifyParentChanged();
+			MarkChanged();
 		}
 		return false;
 	}
@@ -251,7 +251,7 @@ bool GUIListBox::OnMouseDown() {
 		return false;
 	if (sel != SelectedItem) {
 		SelectedItem = sel;
-		NotifyParentChanged();
+		MarkChanged();
 	}
 	IsActivated = true;
 	return false;
@@ -264,7 +264,7 @@ void GUIListBox::OnMouseMove(int x_, int y_) {
 
 void GUIListBox::OnResized() {
 	UpdateMetrics();
-	NotifyParentChanged();
+	MarkChanged();
 }
 
 void GUIListBox::UpdateMetrics() {
diff --git a/engines/ags/shared/gui/gui_main.cpp b/engines/ags/shared/gui/gui_main.cpp
index 93c596a8414..bff16be8178 100644
--- a/engines/ags/shared/gui/gui_main.cpp
+++ b/engines/ags/shared/gui/gui_main.cpp
@@ -63,6 +63,8 @@ void GUIMain::InitDefaults() {
 	ID = 0;
 	Name.Empty();
 	_flags = kGUIMain_DefFlags;
+	_hasChanged = true;
+	_hasControlsChanged = true;
 
 	X = 0;
 	Y = 0;
@@ -186,12 +188,21 @@ bool GUIMain::HasChanged() const {
 	return _hasChanged;
 }
 
+bool GUIMain::HasControlsChanged() const {
+	return _hasControlsChanged;
+}
+
 void GUIMain::MarkChanged() {
 	_hasChanged = true;
 }
 
+void GUIMain::MarkControlsChanged() {
+	_hasControlsChanged = true;
+}
+
 void GUIMain::ClearChanged() {
 	_hasChanged = false;
+	_hasControlsChanged = false;
 }
 
 void GUIMain::AddControl(GUIControlType type, int32_t id, GUIObject *control) {
@@ -321,7 +332,6 @@ void GUIMain::Poll(int mx, int my) {
 					_controls[MouseOverCtrl]->OnMouseMove(mx, my);
 				}
 			}
-			//MarkChanged(); // TODO: only do if anything really changed
 		} else if (MouseOverCtrl >= 0)
 			_controls[MouseOverCtrl]->OnMouseMove(mx, my);
 	}
@@ -466,7 +476,6 @@ void GUIMain::OnMouseButtonDown(int mx, int my) {
 	if (_controls[MouseOverCtrl]->OnMouseDown())
 		MouseOverCtrl = MOVER_MOUSEDOWNLOCKED;
 	_controls[MouseDownCtrl]->OnMouseMove(mx - X, my - Y);
-	//MarkChanged(); // TODO: only do if anything really changed
 }
 
 void GUIMain::OnMouseButtonUp() {
@@ -482,7 +491,6 @@ void GUIMain::OnMouseButtonUp() {
 
 	_controls[MouseDownCtrl]->OnMouseUp();
 	MouseDownCtrl = -1;
-	//MarkChanged(); // TODO: only do if anything really changed
 }
 
 void GUIMain::ReadFromFile(Stream *in, GuiVersion gui_version) {
@@ -660,32 +668,34 @@ void DrawTextAlignedHor(Bitmap *ds, const char *text, int font, color_t text_col
 void MarkAllGUIForUpdate() {
 	for (auto &gui : _GP(guis)) {
 		gui.MarkChanged();
+		for (int i = 0; i < gui.GetControlCount(); ++i)
+			gui.GetControl(i)->MarkChanged();
 	}
 }
 
 void MarkForFontUpdate(int font) {
 	for (auto &btn : _GP(guibuts)) {
 		if (btn.Font == font)
-			btn.NotifyParentChanged();
+			btn.MarkChanged();
 	}
 	for (auto &lbl : _GP(guilabels)) {
 		if (lbl.Font == font)
-			lbl.NotifyParentChanged();
+			lbl.MarkChanged();
 	}
 	for (auto &list : _GP(guilist)) {
 		if (list.Font == font)
-			list.NotifyParentChanged();
+			list.MarkChanged();
 	}
 	for (auto &tb : _GP(guitext)) {
 		if (tb.Font == font)
-			tb.NotifyParentChanged();
+			tb.MarkChanged();
 	}
 }
 
 void MarkSpecialLabelsForUpdate(GUILabelMacro macro) {
 	for (auto &lbl : _GP(guilabels)) {
 		if ((lbl.GetTextMacros() & macro) != 0) {
-			lbl.NotifyParentChanged();
+			lbl.MarkChanged();
 		}
 	}
 }
@@ -693,7 +703,7 @@ void MarkSpecialLabelsForUpdate(GUILabelMacro macro) {
 void MarkInventoryForUpdate(int char_id, bool is_player) {
 	for (auto &inv : _GP(guiinv)) {
 		if ((char_id < 0) || (inv.CharId == char_id) || (is_player && inv.CharId < 0)) {
-			inv.NotifyParentChanged();
+			inv.MarkChanged();
 		}
 	}
 }
diff --git a/engines/ags/shared/gui/gui_main.h b/engines/ags/shared/gui/gui_main.h
index 462bb70e359..c765f6aa51f 100644
--- a/engines/ags/shared/gui/gui_main.h
+++ b/engines/ags/shared/gui/gui_main.h
@@ -100,8 +100,10 @@ public:
 
 	// Tells if GUI has graphically changed recently
 	bool        HasChanged() const;
+	bool        HasControlsChanged() const;
 	// Manually marks GUI as graphically changed
 	void        MarkChanged();
+	void        MarkControlsChanged();
 	// Clears changed flag
 	void        ClearChanged();
 
@@ -196,6 +198,7 @@ public:
 private:
 	int32_t _flags;         // style and behavior flags
 	bool    _hasChanged;    // flag tells whether GUI has graphically changed recently
+	bool    _hasControlsChanged;
 
 	// Array of types and control indexes in global GUI object arrays;
 	// maps GUI child slots to actual controls and used for rebuilding Controls array
diff --git a/engines/ags/shared/gui/gui_object.cpp b/engines/ags/shared/gui/gui_object.cpp
index 4b238e45452..c2367983a7a 100644
--- a/engines/ags/shared/gui/gui_object.cpp
+++ b/engines/ags/shared/gui/gui_object.cpp
@@ -39,6 +39,7 @@ GUIObject::GUIObject() {
 	ZOrder = -1;
 	IsActivated = false;
 	_scEventCount = 0;
+	_hasChanged = true;
 }
 
 String GUIObject::GetScriptName() const {
@@ -97,7 +98,7 @@ void GUIObject::SetEnabled(bool on) {
 		Flags |= kGUICtrl_Enabled;
 	else
 		Flags &= ~kGUICtrl_Enabled;
-	NotifyParentChanged();
+	MarkChanged();
 }
 
 void GUIObject::SetTranslated(bool on) {
@@ -105,7 +106,7 @@ void GUIObject::SetTranslated(bool on) {
 		Flags |= kGUICtrl_Translated;
 	else
 		Flags &= ~kGUICtrl_Translated;
-	NotifyParentChanged();
+	MarkChanged();
 }
 
 void GUIObject::SetVisible(bool on) {
diff --git a/engines/ags/shared/gui/gui_object.h b/engines/ags/shared/gui/gui_object.h
index 2c75bbcfae9..7f25ca7f7ce 100644
--- a/engines/ags/shared/gui/gui_object.h
+++ b/engines/ags/shared/gui/gui_object.h
@@ -106,7 +106,10 @@ public:
 	// TODO: these members are currently public; hide them later
 public:
 	// Notifies parent GUI that this control has changed
-	void     NotifyParentChanged();
+	void     MarkChanged();
+
+	bool     HasChanged() const;
+	void     ClearChanged();
 
 	int32_t  Id;         // GUI object's identifier
 	int32_t  ParentId;   // id of parent GUI
@@ -123,6 +126,7 @@ public:
 
 protected:
 	uint32_t Flags;      // generic style and behavior flags
+	bool     _hasChanged;
 
 	// TODO: explicit event names & handlers for every event
 	int32_t  _scEventCount;                    // number of supported script events
diff --git a/engines/ags/shared/gui/gui_slider.cpp b/engines/ags/shared/gui/gui_slider.cpp
index 11e6197299c..33aab93524d 100644
--- a/engines/ags/shared/gui/gui_slider.cpp
+++ b/engines/ags/shared/gui/gui_slider.cpp
@@ -185,7 +185,7 @@ void GUISlider::OnMouseMove(int x, int y) {
 		Value = (int)(((float)(((Y + Height) - y) - 2) * (float)(MaxValue - MinValue)) / (float)_handleRange) + MinValue;
 
 	Value = Math::Clamp(Value, MinValue, MaxValue);
-	NotifyParentChanged();
+	MarkChanged();
 	IsActivated = true;
 }
 
diff --git a/engines/ags/shared/gui/gui_textbox.cpp b/engines/ags/shared/gui/gui_textbox.cpp
index 8e7a8b67ec8..29393a1ea44 100644
--- a/engines/ags/shared/gui/gui_textbox.cpp
+++ b/engines/ags/shared/gui/gui_textbox.cpp
@@ -83,7 +83,7 @@ void GUITextBox::OnKeyPress(const KeyInput &ki) {
 		return;
 	}
 
-	NotifyParentChanged();
+	MarkChanged();
 	// backspace, remove character
 	if (keycode == eAGSKeyCodeBackspace) {
 		Backspace(Text);


Commit: a476850123ecf2638636d42278d6f1c3189fefea
    https://github.com/scummvm/scummvm/commit/a476850123ecf2638636d42278d6f1c3189fefea
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:19-07:00

Commit Message:
AGS: Calculate visible graphic size of gui controls for textures

>From upstream a91bec5f3fa7f37187a084cabd0ab2289e609a6e

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/globals.cpp
    engines/ags/globals.h
    engines/ags/shared/gui/gui_button.cpp
    engines/ags/shared/gui/gui_button.h
    engines/ags/shared/gui/gui_label.cpp
    engines/ags/shared/gui/gui_label.h
    engines/ags/shared/gui/gui_listbox.cpp
    engines/ags/shared/gui/gui_listbox.h
    engines/ags/shared/gui/gui_main.cpp
    engines/ags/shared/gui/gui_main.h
    engines/ags/shared/gui/gui_object.h
    engines/ags/shared/gui/gui_slider.cpp
    engines/ags/shared/gui/gui_slider.h
    engines/ags/shared/util/geometry.cpp
    engines/ags/shared/util/geometry.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 93c7066f64f..915fbd9e1ef 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -381,6 +381,7 @@ void init_game_drawdata() {
 	}
 	_GP(guiobjbg).resize(guio_num);
 	_GP(guiobjbmp).resize(guio_num);
+	_GP(guiobjoff).resize(guio_num);
 }
 
 void dispose_game_drawdata() {
@@ -397,6 +398,7 @@ void dispose_game_drawdata() {
 	_GP(guiobjbg).clear();
 	_GP(guiobjbmp).clear();
 	_GP(guiobjbmpref).clear();
+	_GP(guiobjoff).clear();
 }
 
 static void dispose_debug_room_drawdata() {
@@ -1970,19 +1972,21 @@ void draw_gui_controls(GUIMain &gui) {
 			continue;
 		obj->ClearChanged();
 
+		Rect obj_surf = obj->CalcGraphicRect(GUI::Options.ClipControls);
 		if (_GP(guiobjbg)[draw_index] == nullptr ||
-			_GP(guiobjbg)[draw_index]->GetSize() != Size(obj->Width, obj->Height)) {
+			_GP(guiobjbg)[draw_index]->GetSize() != obj_surf.GetSize()) {
 			recreate_drawobj_bitmap(_GP(guiobjbg)[draw_index], _GP(guiobjbmp)[draw_index],
-				obj->Width, obj->Height);
+				obj_surf.GetWidth(), obj_surf.GetHeight());
 		}
 
 		_GP(guiobjbg)[draw_index]->ClearTransparent();
-		obj->Draw(_GP(guiobjbg)[draw_index]);
+		obj->Draw(_GP(guiobjbg)[draw_index], obj->X - obj_surf.Left, obj->Y - obj_surf.Top);
 
 		if (_GP(guiobjbmp)[draw_index] != nullptr)
 			_G(gfxDriver)->UpdateDDBFromBitmap(_GP(guiobjbmp)[draw_index], _GP(guiobjbg)[draw_index], obj->HasAlphaChannel());
 		else
 			_GP(guiobjbmp)[draw_index] = _G(gfxDriver)->CreateDDBFromBitmap(_GP(guiobjbg)[draw_index], obj->HasAlphaChannel());
+		_GP(guiobjoff)[draw_index] = Point(obj_surf.GetLT());
 	}
 }
 
@@ -1991,11 +1995,9 @@ void draw_gui_and_overlays() {
 	// Draw gui controls on separate textures if:
 	// - it is a 3D renderer (software one may require adjustments -- needs testing)
 	// - not legacy alpha blending (may we implement specific texture blend?)
-	// - gui controls clipping is on (need to implement content size calc for all controls)
 	const bool draw_controls_as_textures =
 		_G(gfxDriver)->HasAcceleratedTransform()
-		&& (_GP(game).options[OPT_NEWGUIALPHA] == kGuiAlphaRender_Proper)
-		&& (_GP(game).options[OPT_CLIPGUICONTROLS] != 0);
+		&& (_GP(game).options[OPT_NEWGUIALPHA] == kGuiAlphaRender_Proper);
 
 	if (pl_any_want_hook(AGSE_PREGUIDRAW))
 		add_render_stage(AGSE_PREGUIDRAW);
@@ -2098,7 +2100,9 @@ void draw_gui_and_overlays() {
 						continue;
 					_GP(guiobjbmp)[draw_index + obj_id]->SetTransparency(_GP(guis)[aa].Transparency);
 					add_to_sprite_list(_GP(guiobjbmp)[draw_index + obj_id],
-						_GP(guis)[aa].X + obj->X, _GP(guis)[aa].Y + obj->Y, _GP(guis)[aa].ZOrder, false);
+						_GP(guis)[aa].X + _GP(guiobjoff)[draw_index + obj_id].X,
+						_GP(guis)[aa].Y + _GP(guiobjoff)[draw_index + obj_id].Y,
+						_GP(guis)[aa].ZOrder, false);
 				}
 			}
 		}
diff --git a/engines/ags/globals.cpp b/engines/ags/globals.cpp
index 4a6f71c4f05..5201df62621 100644
--- a/engines/ags/globals.cpp
+++ b/engines/ags/globals.cpp
@@ -190,6 +190,7 @@ Globals::Globals() {
 
 	_guiobjbg = new std::vector<Shared::Bitmap *>();
 	_guiobjbmp = new std::vector<Engine::IDriverDependantBitmap *>();
+	_guiobjoff = new std::vector<Point>();
 	_guiobjbmpref = new std::vector<int>();
 
 	// draw_software.cpp globals
@@ -443,6 +444,7 @@ Globals::~Globals() {
 	delete _maincoltable;
 	delete _guiobjbg;
 	delete _guiobjbmp;
+	delete _guiobjoff;
 	delete _guiobjbmpref;
 
 	// draw_software.cpp globals
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index 3dabde6a970..bc893abc87e 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -610,6 +610,7 @@ public:
 	// GUI control surfaces
 	std::vector<Shared::Bitmap *> *_guiobjbg;
 	std::vector<Engine::IDriverDependantBitmap *> *_guiobjbmp;
+	std::vector<Point> *_guiobjoff; // because surface may be larger than logical position
 	std::vector<int> *_guiobjbmpref; // first control texture index of each GUI
 
 	/**@}*/
diff --git a/engines/ags/shared/gui/gui_button.cpp b/engines/ags/shared/gui/gui_button.cpp
index 9a00df229ae..79f33f64dcc 100644
--- a/engines/ags/shared/gui/gui_button.cpp
+++ b/engines/ags/shared/gui/gui_button.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "ags/shared/ac/sprite_cache.h"
+#include "ags/shared/ac/game_struct_defines.h"
 #include "ags/shared/gui/gui_button.h"
 #include "ags/shared/gui/gui_main.h" // TODO: extract helper functions
 #include "ags/shared/util/stream.h"
@@ -74,7 +75,7 @@ GUIButton::GUIButton() {
 	IsPushed = false;
 	IsMouseOver = false;
 	_placeholder = kButtonPlace_None;
-	_unnamed = false;
+	_unnamed = true;
 
 	_scEventCount = 1;
 	_scEventNames[0] = "Click";
@@ -93,6 +94,47 @@ bool GUIButton::IsClippingImage() const {
 	return (Flags & kGUICtrl_Clip) != 0;
 }
 
+Rect GUIButton::CalcGraphicRect(bool clipped) {
+	if (clipped)
+		return RectWH(X, Y, Width, Height);
+	// TODO: need to find a way to cache image and text position, or there'll be some repetition
+	Rect rc = RectWH(X, Y, Width, Height);
+	if (IsImageButton()) {
+		if (IsClippingImage())
+			return rc;
+		// Main button graphic
+		if (_GP(spriteset)[CurrentImage] != nullptr)
+			rc = SumRects(rc, RectWH(X, Y, get_adjusted_spritewidth(CurrentImage), get_adjusted_spriteheight(CurrentImage)));
+		// Optionally merge with the inventory pic
+		if (_placeholder != kButtonPlace_None && _G(gui_inv_pic) >= 0) {
+			Size inv_sz = Size(get_adjusted_spritewidth(_G(gui_inv_pic)), get_adjusted_spriteheight(_G(gui_inv_pic)));
+			GUIButtonPlaceholder place = _placeholder;
+			if (place == kButtonPlace_InvItemAuto) {
+				place = ((inv_sz.Width > Width - 6) || (inv_sz.Height > Height - 6)) ?
+					kButtonPlace_InvItemStretch : kButtonPlace_InvItemCenter;
+			}
+
+			Rect inv_rc = (place == kButtonPlace_InvItemStretch) ?
+				RectWH(X + 3, Y + 3, Width - 6, Height - 6) :
+				RectWH(X + Width / 2 - inv_sz.Width / 2,
+					Y + Height / 2 - inv_sz.Height / 2,
+					inv_sz.Width, inv_sz.Height);
+			rc = SumRects(rc, inv_rc);
+		}
+	}
+	// Optionally merge with the button text
+	if (!IsImageButton() || ((_placeholder == kButtonPlace_None) && !_unnamed)) {
+		PrepareTextToDraw();
+		Rect frame = RectWH(X + 2, Y + 2, Width - 4, Height - 4);
+		if (IsPushed && IsMouseOver) {
+			frame.Left++;
+			frame.Top++;
+		}
+		rc = SumRects(rc, GUI::CalcTextPosition(_textToDraw.GetCStr(), Font, frame, TextAlignment));
+	}
+	return rc;
+}
+
 void GUIButton::Draw(Bitmap *ds, int x, int y) {
 	bool draw_disabled = !IsGUIEnabled(this);
 
@@ -109,8 +151,7 @@ void GUIButton::Draw(Bitmap *ds, int x, int y) {
 		// buttons off when disabled - no point carrying on
 		return;
 
-	// CHECKME: why testing both CurrentImage and Image?
-	if (CurrentImage > 0 && IsImageButton())
+	if (IsImageButton())
 		DrawImageButton(ds, x, y, draw_disabled);
 	// CHECKME: why don't draw frame if no Text? this will make button completely invisible!
 	else if (!_text.IsEmpty())
@@ -141,7 +182,7 @@ void GUIButton::SetText(const String &text) {
 		_placeholder = kButtonPlace_None;
 
 	// TODO: find a way to remove this bogus limitation ("New Button" is a valid Text too)
-	_unnamed = _text.Compare("New Button") == 0;
+	_unnamed = _text.IsEmpty() || _text.Compare("New Button") == 0;
 	MarkChanged();
 }
 
@@ -174,13 +215,12 @@ void GUIButton::OnMouseLeave() {
 }
 
 void GUIButton::OnMouseUp() {
-	int new_image;
+	int new_image = Image;
 	if (IsMouseOver) {
-		new_image = MouseOverImage;
+		if (MouseOverImage > 0)
+			new_image = MouseOverImage;
 		if (IsGUIEnabled(this) && IsClickable())
 			IsActivated = true;
-	} else {
-		new_image = Image;
 	}
 
 	if ((CurrentImage != new_image) || (IsPushed && !IsImageButton())) {
@@ -291,23 +331,20 @@ void GUIButton::DrawImageButton(Bitmap *ds, int x, int y, bool draw_disabled) {
 
 	// Draw active inventory item
 	if (_placeholder != kButtonPlace_None && _G(gui_inv_pic) >= 0) {
+		Size inv_sz = Size(get_adjusted_spritewidth(_G(gui_inv_pic)), get_adjusted_spriteheight(_G(gui_inv_pic)));
 		GUIButtonPlaceholder place = _placeholder;
 		if (place == kButtonPlace_InvItemAuto) {
-			if ((get_adjusted_spritewidth(_G(gui_inv_pic)) > Width - 6) ||
-				(get_adjusted_spriteheight(_G(gui_inv_pic)) > Height - 6)) {
-				place = kButtonPlace_InvItemStretch;
-			} else {
-				place = kButtonPlace_InvItemCenter;
-			}
+			place = ((inv_sz.Width > Width - 6) || (inv_sz.Height > Height - 6)) ?
+				kButtonPlace_InvItemStretch : kButtonPlace_InvItemCenter;
 		}
 
 		if (place == kButtonPlace_InvItemStretch) {
 			ds->StretchBlt(_GP(spriteset)[_G(gui_inv_pic)], RectWH(x + 3, y + 3, Width - 6, Height - 6),
 				kBitmap_Transparency);
-		} else if (place == kButtonPlace_InvItemCenter) {
+		} else {
 			draw_gui_sprite(ds, _G(gui_inv_pic),
-				x + Width / 2 - get_adjusted_spritewidth(_G(gui_inv_pic)) / 2,
-				y + Height / 2 - get_adjusted_spriteheight(_G(gui_inv_pic)) / 2,
+				x + Width / 2 - inv_sz.Width / 2,
+				y + Height / 2 - inv_sz.Height / 2,
 				true);
 		}
 	}
@@ -320,7 +357,7 @@ void GUIButton::DrawImageButton(Bitmap *ds, int x, int y, bool draw_disabled) {
 	}
 
 	// Don't print Text of (INV) (INVSHR) (INVNS)
-	if (_placeholder == kButtonPlace_None && !_unnamed)
+	if ((_placeholder == kButtonPlace_None) && !_unnamed)
 		DrawText(ds, x, y, draw_disabled);
 
 	if (IsClippingImage() && !GUI::Options.ClipControls)
@@ -328,8 +365,6 @@ void GUIButton::DrawImageButton(Bitmap *ds, int x, int y, bool draw_disabled) {
 }
 
 void GUIButton::DrawText(Bitmap *ds, int x, int y, bool draw_disabled) {
-	if (_text.IsEmpty())
-		return;
 	// TODO: need to find a way to cache Text prior to drawing;
 	// but that will require to update all gui controls when translation is changed in game
 	PrepareTextToDraw();
diff --git a/engines/ags/shared/gui/gui_button.h b/engines/ags/shared/gui/gui_button.h
index df154a267fc..8390a7c569a 100644
--- a/engines/ags/shared/gui/gui_button.h
+++ b/engines/ags/shared/gui/gui_button.h
@@ -67,6 +67,7 @@ public:
 	bool IsClippingImage() const;
 
 	// Operations
+	Rect CalcGraphicRect(bool clipped) override;
 	void Draw(Bitmap *ds, int x = 0, int y = 0) override;
 	void SetClipImage(bool on);
 	void SetText(const String &text);
diff --git a/engines/ags/shared/gui/gui_label.cpp b/engines/ags/shared/gui/gui_label.cpp
index a396d040b9b..26c0c7eafb3 100644
--- a/engines/ags/shared/gui/gui_label.cpp
+++ b/engines/ags/shared/gui/gui_label.cpp
@@ -19,6 +19,7 @@
  *
  */
 
+#include "ags/lib/std/algorithm.h"
 #include "ags/shared/ac/game_version.h"
 #include "ags/shared/font/fonts.h"
 #include "ags/shared/gui/gui_label.h"
@@ -48,6 +49,35 @@ GUILabelMacro GUILabel::GetTextMacros() const {
 	return _textMacro;
 }
 
+Rect GUILabel::CalcGraphicRect(bool clipped) {
+	if (clipped)
+		return RectWH(X, Y, Width, Height);
+	// TODO: need to find a way to text position, or there'll be some repetition
+	// have to precache text and size on some events:
+	// - translation change
+	// - macro value change (score, overhotspot etc)
+	Rect rc = RectWH(X, Y, Width, Height);
+	PrepareTextToDraw();
+	if (SplitLinesForDrawing(_GP(Lines)) == 0)
+		return rc;
+	const int linespacing = // Older engine labels used (font height + 1) as linespacing for some reason
+		((_G(loaded_game_file_version) < kGameVersion_360) && (get_font_flags(Font) & FFLG_DEFLINESPACING)) ?
+		(get_font_height(Font) + 1) :
+		get_font_linespacing(Font);
+	// < 2.72 labels did not limit vertical size of text
+	const bool limit_by_label_frame = _G(loaded_game_file_version) >= kGameVersion_272;
+	int at_y = 0;
+	Line max_line;
+	for (size_t i = 0;
+		i < _GP(Lines).Count() && (!limit_by_label_frame || at_y <= Height);
+		++i, at_y += linespacing) {
+		Line lpos = GUI::CalcTextPositionHor(_GP(Lines)[i].GetCStr(), Font, 0, 0 + Width - 1, at_y,
+			(FrameAlignment)TextAlignment);
+		max_line.X2 = std::max(max_line.X2, lpos.X2);
+	}
+	return SumRects(rc, RectWH(X, Y, max_line.X2 - max_line.X1 + 1, at_y - linespacing + get_font_surface_height(Font)));
+}
+
 void GUILabel::Draw(Bitmap *ds, int x, int y) {
 	// TODO: need to find a way to cache text prior to drawing;
 	// but that will require to update all gui controls when translation is changed in game
diff --git a/engines/ags/shared/gui/gui_label.h b/engines/ags/shared/gui/gui_label.h
index 7dc68c1700e..5a2333c9cc3 100644
--- a/engines/ags/shared/gui/gui_label.h
+++ b/engines/ags/shared/gui/gui_label.h
@@ -43,6 +43,7 @@ public:
 	GUILabelMacro GetTextMacros() const;
 
 	// Operations
+	Rect CalcGraphicRect(bool clipped) override;
 	void Draw(Bitmap *ds, int x = 0, int y = 0) override;
 	void SetText(const String &text);
 
diff --git a/engines/ags/shared/gui/gui_listbox.cpp b/engines/ags/shared/gui/gui_listbox.cpp
index 6926552c1f1..92ff4b3f552 100644
--- a/engines/ags/shared/gui/gui_listbox.cpp
+++ b/engines/ags/shared/gui/gui_listbox.cpp
@@ -19,6 +19,7 @@
  *
  */
 
+#include "ags/lib/std/algorithm.h"
 #include "ags/shared/gui/gui_listbox.h"
 #include "ags/shared/ac/game_version.h"
 #include "ags/shared/font/fonts.h"
@@ -76,6 +77,34 @@ bool GUIListBox::IsInRightMargin(int x) const {
 	return 0;
 }
 
+Rect GUIListBox::CalcGraphicRect(bool clipped) {
+	if (clipped)
+		return RectWH(X, Y, Width, Height);
+	// TODO: need to find a way to text position, or there'll be some repetition
+	// have to precache text and size on some events:
+	// - translation change
+	// - macro value change (score, overhotspot etc)
+	Rect rc = RectWH(X, Y, Width, Height);
+	UpdateMetrics();
+	const int width = Width - 1;
+	const int height = Height - 1;
+	const int pixel_size = get_fixed_pixel_size(1);
+	int right_hand_edge = width - pixel_size - 1;
+	// calculate the scroll bar's width if necessary
+	if (ItemCount > VisibleItemCount &&IsBorderShown() && AreArrowsShown())
+		right_hand_edge -= get_fixed_pixel_size(7);
+	Line max_line;
+	for (int item = 0; (item < VisibleItemCount) && (item + TopItem < ItemCount); ++item) {
+		int at_y = pixel_size + item * RowHeight;
+		int item_index = item + TopItem;
+		PrepareTextToDraw(Items[item_index]);
+		Line lpos = GUI::CalcTextPositionHor(_textToDraw.GetCStr(), Font, 1 + pixel_size, right_hand_edge, at_y + 1,
+			(FrameAlignment)TextAlignment);
+		max_line.X2 = std::max(max_line.X2, lpos.X2);
+	}
+	return SumRects(rc, RectWH(X, Y, max_line.X2 - max_line.X1 + 1, Height));
+}
+
 int GUIListBox::AddItem(const String &text) {
 	Items.push_back(text);
 	SavedGameIndex.push_back(-1);
@@ -139,10 +168,7 @@ void GUIListBox::Draw(Bitmap *ds, int x, int y) {
 	Rect old_clip = ds->GetClip();
 	if (scrollbar && GUI::Options.ClipControls)
 		ds->SetClip(Rect(x, y, right_hand_edge + 1, y + Height - 1));
-	for (int item = 0; item < VisibleItemCount; ++item) {
-		if (item + TopItem >= ItemCount)
-			break;
-
+	for (int item = 0; (item < VisibleItemCount) && (item + TopItem < ItemCount); ++item) {
 		int at_y = y + pixel_size + item * RowHeight;
 		if (item + TopItem == SelectedItem) {
 			text_color = ds->GetCompatibleColor(SelectedTextColor);
diff --git a/engines/ags/shared/gui/gui_listbox.h b/engines/ags/shared/gui/gui_listbox.h
index 8a712fddafe..6fc08cda7ee 100644
--- a/engines/ags/shared/gui/gui_listbox.h
+++ b/engines/ags/shared/gui/gui_listbox.h
@@ -43,6 +43,7 @@ public:
 	// Operations
 	int  AddItem(const String &text);
 	void Clear();
+	Rect CalcGraphicRect(bool clipped) override;
 	void Draw(Bitmap *ds, int x = 0, int y = 0) override;
 	int  InsertItem(int index, const String &text);
 	void RemoveItem(int index);
diff --git a/engines/ags/shared/gui/gui_main.cpp b/engines/ags/shared/gui/gui_main.cpp
index bff16be8178..bf6e35c106f 100644
--- a/engines/ags/shared/gui/gui_main.cpp
+++ b/engines/ags/shared/gui/gui_main.cpp
@@ -643,6 +643,21 @@ namespace GUI {
 
 GuiVersion GameGuiVersion = kGuiVersion_Initial;
 
+Rect CalcTextPosition(const char *text, int font, const Rect &frame, FrameAlignment align) {
+	int use_height = (_G(loaded_game_file_version) < kGameVersion_360_21) ?
+		get_font_height(font) + ((align & kMAlignVCenter) ? 1 : 0) :
+		get_font_height_outlined(font);
+	Rect rc = AlignInRect(frame, RectWH(0, 0, get_text_width_outlined(text, font), use_height), align);
+	rc.SetHeight(get_font_surface_height(font));
+	return rc;
+}
+
+Line CalcTextPositionHor(const char *text, int font, int x1, int x2, int y, FrameAlignment align) {
+	int w = get_text_width_outlined(text, font);
+	int x = AlignInHRange(x1, x2, 0, w, align);
+	return Line(x, y, x + w - 1, y);
+}
+
 void DrawDisabledEffect(Bitmap *ds, const Rect &rc) {
 	color_t draw_color = ds->GetCompatibleColor(8);
 	for (int at_x = rc.Left; at_x <= rc.Right; ++at_x) {
@@ -653,16 +668,13 @@ void DrawDisabledEffect(Bitmap *ds, const Rect &rc) {
 }
 
 void DrawTextAligned(Bitmap *ds, const char *text, int font, color_t text_color, const Rect &frame, FrameAlignment align) {
-	int text_height = (_G(loaded_game_file_version) < kGameVersion_360_21) ?
-		get_font_height(font) + ((align & kMAlignVCenter) ? 1 : 0) :
-		get_font_height_outlined(font);
-	Rect item = AlignInRect(frame, RectWH(0, 0, get_text_width_outlined(text, font), text_height), align);
+	Rect item = CalcTextPosition(text, font, frame, align);
 	wouttext_outline(ds, item.Left, item.Top, font, text_color, text);
 }
 
 void DrawTextAlignedHor(Bitmap *ds, const char *text, int font, color_t text_color, int x1, int x2, int y, FrameAlignment align) {
-	int x = AlignInHRange(x1, x2, 0, get_text_width_outlined(text, font), align);
-	wouttext_outline(ds, x, y, font, text_color, text);
+	Line line = CalcTextPositionHor(text, font, x1, x2, y, align);
+	wouttext_outline(ds, line.X1, y, font, text_color, text);
 }
 
 void MarkAllGUIForUpdate() {
diff --git a/engines/ags/shared/gui/gui_main.h b/engines/ags/shared/gui/gui_main.h
index c765f6aa51f..91da669de22 100644
--- a/engines/ags/shared/gui/gui_main.h
+++ b/engines/ags/shared/gui/gui_main.h
@@ -215,6 +215,10 @@ namespace GUI {
 extern GuiVersion GameGuiVersion;
 extern GuiOptions Options;
 
+// Calculates the text's graphical position, given the alignment
+Rect CalcTextPosition(const char *text, int font, const Rect &frame, FrameAlignment align);
+// Calculates the text's graphical position, given the horizontal alignment
+Line CalcTextPositionHor(const char *text, int font, int x1, int x2, int y, FrameAlignment align);
 // Draw standart "shading" effect over rectangle
 void DrawDisabledEffect(Bitmap *ds, const Rect &rc);
 // Draw text aligned inside rectangle
diff --git a/engines/ags/shared/gui/gui_object.h b/engines/ags/shared/gui/gui_object.h
index 7f25ca7f7ce..d7d857c2985 100644
--- a/engines/ags/shared/gui/gui_object.h
+++ b/engines/ags/shared/gui/gui_object.h
@@ -66,6 +66,11 @@ public:
 	virtual bool    HasAlphaChannel() const;
 
 	// Operations
+	// Returns the (untransformed!) visual rectangle of this control,
+	// optionally clipped by the logical position
+	virtual Rect    CalcGraphicRect(bool clipped) {
+		return RectWH(X, Y, Width, Height);
+	}
 	virtual void    Draw(Bitmap *ds, int x = 0, int y = 0) {
 		(void)ds; (void)x; (void)y;
 	}
diff --git a/engines/ags/shared/gui/gui_slider.cpp b/engines/ags/shared/gui/gui_slider.cpp
index 33aab93524d..d8445428688 100644
--- a/engines/ags/shared/gui/gui_slider.cpp
+++ b/engines/ags/shared/gui/gui_slider.cpp
@@ -19,6 +19,7 @@
  *
  */
 
+#include "ags/lib/std/algorithm.h"
 #include "ags/shared/ac/sprite_cache.h"
 #include "ags/shared/gui/gui_main.h"
 #include "ags/shared/gui/gui_slider.h"
@@ -56,7 +57,22 @@ bool GUISlider::IsOverControl(int x, int y, int leeway) const {
 	return _cachedHandle.IsInside(Point(x, y));
 }
 
-void GUISlider::Draw(Bitmap *ds, int x, int y) {
+Rect GUISlider::CalcGraphicRect(bool clipped) {
+	// Sliders are never clipped as of 3.6.0
+	// TODO: precalculate everything on width/height/graphic change!!
+	UpdateMetrics();
+	Rect logical = RectWH(X, Y, Width, Height);
+	Rect bar = Rect::MoveBy(_cachedBar, X, Y);
+	Rect handle = Rect::MoveBy(_cachedHandle, X, Y);
+	return Rect(
+		std::min(std::min(logical.Left, bar.Left), handle.Left),
+		std::min(std::min(logical.Top, bar.Top), handle.Top),
+		std::max(std::max(logical.Right, bar.Right), handle.Right),
+		std::max(std::max(logical.Bottom, bar.Bottom), handle.Bottom)
+	);
+}
+
+void GUISlider::UpdateMetrics() {
 	// Clamp Value
 	// TODO: this is necessary here because some Slider fields are still public
 	if (MinValue >= MaxValue)
@@ -95,7 +111,7 @@ void GUISlider::Draw(Bitmap *ds, int x, int y) {
 	if (IsHorizontal()) // horizontal slider
 	{
 		// Value pos is a coordinate corresponding to current slider's value
-		bar = RectWH(x + 1, y + Height / 2 - thick_f, Width - 1, bar_thick);
+		bar = RectWH(1, Height / 2 - thick_f, Width - 1, bar_thick);
 		handle_range = Width - 4;
 		int value_pos = (int)(((float)(Value - MinValue) * (float)handle_range) / (float)(MaxValue - MinValue));
 		handle = RectWH((bar.Left + get_fixed_pixel_size(2)) - (handle_sz.Width / 2) + 1 + value_pos - 2,
@@ -105,7 +121,7 @@ void GUISlider::Draw(Bitmap *ds, int x, int y) {
 	}
 	// vertical slider
 	else {
-		bar = RectWH(x + Width / 2 - thick_f, y + 1, bar_thick, Height - 1);
+		bar = RectWH(Width / 2 - thick_f, 1, bar_thick, Height - 1);
 		handle_range = Height - 4;
 		int value_pos = (int)(((float)(MaxValue - Value) * (float)handle_range) / (float)(MaxValue - MinValue));
 		handle = RectWH(bar.Left + (bar.GetWidth() - handle_sz.Width) / 2,
@@ -114,6 +130,17 @@ void GUISlider::Draw(Bitmap *ds, int x, int y) {
 		handle.MoveToX(handle.Left + data_to_game_coord(HandleOffset));
 	}
 
+	_cachedBar = bar;
+	_cachedHandle = handle;
+	_handleRange = handle_range;
+}
+
+void GUISlider::Draw(Bitmap *ds, int x, int y) {
+	UpdateMetrics();
+
+	Rect bar = Rect::MoveBy(_cachedBar, x, y);
+	Rect handle = Rect::MoveBy(_cachedHandle, x, y);
+
 	color_t draw_color;
 	if (BgImage > 0) {
 		// tiled image as slider background
@@ -164,9 +191,6 @@ void GUISlider::Draw(Bitmap *ds, int x, int y) {
 		ds->DrawLine(Line(handle.Right, handle.Top + 1, handle.Right, handle.Bottom), draw_color);
 		ds->DrawLine(Line(handle.Left + 1, handle.Bottom, handle.Right, handle.Bottom), draw_color);
 	}
-
-	_cachedHandle = handle;
-	_handleRange = handle_range;
 }
 
 bool GUISlider::OnMouseDown() {
diff --git a/engines/ags/shared/gui/gui_slider.h b/engines/ags/shared/gui/gui_slider.h
index 59ffbf07a55..671afad38f6 100644
--- a/engines/ags/shared/gui/gui_slider.h
+++ b/engines/ags/shared/gui/gui_slider.h
@@ -33,14 +33,15 @@ class GUISlider : public GUIObject {
 public:
 	GUISlider();
 
-	// Compatibility: sliders are not clipped as of 3.6.0
-	bool IsContentClipped() const override { return false; }
-
 	// Tells if the slider is horizontal (otherwise - vertical)
 	bool IsHorizontal() const;
 	bool IsOverControl(int x, int y, int leeway) const override;
 
+	// Compatibility: sliders are not clipped as of 3.6.0
+	bool IsContentClipped() const override { return false; }
+
 	// Operations
+	Rect CalcGraphicRect(bool clipped) override;
 	void Draw(Bitmap *ds, int x = 0, int y = 0) override;
 
 	// Events
@@ -65,8 +66,12 @@ public:
 	bool    IsMousePressed;
 
 private:
-	// The following variables are not persisted on disk
-	// Cached coordinates of slider handle
+	// Updates dynamic metrics and positions of elements
+	void UpdateMetrics();
+
+	// Cached coordinates of slider bar; in relative coords
+	Rect    _cachedBar;
+	// Cached coordinates of slider handle; in relative coords
 	Rect    _cachedHandle;
 	// The length of the handle movement range, in pixels
 	int     _handleRange;
diff --git a/engines/ags/shared/util/geometry.cpp b/engines/ags/shared/util/geometry.cpp
index 5da59792361..b0a88abee58 100644
--- a/engines/ags/shared/util/geometry.cpp
+++ b/engines/ags/shared/util/geometry.cpp
@@ -24,7 +24,8 @@
 
 namespace AGS3 {
 
-bool AreRectsIntersecting(const Rect &r1, const Rect &r2) { // NOTE: remember that in AGS Y axis is pointed downwards
+bool AreRectsIntersecting(const Rect &r1, const Rect &r2) {
+	// NOTE: remember that in AGS Y axis is pointed downwards (top < bottom)
 	return r1.Left <= r2.Right && r1.Right >= r2.Left &&
 	       r1.Top <= r2.Bottom && r1.Bottom >= r2.Top;
 }
@@ -119,4 +120,14 @@ Rect PlaceInRect(const Rect &place, const Rect &item, const RectPlacement &place
 	}
 }
 
+Rect SumRects(const Rect &r1, const Rect &r2) { // NOTE: remember that in AGS Y axis is pointed downwards (top < bottom)
+	return Rect(std::min(r1.Left, r2.Left), std::min(r1.Top, r2.Top),
+		std::max(r1.Right, r2.Right), std::max(r1.Bottom, r2.Bottom));
+}
+
+Rect IntersectRects(const Rect &r1, const Rect &r2) { // NOTE: the result may be empty (negative) rect if there's no intersection
+	return Rect(std::max(r1.Left, r2.Left), std::max(r1.Top, r2.Top),
+		std::min(r1.Right, r2.Right), std::min(r1.Bottom, r2.Bottom));
+}
+
 } // namespace AGS3
diff --git a/engines/ags/shared/util/geometry.h b/engines/ags/shared/util/geometry.h
index 5e5653cae89..451c8892193 100644
--- a/engines/ags/shared/util/geometry.h
+++ b/engines/ags/shared/util/geometry.h
@@ -364,6 +364,10 @@ Rect OffsetRect(const Rect &r, const Point off);
 Rect CenterInRect(const Rect &place, const Rect &item);
 Rect ClampToRect(const Rect &place, const Rect &item);
 Rect PlaceInRect(const Rect &place, const Rect &item, const RectPlacement &placement);
+// Sum two rectangles, the result is the rectangle bounding them both
+Rect SumRects(const Rect &r1, const Rect &r2);
+// Intersect two rectangles, the resolt is the rectangle bounding their intersection
+Rect IntersectRects(const Rect &r1, const Rect &r2);
 
 //} // namespace Shared
 //} // namespace AGS


Commit: 9fff1dc1fa6db3eb3640cdaa01b149c539b5d188
    https://github.com/scummvm/scummvm/commit/9fff1dc1fa6db3eb3640cdaa01b149c539b5d188
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:19-07:00

Commit Message:
AGS: Separate sprite lists and batch lists in renderers

>From upstream ccbf3d16788720e94e5362ff8aeae1e32b4febe5

Changed paths:
    engines/ags/engine/gfx/ali_3d_scummvm.cpp
    engines/ags/engine/gfx/ali_3d_scummvm.h
    engines/ags/engine/gfx/gfx_driver_base.h


diff --git a/engines/ags/engine/gfx/ali_3d_scummvm.cpp b/engines/ags/engine/gfx/ali_3d_scummvm.cpp
index dc13c9f6ff4..79efe05f1ac 100644
--- a/engines/ags/engine/gfx/ali_3d_scummvm.cpp
+++ b/engines/ags/engine/gfx/ali_3d_scummvm.cpp
@@ -234,7 +234,7 @@ void ScummVMRendererGraphicsDriver::InitSpriteBatch(size_t index, const SpriteBa
 	if (_spriteBatches.size() <= index)
 		_spriteBatches.resize(index + 1);
 	ALSpriteBatch &batch = _spriteBatches[index];
-	batch.List.clear();
+	batch.ID = index;
 	// TODO: correct offsets to have pre-scale (source) and post-scale (dest) offsets!
 	const int src_w = desc.Viewport.GetWidth() / desc.Transform.ScaleX;
 	const int src_h = desc.Viewport.GetHeight() / desc.Transform.ScaleY;
@@ -271,12 +271,12 @@ void ScummVMRendererGraphicsDriver::InitSpriteBatch(size_t index, const SpriteBa
 }
 
 void ScummVMRendererGraphicsDriver::ResetAllBatches() {
-	for (ALSpriteBatches::iterator it = _spriteBatches.begin(); it != _spriteBatches.end(); ++it)
-		it->List.clear();
+	_spriteBatches.clear();
+	_spriteList.clear();
 }
 
 void ScummVMRendererGraphicsDriver::DrawSprite(int x, int y, IDriverDependantBitmap *bitmap) {
-	_spriteBatches[_actSpriteBatch].List.push_back(ALDrawListEntry((ALSoftwareBitmap *)bitmap, x, y));
+	_spriteList.push_back(ALDrawListEntry((ALSoftwareBitmap *)bitmap, _actSpriteBatch, x, y));
 }
 
 void ScummVMRendererGraphicsDriver::SetScreenFade(int /*red*/, int /*green*/, int /*blue*/) {
@@ -289,7 +289,8 @@ void ScummVMRendererGraphicsDriver::SetScreenTint(int red, int green, int blue)
 	_tint_green = green;
 	_tint_blue = blue;
 	if (((_tint_red > 0) || (_tint_green > 0) || (_tint_blue > 0)) && (_srcColorDepth > 8)) {
-		_spriteBatches[_actSpriteBatch].List.push_back(ALDrawListEntry((ALSoftwareBitmap *)0x1, 0, 0));
+		_spriteList.push_back(
+			ALDrawListEntry(reinterpret_cast<ALSoftwareBitmap *>(DRAWENTRY_TINT), _actSpriteBatch, 0, 0));
 	}
 }
 
@@ -305,10 +306,11 @@ void ScummVMRendererGraphicsDriver::RenderToBackBuffer() {
 	// that here would slow things down significantly, so if we ever go that way sprite caching will
 	// be required (similarily to how AGS caches flipped/scaled object sprites now for).
 	//
-	for (size_t i = 0; i <= _actSpriteBatch; ++i) {
-		const Rect &viewport = _spriteBatchDesc[i].Viewport;
-		const SpriteTransform &transform = _spriteBatchDesc[i].Transform;
-		const ALSpriteBatch &batch = _spriteBatches[i];
+	for (size_t cur_spr = 0; cur_spr < _spriteList.size();) {
+		const auto &batch_desc = _spriteBatchDesc[_spriteList[cur_spr].node];
+		const ALSpriteBatch &batch = _spriteBatches[_spriteList[cur_spr].node];
+		const Rect &viewport = batch_desc.Viewport;
+		const SpriteTransform &transform = batch_desc.Transform;
 
 		virtualScreen->SetClip(viewport);
 		Bitmap *surface = batch.Surface.get();
@@ -318,38 +320,38 @@ void ScummVMRendererGraphicsDriver::RenderToBackBuffer() {
 			if (!batch.Opaque)
 				surface->ClearTransparent();
 			_stageVirtualScreen = surface;
-			RenderSpriteBatch(batch, surface, transform.X, transform.Y);
+			cur_spr = RenderSpriteBatch(batch, cur_spr, surface, transform.X, transform.Y);
 			if (!batch.IsVirtualScreen)
 				virtualScreen->StretchBlt(surface, RectWH(view_offx, view_offy, viewport.GetWidth(), viewport.GetHeight()),
-				                          batch.Opaque ? kBitmap_Copy : kBitmap_Transparency);
+					batch.Opaque ? kBitmap_Copy : kBitmap_Transparency);
 		} else {
-			RenderSpriteBatch(batch, virtualScreen, view_offx + transform.X, view_offy + transform.Y);
+			cur_spr = RenderSpriteBatch(batch, cur_spr, virtualScreen, view_offx + transform.X, view_offy + transform.Y);
 		}
 		_stageVirtualScreen = virtualScreen;
 	}
 	ClearDrawLists();
 }
 
-void ScummVMRendererGraphicsDriver::RenderSpriteBatch(const ALSpriteBatch &batch, Shared::Bitmap *surface, int surf_offx, int surf_offy) {
-	const std::vector<ALDrawListEntry> &drawlist = batch.List;
-	for (size_t i = 0; i < drawlist.size(); i++) {
-		if (drawlist[i].bitmap == nullptr) {
+size_t ScummVMRendererGraphicsDriver::RenderSpriteBatch(const ALSpriteBatch &batch, size_t from, Bitmap *surface, int surf_offx, int surf_offy) {
+	for (; (from < _spriteList.size()) && (_spriteList[from].node == batch.ID); ++from) {
+		const auto &sprite = _spriteList[from];
+		if (sprite.ddb == nullptr) {
 			if (_nullSpriteCallback)
-				_nullSpriteCallback(drawlist[i].x, drawlist[i].y);
+				_nullSpriteCallback(sprite.x, sprite.y);
 			else
 				error("Unhandled attempt to draw null sprite");
 
 			continue;
-		} else if (drawlist[i].bitmap == (ALSoftwareBitmap *)0x1) {
+		} else if (sprite.ddb == reinterpret_cast<ALSoftwareBitmap *>(DRAWENTRY_TINT)) {
 			// draw screen tint fx
 			set_trans_blender(_tint_red, _tint_green, _tint_blue, 0);
 			surface->LitBlendBlt(surface, 0, 0, 128);
 			continue;
 		}
 
-		ALSoftwareBitmap *bitmap = drawlist[i].bitmap;
-		int drawAtX = drawlist[i].x + surf_offx;
-		int drawAtY = drawlist[i].y + surf_offy;
+		ALSoftwareBitmap *bitmap = sprite.ddb;
+		int drawAtX = sprite.x + surf_offx;
+		int drawAtY = sprite.y + surf_offy;
 
 		if (bitmap->_transparency >= 255) {
 		} // fully transparent, do nothing
@@ -370,9 +372,10 @@ void ScummVMRendererGraphicsDriver::RenderSpriteBatch(const ALSpriteBatch &batch
 		} else {
 			// here _transparency is used as alpha (between 1 and 254), but 0 means opaque!
 			GfxUtil::DrawSpriteWithTransparency(surface, bitmap->_bmp, drawAtX, drawAtY,
-			                                    bitmap->_transparency ? bitmap->_transparency : 255);
+				bitmap->_transparency ? bitmap->_transparency : 255);
 		}
 	}
+	return from;
 }
 
 void ScummVMRendererGraphicsDriver::copySurface(const Graphics::Surface &src, bool mode) {
diff --git a/engines/ags/engine/gfx/ali_3d_scummvm.h b/engines/ags/engine/gfx/ali_3d_scummvm.h
index 7023f4ca1e6..e4a2f29adb8 100644
--- a/engines/ags/engine/gfx/ali_3d_scummvm.h
+++ b/engines/ags/engine/gfx/ali_3d_scummvm.h
@@ -143,14 +143,13 @@ private:
 typedef SpriteDrawListEntry<ALSoftwareBitmap> ALDrawListEntry;
 // Software renderer's sprite batch
 struct ALSpriteBatch {
-	// List of sprites to render
-	std::vector<ALDrawListEntry> List;
+	uint32_t ID = 0;
 	// Intermediate surface which will be drawn upon and transformed if necessary
-	std::shared_ptr<Bitmap>      Surface;
+	std::shared_ptr<Bitmap> Surface;
 	// Whether surface is a virtual screen's region
-	bool                         IsVirtualScreen;
+	bool IsVirtualScreen = false;
 	// Tells whether the surface is treated as opaque or transparent
-	bool                         Opaque;
+	bool Opaque = false;
 };
 typedef std::vector<ALSpriteBatch> ALSpriteBatches;
 
@@ -181,10 +180,10 @@ public:
 	int  GetCompatibleBitmapFormat(int color_depth) override;
 	IDriverDependantBitmap *CreateDDB(int width, int height, int color_depth, bool opaque) override;
 	IDriverDependantBitmap *CreateDDBFromBitmap(Bitmap *bitmap, bool hasAlpha, bool opaque) override;
-	void UpdateDDBFromBitmap(IDriverDependantBitmap *bitmapToUpdate, Bitmap *bitmap, bool hasAlpha) override;
-	void DestroyDDB(IDriverDependantBitmap *bitmap) override;
+	void UpdateDDBFromBitmap(IDriverDependantBitmap *ddb, Bitmap *bitmap, bool hasAlpha) override;
+	void DestroyDDB(IDriverDependantBitmap *ddb) override;
 
-	void DrawSprite(int x, int y, IDriverDependantBitmap *bitmap) override;
+	void DrawSprite(int x, int y, IDriverDependantBitmap *ddb) override;
 	void SetScreenFade(int red, int green, int blue) override;
 	void SetScreenTint(int red, int green, int blue) override;
 
@@ -252,7 +251,10 @@ private:
 	Bitmap *_stageVirtualScreen;
 	int _tint_red, _tint_green, _tint_blue;
 
+	// Sprite batches (parent scene nodes)
 	ALSpriteBatches _spriteBatches;
+	// List of sprites to render
+	std::vector<ALDrawListEntry> _spriteList;
 
 	void InitSpriteBatch(size_t index, const SpriteBatchDesc &desc) override;
 	void ResetAllBatches() override;
@@ -263,7 +265,7 @@ private:
 	// Unset parameters and release resources related to the display mode
 	void ReleaseDisplayMode();
 	// Renders single sprite batch on the precreated surface
-	void RenderSpriteBatch(const ALSpriteBatch &batch, Shared::Bitmap *surface, int surf_offx, int surf_offy);
+	size_t RenderSpriteBatch(const ALSpriteBatch &batch, size_t from, Shared::Bitmap *surface, int surf_offx, int surf_offy);
 
 	void highcolor_fade_in(Bitmap *vs, void(*draw_callback)(), int offx, int offy, int speed, int targetColourRed, int targetColourGreen, int targetColourBlue);
 	void highcolor_fade_out(Bitmap *vs, void(*draw_callback)(), int offx, int offy, int speed, int targetColourRed, int targetColourGreen, int targetColourBlue);
diff --git a/engines/ags/engine/gfx/gfx_driver_base.h b/engines/ags/engine/gfx/gfx_driver_base.h
index 5c669b549ef..b1fbd460dbf 100644
--- a/engines/ags/engine/gfx/gfx_driver_base.h
+++ b/engines/ags/engine/gfx/gfx_driver_base.h
@@ -71,19 +71,15 @@ typedef std::vector<SpriteBatchDesc> SpriteBatchDescs;
 // The single sprite entry in the render list
 template<class T_DDB>
 struct SpriteDrawListEntry {
-	T_DDB *bitmap; // TODO: use shared pointer?
-	int x, y; // sprite position, in camera coordinates
-	bool skip;
-
-	SpriteDrawListEntry()
-		: bitmap(nullptr)
-		, x(0)
-		, y(0)
-		, skip(false) {
-	}
-
-	SpriteDrawListEntry(T_DDB *ddb, int x_ = 0, int y_ = 0)
-		: bitmap(ddb)
+	T_DDB *ddb = nullptr; // TODO: use shared pointer?
+	uint32_t node = 0; // sprite batch / scene node index
+	int x = 0, y = 0; // sprite position, in local batch / node coordinates
+	bool skip = false;
+
+	SpriteDrawListEntry() = default;
+	SpriteDrawListEntry(T_DDB * ddb_, uint32_t node_, int x_, int y_)
+		: ddb(ddb_)
+		, node(node_)
 		, x(x_)
 		, y(y_)
 		, skip(false) {
@@ -123,6 +119,11 @@ public:
 	}
 
 protected:
+	// Special internal values, applied to DrawListEntry
+	static const intptr_t DRAWENTRY_STAGECALLBACK = 0x0;
+	static const intptr_t DRAWENTRY_FADE = 0x1;
+	static const intptr_t DRAWENTRY_TINT = 0x2;
+
 	// Called after graphics driver was initialized for use for the first time
 	virtual void OnInit();
 	// Called just before graphics mode is going to be uninitialized and its


Commit: 2b96f0ff67bf86b6218ad9f9deefe2de098d5614
    https://github.com/scummvm/scummvm/commit/2b96f0ff67bf86b6218ad9f9deefe2de098d5614
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:20-07:00

Commit Message:
AGS: Sprite batches may have parents

>From upstream 369b3985400284df53695cee4d3f8f2dfc138beb

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/gfx/ali_3d_scummvm.cpp
    engines/ags/engine/gfx/gfx_driver_base.cpp
    engines/ags/engine/gfx/gfx_driver_base.h
    engines/ags/engine/gfx/graphics_driver.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 915fbd9e1ef..15db3378dc9 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -661,15 +661,16 @@ void render_black_borders() {
 			_G(gfxDriver)->DrawSprite(0, 0, _G(blankSidebarImage));
 			_G(gfxDriver)->DrawSprite(viewport.Right + 1, 0, _G(blankSidebarImage));
 		}
+		_G(gfxDriver)->EndSpriteBatch();
 	}
 }
 
-
 void render_to_screen() {
 	// Stage: final plugin callback (still drawn on game screen
 	if (pl_any_want_hook(AGSE_FINALSCREENDRAW)) {
 		_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(), SpriteTransform(), Point(0, _GP(play).shake_screen_yoff), (GlobalFlipType)_GP(play).screen_flipped);
 		_G(gfxDriver)->DrawSprite(AGSE_FINALSCREENDRAW, 0, nullptr);
+		_G(gfxDriver)->EndSpriteBatch();
 	}
 	// Stage: engine overlay
 	construct_engine_overlay();
@@ -2207,6 +2208,7 @@ static void construct_room_view() {
 			}
 		}
 		put_sprite_list_on_screen(true);
+		_G(gfxDriver)->EndSpriteBatch();
 	}
 
 	clear_draw_list();
@@ -2217,6 +2219,7 @@ static void construct_ui_view() {
 	_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetUIViewportAbs(), SpriteTransform(), Point(0, _GP(play).shake_screen_yoff), (GlobalFlipType)_GP(play).screen_flipped);
 	draw_gui_and_overlays();
 	put_sprite_list_on_screen(false);
+	_G(gfxDriver)->EndSpriteBatch();
 	clear_draw_list();
 }
 
@@ -2265,9 +2268,13 @@ void construct_game_scene(bool full_redraw) {
 }
 
 void construct_game_screen_overlay(bool draw_mouse) {
-	_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(), SpriteTransform(), Point(0, _GP(play).shake_screen_yoff), (GlobalFlipType)_GP(play).screen_flipped);
-	if (pl_any_want_hook(AGSE_POSTSCREENDRAW))
+	if (pl_any_want_hook(AGSE_POSTSCREENDRAW)) {
+		_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(),
+			SpriteTransform(), Point(0, _GP(play).shake_screen_yoff),
+			(GlobalFlipType)_GP(play).screen_flipped);
 		_G(gfxDriver)->DrawSprite(AGSE_POSTSCREENDRAW, 0, nullptr);
+		_G(gfxDriver)->EndSpriteBatch();
+	}
 
 	// TODO: find out if it's okay to move cursor animation and state update
 	// to the update loop instead of doing it in the drawing routine
@@ -2301,18 +2308,19 @@ void construct_game_screen_overlay(bool draw_mouse) {
 		_G(lastmy) = _G(mousey);
 	}
 
-	// Stage: mouse cursor
-	if (draw_mouse && !_GP(play).mouse_cursor_hidden && _GP(play).screen_is_faded_out == 0) {
-		_G(gfxDriver)->DrawSprite(_G(mousex) - _G(hotx), _G(mousey) - _G(hoty),
-		                          _G(mouseCursor));
-		invalidate_sprite(_G(mousex) - _G(hotx), _G(mousey) - _G(hoty), _G(mouseCursor), false);
-	}
-
 	if (_GP(play).screen_is_faded_out == 0) {
+		// Stage: mouse cursor
+		_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(), SpriteTransform(), Point(0, _GP(play).shake_screen_yoff), (GlobalFlipType)_GP(play).screen_flipped);
+		if (draw_mouse && !_GP(play).mouse_cursor_hidden) {
+			_G(gfxDriver)->DrawSprite(_G(mousex) - _G(hotx), _G(mousey) - _G(hoty), _G(mouseCursor));
+			invalidate_sprite(_G(mousex) - _G(hotx), _G(mousey) - _G(hoty), _G(mouseCursor), false);
+		}
 		// Stage: screen fx
 		if (_GP(play).screen_tint >= 1)
 			_G(gfxDriver)->SetScreenTint(_GP(play).screen_tint & 0xff, (_GP(play).screen_tint >> 8) & 0xff, (_GP(play).screen_tint >> 16) & 0xff);
-		// Stage: legacy letterbox mode borders
+		_G(gfxDriver)->EndSpriteBatch();
+
+		// Stage: legacy letterbox mode borders (has its own sprite batch)
 		render_black_borders();
 	}
 
@@ -2320,6 +2328,7 @@ void construct_game_screen_overlay(bool draw_mouse) {
 		const Rect &main_viewport = _GP(play).GetMainViewport();
 		_G(gfxDriver)->BeginSpriteBatch(main_viewport, SpriteTransform());
 		_G(gfxDriver)->SetScreenFade(_GP(play).fade_to_red, _GP(play).fade_to_green, _GP(play).fade_to_blue);
+		_G(gfxDriver)->EndSpriteBatch();
 	}
 }
 
@@ -2357,6 +2366,8 @@ void construct_engine_overlay() {
 
 	if (_G(display_fps) != kFPS_Hide)
 		draw_fps(viewport);
+
+	_G(gfxDriver)->EndSpriteBatch();
 }
 
 static void update_shakescreen() {
diff --git a/engines/ags/engine/gfx/ali_3d_scummvm.cpp b/engines/ags/engine/gfx/ali_3d_scummvm.cpp
index 79efe05f1ac..2ff6e418d61 100644
--- a/engines/ags/engine/gfx/ali_3d_scummvm.cpp
+++ b/engines/ags/engine/gfx/ali_3d_scummvm.cpp
@@ -295,6 +295,11 @@ void ScummVMRendererGraphicsDriver::SetScreenTint(int red, int green, int blue)
 }
 
 void ScummVMRendererGraphicsDriver::RenderToBackBuffer() {
+	// Close unended batches, and issue a warning
+	assert(_actSpriteBatch == 0);
+	while (_actSpriteBatch > 0)
+		EndSpriteBatch();
+
 	// Render all the sprite batches with necessary transformations
 	//
 	// NOTE: that's not immediately clear whether it would be faster to first draw upon a camera-sized
diff --git a/engines/ags/engine/gfx/gfx_driver_base.cpp b/engines/ags/engine/gfx/gfx_driver_base.cpp
index 874467d8d20..a74ba4e15c8 100644
--- a/engines/ags/engine/gfx/gfx_driver_base.cpp
+++ b/engines/ags/engine/gfx/gfx_driver_base.cpp
@@ -66,12 +66,16 @@ Rect GraphicsDriverBase::GetRenderDestination() const {
 }
 
 void GraphicsDriverBase::BeginSpriteBatch(const Rect &viewport, const SpriteTransform &transform,
-        const Point offset, GlobalFlipType flip, PBitmap surface) {
-	_actSpriteBatch++;
-	_spriteBatchDesc.push_back(SpriteBatchDesc(viewport, transform, offset, flip, surface));
+	const Point offset, GlobalFlipType flip, PBitmap surface) {
+	_spriteBatchDesc.push_back(SpriteBatchDesc(_actSpriteBatch, viewport, transform, offset, flip, surface));
+	_actSpriteBatch = _spriteBatchDesc.size() - 1;
 	InitSpriteBatch(_actSpriteBatch, _spriteBatchDesc[_actSpriteBatch]);
 }
 
+void GraphicsDriverBase::EndSpriteBatch() {
+	_actSpriteBatch = _spriteBatchDesc[_actSpriteBatch].Parent;
+}
+
 void GraphicsDriverBase::ClearDrawLists() {
 	ResetAllBatches();
 	_actSpriteBatch = 0;
diff --git a/engines/ags/engine/gfx/gfx_driver_base.h b/engines/ags/engine/gfx/gfx_driver_base.h
index b1fbd460dbf..35b62a657a2 100644
--- a/engines/ags/engine/gfx/gfx_driver_base.h
+++ b/engines/ags/engine/gfx/gfx_driver_base.h
@@ -42,6 +42,7 @@ using Shared::PlaneScaling;
 
 // Sprite batch, defines viewport and an optional model transformation for the list of sprites
 struct SpriteBatchDesc {
+	uint32_t                 Parent = 0;
 	// View rectangle for positioning and clipping, in resolution coordinates
 	// (this may be screen or game frame resolution, depending on circumstances)
 	Rect                     Viewport;
@@ -50,15 +51,15 @@ struct SpriteBatchDesc {
 	// Global node offset applied to the whole batch as the last transform
 	Point                    Offset;
 	// Global node flip applied to the whole batch as the last transform
-	GlobalFlipType           Flip;
+	GlobalFlipType           Flip = kFlip_None;
 	// Optional bitmap to draw sprites upon. Used exclusively by the software rendering mode.
 	PBitmap                  Surface;
 
-	SpriteBatchDesc() : Flip(kFlip_None) {
-	}
-	SpriteBatchDesc(const Rect viewport, const SpriteTransform &transform, const Point offset = Point(),
-	                GlobalFlipType flip = kFlip_None, PBitmap surface = nullptr)
-		: Viewport(viewport)
+	SpriteBatchDesc() = default;
+	SpriteBatchDesc(uint32_t parent, const Rect viewport, const SpriteTransform & transform, const Point offset = Point(),
+		GlobalFlipType flip = kFlip_None, PBitmap surface = nullptr)
+		: Parent(parent)
+		, Viewport(viewport)
 		, Transform(transform)
 		, Offset(offset)
 		, Flip(flip)
@@ -102,6 +103,7 @@ public:
 
 	void        BeginSpriteBatch(const Rect &viewport, const SpriteTransform &transform,
 	                             const Point offset = Point(), GlobalFlipType flip = kFlip_None, PBitmap surface = nullptr) override;
+	void        EndSpriteBatch() override;
 	void        ClearDrawLists() override;
 
 	void        SetCallbackForPolling(GFXDRV_CLIENTCALLBACK callback) override {
diff --git a/engines/ags/engine/gfx/graphics_driver.h b/engines/ags/engine/gfx/graphics_driver.h
index 7d20f1ad766..a8fa2d6381a 100644
--- a/engines/ags/engine/gfx/graphics_driver.h
+++ b/engines/ags/engine/gfx/graphics_driver.h
@@ -135,8 +135,12 @@ public:
 	// Prepares next sprite batch, a list of sprites with defined viewport and optional
 	// global model transformation; all subsequent calls to DrawSprite will be adding
 	// sprites to this batch's list.
+	// Beginning a batch while the previous was not ended will create a sub-batch
+	// (think of it as of a child scene node).
 	virtual void BeginSpriteBatch(const Rect &viewport, const SpriteTransform &transform,
-	                              const Point offset = Point(), GlobalFlipType flip = kFlip_None, PBitmap surface = nullptr) = 0;
+		const Point offset = Point(), GlobalFlipType flip = kFlip_None, PBitmap surface = nullptr) = 0;
+	// Ends current sprite batch
+	virtual void EndSpriteBatch() = 0;
 	// Adds sprite to the active batch
 	virtual void DrawSprite(int x, int y, IDriverDependantBitmap *bitmap) = 0;
 	// Adds fade overlay fx to the active batch


Commit: 2f558ae1ed0d293091d690d9839f0abb256ade77
    https://github.com/scummvm/scummvm/commit/2f558ae1ed0d293091d690d9839f0abb256ade77
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:20-07:00

Commit Message:
AGS: Fixed 8-bit sprites unexpectedly converted on load

>From upstream b9804f5ac307e390de205b20f7c803dcf6812df2

Changed paths:
    engines/ags/engine/ac/draw.cpp


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 15db3378dc9..053c030fc24 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -168,8 +168,8 @@ Bitmap *AdjustBitmapForUseWithDisplayMode(Bitmap *bitmap, bool has_alpha) {
 	}
 
 	// Finally, if we did not create a new copy already, - convert to driver compatible format
-	if ((new_bitmap == bitmap) && (bmp_col_depth != compat_col_depth))
-		new_bitmap = GfxUtil::ConvertBitmap(bitmap, compat_col_depth);
+	if (new_bitmap == bitmap)
+		new_bitmap = GfxUtil::ConvertBitmap(bitmap, _G(gfxDriver)->GetCompatibleBitmapFormat(bitmap->GetColorDepth()));
 
 	if (must_switch_palette)
 		unselect_palette();


Commit: d09117334df1a8426dbc8e466b0c528e853198f9
    https://github.com/scummvm/scummvm/commit/d09117334df1a8426dbc8e466b0c528e853198f9
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:20-07:00

Commit Message:
AGS: Removed obscure hack in DrawSpriteWithTransparency()

>From upstream 083d8496f2d7c0eedc31c068ddf27720dee4ff2b

Changed paths:
    engines/ags/engine/gfx/gfx_util.cpp


diff --git a/engines/ags/engine/gfx/gfx_util.cpp b/engines/ags/engine/gfx/gfx_util.cpp
index e872431d128..ff2743cc3d7 100644
--- a/engines/ags/engine/gfx/gfx_util.cpp
+++ b/engines/ags/engine/gfx/gfx_util.cpp
@@ -103,12 +103,7 @@ void DrawSpriteWithTransparency(Bitmap *ds, Bitmap *sprite, int x, int y, int al
 	int surface_depth = ds->GetColorDepth();
 	int sprite_depth = sprite->GetColorDepth();
 
-	if (sprite_depth < surface_depth
-	        // CHECKME: what is the purpose of this hack and is this still relevant?
-#if AGS_PLATFORM_OS_IOS || AGS_PLATFORM_OS_ANDROID
-	        || (ds->GetBPP() < surface_depth && _G(psp_gfx_renderer) > 0) // Fix for corrupted speechbox outlines with the OGL driver
-#endif
-	   ) {
+	if (sprite_depth < surface_depth) {
 		// If sprite is lower color depth than destination surface, e.g.
 		// 8-bit sprites drawn on 16/32-bit surfaces.
 		if (sprite_depth == 8 && surface_depth >= 24) {


Commit: fec8ab31837743cca2342154087515f77e56281e
    https://github.com/scummvm/scummvm/commit/fec8ab31837743cca2342154087515f77e56281e
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:21-07:00

Commit Message:
AGS: Simplified DrawSpriteWithTransparency() a little

>From upstream 38922c2eb62b8082ae7ba07f07a8fe2192f1bac5

Changed paths:
    engines/ags/engine/gfx/gfx_util.cpp


diff --git a/engines/ags/engine/gfx/gfx_util.cpp b/engines/ags/engine/gfx/gfx_util.cpp
index ff2743cc3d7..4772a6ef19c 100644
--- a/engines/ags/engine/gfx/gfx_util.cpp
+++ b/engines/ags/engine/gfx/gfx_util.cpp
@@ -100,8 +100,9 @@ void DrawSpriteWithTransparency(Bitmap *ds, Bitmap *sprite, int x, int y, int al
 		return;
 	}
 
-	int surface_depth = ds->GetColorDepth();
-	int sprite_depth = sprite->GetColorDepth();
+	Bitmap hctemp;
+	const int surface_depth = ds->GetColorDepth();
+	const int sprite_depth = sprite->GetColorDepth();
 
 	if (sprite_depth < surface_depth) {
 		// If sprite is lower color depth than destination surface, e.g.
@@ -115,7 +116,6 @@ void DrawSpriteWithTransparency(Bitmap *ds, Bitmap *sprite, int x, int y, int al
 
 		// 256-col sprite -> hi-color background, or
 		// 16-bit sprite -> 32-bit background
-		Bitmap hctemp;
 		hctemp.CreateCopy(sprite, surface_depth);
 		if (sprite_depth == 8) {
 			// only do this for 256-col -> hi-color, cos the Blit call converts
@@ -133,19 +133,14 @@ void DrawSpriteWithTransparency(Bitmap *ds, Bitmap *sprite, int x, int y, int al
 			}
 		}
 
-		if (alpha < 0xFF) {
-			set_trans_blender(0, 0, 0, alpha);
-			ds->TransBlendBlt(&hctemp, x, y);
-		} else {
-			ds->Blit(&hctemp, x, y, kBitmap_Transparency);
-		}
+		sprite = &hctemp;
+	}
+
+	if ((alpha < 0xFF) && (surface_depth > 8) && (sprite_depth > 8)) {
+		set_trans_blender(0, 0, 0, alpha);
+		ds->TransBlendBlt(sprite, x, y);
 	} else {
-		if (alpha < 0xFF && surface_depth > 8 && sprite_depth > 8) {
-			set_trans_blender(0, 0, 0, alpha);
-			ds->TransBlendBlt(sprite, x, y);
-		} else {
-			ds->Blit(sprite, x, y, kBitmap_Transparency);
-		}
+		ds->Blit(sprite, x, y, kBitmap_Transparency);
 	}
 }
 


Commit: d52e2fb9543f26207ec01dfde8808adaa8112791
    https://github.com/scummvm/scummvm/commit/d52e2fb9543f26207ec01dfde8808adaa8112791
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:21-07:00

Commit Message:
AGS: Gui control textures are rendered as gui sub-batches

>From upstream 663394adb286384ba1b03ad95abc93a8dac40b05

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/sprite_list_entry.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 053c030fc24..13ecc187c2a 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -772,10 +772,10 @@ static void clear_draw_list() {
 	_GP(thingsToDrawList).clear();
 }
 
-static void add_thing_to_draw(IDriverDependantBitmap *bmp, int x, int y) {
-	assert(bmp != nullptr);
+static void add_thing_to_draw(IDriverDependantBitmap *ddb, int x, int y) {
+	assert(ddb != nullptr);
 	SpriteListEntry sprite;
-	sprite.bmp = bmp;
+	sprite.ddb = ddb;
 	sprite.x = x;
 	sprite.y = y;
 	_GP(thingsToDrawList).push_back(sprite);
@@ -791,18 +791,18 @@ static void clear_sprite_list() {
 	_GP(sprlist).clear();
 }
 
-static void add_to_sprite_list(IDriverDependantBitmap *spp, int xx, int yy, int zorder, bool isWalkBehind) {
-	if (spp == nullptr)
-		quit("add_to_sprite_list: attempted to draw NULL sprite");
+static void add_to_sprite_list(IDriverDependantBitmap *ddb, int x, int y, int zorder, bool isWalkBehind, int id = -1) {
+	assert(ddb);
 	// completely invisible, so don't draw it at all
-	if (spp->GetTransparency() == 255)
+	if (ddb->GetTransparency() == 255)
 		return;
 
 	SpriteListEntry sprite;
-	sprite.bmp = spp;
+	sprite.id = id;
+	sprite.ddb = ddb;
 	sprite.zorder = zorder;
-	sprite.x = xx;
-	sprite.y = yy;
+	sprite.x = x;
+	sprite.y = y;
 
 	if (_G(walkBehindMethod) == DrawAsSeparateSprite)
 		sprite.takesPriorityIfEqual = !isWalkBehind;
@@ -812,8 +812,14 @@ static void add_to_sprite_list(IDriverDependantBitmap *spp, int xx, int yy, int
 	_GP(sprlist).push_back(sprite);
 }
 
-// function to sort the sprites into zorder order
+// z-order sorting function for sprites
 static bool spritelistentry_less(const SpriteListEntry &e1, const SpriteListEntry &e2) {
+	return (e1.zorder < e2.zorder);
+}
+
+// room-specialized function to sort the sprites into baseline order
+// has special handling for walk-behinds (this is complicated...)
+static bool spritelistentry_room_less(const SpriteListEntry &e1, const SpriteListEntry &e2) {
 	if (e1.zorder == e2.zorder) {
 		if (e1.takesPriorityIfEqual)
 			return false;
@@ -824,11 +830,14 @@ static bool spritelistentry_less(const SpriteListEntry &e1, const SpriteListEntr
 }
 
 // copy the sorted sprites into the Things To Draw list
-static void draw_sprite_list() {
-	std::sort(_GP(sprlist).begin(), _GP(sprlist).end(), spritelistentry_less);
-	_GP(thingsToDrawList).insert(_GP(thingsToDrawList).end(), _GP(sprlist).begin(), _GP(sprlist).end());
+static void draw_sprite_list(bool is_room) {
+	std::sort(_GP(sprlist).begin(), _GP(sprlist).end(), is_room ? spritelistentry_room_less : spritelistentry_less);
+	_GP(thingsToDrawList).insert(_GP(thingsToDrawList).end(),
+		_GP(sprlist).begin(), _GP(sprlist).end());
 }
 
+// Push the gathered list of sprites into the active graphic renderer
+void put_sprite_list_on_screen(bool in_room);
 //
 //------------------------------------------------------------------------
 
@@ -1857,7 +1866,7 @@ void prepare_room_sprites() {
 			if (pl_any_want_hook(AGSE_PRESCREENDRAW))
 				add_render_stage(AGSE_PRESCREENDRAW);
 
-			draw_sprite_list();
+			draw_sprite_list(true);
 		}
 	}
 	_G(our_eip) = 36;
@@ -2001,37 +2010,32 @@ void draw_gui_and_overlays() {
 		&& (_GP(game).options[OPT_NEWGUIALPHA] == kGuiAlphaRender_Proper);
 
 	if (pl_any_want_hook(AGSE_PREGUIDRAW))
-		add_render_stage(AGSE_PREGUIDRAW);
+		_G(gfxDriver)->DrawSprite(AGSE_PREGUIDRAW, 0, nullptr); // render stage
 
 	clear_sprite_list();
 
 	// Add active overlays to the sprite list
 	for (auto &over : _GP(screenover)) {
-		if (over.transparency == 255)
-			continue; // skip fully transparent
+		if (over.transparency == 255) continue; // skip fully transparent
 		over.bmp->SetTransparency(over.transparency);
-
 		int tdxp, tdyp;
 		get_overlay_position(over, &tdxp, &tdyp);
-		add_to_sprite_list(over.bmp, tdxp, tdyp, over.zorder, false);
+		add_to_sprite_list(over.bmp, tdxp, tdyp, over.zorder, false, -1);
 	}
 
 	// Add GUIs
 	_G(our_eip) = 35;
 	if (((_G(debug_flags) & DBG_NOIFACE) == 0) && (_G(displayed_room) >= 0)) {
-		int aa;
-
 		if (_G(playerchar)->activeinv >= MAX_INV) {
 			quit("!The player.activeinv variable has been corrupted, probably as a result\n"
-			     "of an incorrect assignment in the game script.");
+				"of an incorrect assignment in the game script.");
 		}
-		if (_G(playerchar)->activeinv < 1)
-			_G(gui_inv_pic) = -1;
-		else
-			_G(gui_inv_pic) = _GP(game).invinfo[_G(playerchar)->activeinv].pic;
+		if (_G(playerchar)->activeinv < 1) _G(gui_inv_pic) = -1;
+		else _G(gui_inv_pic) = _GP(game).invinfo[_G(playerchar)->activeinv].pic;
 		_G(our_eip) = 37;
+		// Prepare and update GUI textures
 		{
-			for (aa = 0; aa < _GP(game).numgui; aa++) {
+			for (int aa = 0; aa < _GP(game).numgui; aa++) {
 				if (!_GP(guis)[aa].IsDisplayed()) continue; // not on screen
 				if (!_GP(guis)[aa].HasChanged() && !_GP(guis)[aa].HasControlsChanged()) continue; // no changes: no need to update image
 				if (_GP(guis)[aa].Transparency == 255) continue; // 100% transparent
@@ -2076,78 +2080,90 @@ void draw_gui_and_overlays() {
 		}
 		_G(our_eip) = 38;
 		// Draw the GUIs
-		for (int gg = 0; gg < _GP(game).numgui; gg++) {
-			aa = _GP(play).gui_draw_order[gg];
+		for (int aa = 0; aa < _GP(game).numgui; ++aa) {
 			if (!_GP(guis)[aa].IsDisplayed()) continue; // not on screen
 			if (_GP(guis)[aa].Transparency == 255) continue; // 100% transparent
 
 			// Don't draw GUI if "GUIs Turn Off When Disabled"
 			if ((_GP(game).options[OPT_DISABLEOFF] == kGuiDis_Off) &&
-					(_G(all_buttons_disabled) >= 0) &&
-					(_GP(guis)[aa].PopupStyle != kGUIPopupNoAutoRemove))
+				(_G(all_buttons_disabled) >= 0) &&
+				(_GP(guis)[aa].PopupStyle != kGUIPopupNoAutoRemove))
 				continue;
 
-			_GP(guibgbmp)[aa]->SetTransparency(_GP(guis)[aa].Transparency);
-			add_to_sprite_list(_GP(guibgbmp)[aa], _GP(guis)[aa].X, _GP(guis)[aa].Y, _GP(guis)[aa].ZOrder, false);
-
-			// Add all the gui controls as separate textures
-			if (draw_controls_as_textures &&
-				!(_G(all_buttons_disabled) && (GUI::Options.DisabledStyle == kGuiDis_Blackout))) {
-				const int draw_index = _GP(guiobjbmpref)[aa];
-				for (const auto &obj_id : _GP(guis)[aa].GetControlsDrawOrder()) {
-					GUIObject *obj = _GP(guis)[aa].GetControl(obj_id);
-					if (!obj->IsVisible() ||
-						(!obj->IsEnabled() && (GUI::Options.DisabledStyle == kGuiDis_Blackout)))
-						continue;
-					_GP(guiobjbmp)[draw_index + obj_id]->SetTransparency(_GP(guis)[aa].Transparency);
-					add_to_sprite_list(_GP(guiobjbmp)[draw_index + obj_id],
-						_GP(guis)[aa].X + _GP(guiobjoff)[draw_index + obj_id].X,
-						_GP(guis)[aa].Y + _GP(guiobjoff)[draw_index + obj_id].Y,
-						_GP(guis)[aa].ZOrder, false);
-				}
-			}
+			auto *gui_ddb = _GP(guibgbmp)[aa];
+			assert(gui_ddb); // Test for missing texture, might happen if not marked for update
+			if (!gui_ddb) continue;
+			gui_ddb->SetTransparency(_GP(guis)[aa].Transparency);
+			add_to_sprite_list(gui_ddb, _GP(guis)[aa].X, _GP(guis)[aa].Y, _GP(guis)[aa].ZOrder, false, aa);
 		}
 
 		// Poll the GUIs
 		// TODO: move this out of the draw routine into game update!!
-		// only poll if the interface is enabled
-		if (IsInterfaceEnabled()) {
+		if (IsInterfaceEnabled()) // only poll if the interface is enabled
+		{
 			for (int gg = 0; gg < _GP(game).numgui; gg++) {
 				if (!_GP(guis)[gg].IsDisplayed()) continue; // not on screen
 				// Don't touch GUI if "GUIs Turn Off When Disabled"
 				if ((_GP(game).options[OPT_DISABLEOFF] == kGuiDis_Off) &&
-						(_G(all_buttons_disabled) >= 0) &&
-						(_GP(guis)[gg].PopupStyle != kGUIPopupNoAutoRemove))
+					(_G(all_buttons_disabled) >= 0) &&
+					(_GP(guis)[gg].PopupStyle != kGUIPopupNoAutoRemove))
 					continue;
 				_GP(guis)[gg].Poll(_G(mousex), _G(mousey));
 			}
 		}
 	}
 
-	// sort and append ui sprites to the global draw things list
-	draw_sprite_list();
+	// If not adding gui controls as textures, simply move the resulting sprlist to render
+	if (!draw_controls_as_textures ||
+		(_G(all_buttons_disabled) && (GUI::Options.DisabledStyle == kGuiDis_Blackout))) {
+		draw_sprite_list(false);
+		put_sprite_list_on_screen(false);
+		return;
+	}
+	// If adding control textures, sort the ui list, and then pass into renderer,
+	// adding controls and creating sub-batches as necessary
+	std::sort(_GP(sprlist).begin(), _GP(sprlist).end(), spritelistentry_less);
+	for (const auto &s : _GP(sprlist)) {
+		invalidate_sprite(s.x, s.y, s.ddb, false);
+		_G(gfxDriver)->DrawSprite(s.x, s.y, s.ddb);
+		if (s.id < 0) continue; // not a group parent (gui)
+		// Create a sub-batch
+		_G(gfxDriver)->BeginSpriteBatch(RectWH(s.x, s.y, s.ddb->GetWidth(), s.ddb->GetHeight()), SpriteTransform());
+		const int draw_index = _GP(guiobjbmpref)[s.id];
+		for (const auto &obj_id : _GP(guis)[s.id].GetControlsDrawOrder()) {
+			GUIObject *obj = _GP(guis)[s.id].GetControl(obj_id);
+			if (!obj->IsVisible() ||
+				(!obj->IsEnabled() && (GUI::Options.DisabledStyle == kGuiDis_Blackout)))
+				continue;
+			auto *obj_ddb = _GP(guiobjbmp)[draw_index + obj_id];
+			assert(obj_ddb); // Test for missing texture, might happen if not marked for update
+			if (!obj_ddb) continue;
+			obj_ddb->SetTransparency(_GP(guis)[s.id].Transparency);
+			_G(gfxDriver)->DrawSprite(
+				_GP(guiobjoff)[draw_index + obj_id].X,
+				_GP(guiobjoff)[draw_index + obj_id].Y,
+				obj_ddb);
+		}
+		_G(gfxDriver)->EndSpriteBatch();
+	}
 
 	_G(our_eip) = 1099;
 }
 
 // Push the gathered list of sprites into the active graphic renderer
 void put_sprite_list_on_screen(bool in_room) {
-	for (size_t i = 0; i < _GP(thingsToDrawList).size(); ++i) {
-		const auto *thisThing = &_GP(thingsToDrawList)[i];
-
-		if (thisThing->bmp != nullptr) {
-			if (thisThing->bmp->GetTransparency() == 255)
+	for (const auto &t : _GP(thingsToDrawList)) {
+		assert(t.ddb || (t.renderStage >= 0));
+		if (t.ddb) {
+			if (t.ddb->GetTransparency() == 255)
 				continue; // skip completely invisible things
 			// mark the image's region as dirty
-			invalidate_sprite(thisThing->x, thisThing->y, thisThing->bmp, in_room);
-
+			invalidate_sprite(t.x, t.y, t.ddb, in_room);
 			// push to the graphics driver
-			_G(gfxDriver)->DrawSprite(thisThing->x, thisThing->y, thisThing->bmp);
-		} else if (thisThing->renderStage >= 0) {
+			_G(gfxDriver)->DrawSprite(t.x, t.y, t.ddb);
+		} else if (t.renderStage >= 0) {
 			// meta entry to run the plugin hook
-			_G(gfxDriver)->DrawSprite(thisThing->renderStage, 0, nullptr);
-		} else {
-			quit("Null pointer added to draw list");
+			_G(gfxDriver)->DrawSprite(t.renderStage, 0, nullptr);
 		}
 	}
 
@@ -2218,7 +2234,6 @@ static void construct_room_view() {
 static void construct_ui_view() {
 	_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetUIViewportAbs(), SpriteTransform(), Point(0, _GP(play).shake_screen_yoff), (GlobalFlipType)_GP(play).screen_flipped);
 	draw_gui_and_overlays();
-	put_sprite_list_on_screen(false);
 	_G(gfxDriver)->EndSpriteBatch();
 	clear_draw_list();
 }
diff --git a/engines/ags/engine/ac/sprite_list_entry.h b/engines/ags/engine/ac/sprite_list_entry.h
index 3d6d061708a..0aeee8a16b9 100644
--- a/engines/ags/engine/ac/sprite_list_entry.h
+++ b/engines/ags/engine/ac/sprite_list_entry.h
@@ -26,8 +26,10 @@
 
 namespace AGS3 {
 
+// Describes a texture or node description, for sorting and passing into renderer
 struct SpriteListEntry {
-	Engine::IDriverDependantBitmap *bmp = nullptr;
+	int id = -1; // user identifier, for any custom purpose
+	Engine::IDriverDependantBitmap *ddb = nullptr;
 	AGS::Shared::Bitmap *pic = nullptr;
 	int x = 0, y = 0;
 	int zorder = 0;


Commit: 211abe3744b0b79d58d0705465620273425b60e2
    https://github.com/scummvm/scummvm/commit/211abe3744b0b79d58d0705465620273425b60e2
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:21-07:00

Commit Message:
AGS: Implemented HasAlphaChannel() in GuiButton and GuiSlider

>From upstream 05999579489e427b7df7a01bab9643b6677d5afb

Changed paths:
    engines/ags/shared/gui/gui_button.cpp
    engines/ags/shared/gui/gui_button.h
    engines/ags/shared/gui/gui_object.cpp
    engines/ags/shared/gui/gui_object.h
    engines/ags/shared/gui/gui_slider.cpp
    engines/ags/shared/gui/gui_slider.h


diff --git a/engines/ags/shared/gui/gui_button.cpp b/engines/ags/shared/gui/gui_button.cpp
index 79f33f64dcc..fdd6a997b16 100644
--- a/engines/ags/shared/gui/gui_button.cpp
+++ b/engines/ags/shared/gui/gui_button.cpp
@@ -94,6 +94,10 @@ bool GUIButton::IsClippingImage() const {
 	return (Flags & kGUICtrl_Clip) != 0;
 }
 
+bool GUIButton::HasAlphaChannel() const {
+	return is_sprite_alpha(CurrentImage);
+}
+
 Rect GUIButton::CalcGraphicRect(bool clipped) {
 	if (clipped)
 		return RectWH(X, Y, Width, Height);
diff --git a/engines/ags/shared/gui/gui_button.h b/engines/ags/shared/gui/gui_button.h
index 8390a7c569a..e83f0242a38 100644
--- a/engines/ags/shared/gui/gui_button.h
+++ b/engines/ags/shared/gui/gui_button.h
@@ -65,6 +65,7 @@ public:
 	const String &GetText() const;
 	bool IsImageButton() const;
 	bool IsClippingImage() const;
+	bool HasAlphaChannel() const override;
 
 	// Operations
 	Rect CalcGraphicRect(bool clipped) override;
diff --git a/engines/ags/shared/gui/gui_object.cpp b/engines/ags/shared/gui/gui_object.cpp
index c2367983a7a..50264e0dbcd 100644
--- a/engines/ags/shared/gui/gui_object.cpp
+++ b/engines/ags/shared/gui/gui_object.cpp
@@ -82,10 +82,6 @@ bool GUIObject::IsVisible() const {
 	return (Flags & kGUICtrl_Visible) != 0;
 }
 
-bool GUIObject::HasAlphaChannel() const {
-	return false;
-}
-
 void GUIObject::SetClickable(bool on) {
 	if (on)
 		Flags |= kGUICtrl_Clickable;
diff --git a/engines/ags/shared/gui/gui_object.h b/engines/ags/shared/gui/gui_object.h
index d7d857c2985..b66eee51397 100644
--- a/engines/ags/shared/gui/gui_object.h
+++ b/engines/ags/shared/gui/gui_object.h
@@ -63,7 +63,7 @@ public:
     // Compatibility: should the control's graphic be clipped to its x,y,w,h
     virtual bool    IsContentClipped() const { return true; }
 	// Tells if the object image supports alpha channel
-	virtual bool    HasAlphaChannel() const;
+	virtual bool    HasAlphaChannel() const { return false; }
 
 	// Operations
 	// Returns the (untransformed!) visual rectangle of this control,
diff --git a/engines/ags/shared/gui/gui_slider.cpp b/engines/ags/shared/gui/gui_slider.cpp
index d8445428688..2704b3ea7e2 100644
--- a/engines/ags/shared/gui/gui_slider.cpp
+++ b/engines/ags/shared/gui/gui_slider.cpp
@@ -49,6 +49,10 @@ bool GUISlider::IsHorizontal() const {
 	return Width > Height;
 }
 
+bool GUISlider::HasAlphaChannel() const {
+	return is_sprite_alpha(BgImage) || is_sprite_alpha(HandleImage);
+}
+
 bool GUISlider::IsOverControl(int x, int y, int leeway) const {
 	// check the overall boundary
 	if (GUIObject::IsOverControl(x, y, leeway))
diff --git a/engines/ags/shared/gui/gui_slider.h b/engines/ags/shared/gui/gui_slider.h
index 671afad38f6..3a7074b0ffc 100644
--- a/engines/ags/shared/gui/gui_slider.h
+++ b/engines/ags/shared/gui/gui_slider.h
@@ -39,6 +39,7 @@ public:
 
 	// Compatibility: sliders are not clipped as of 3.6.0
 	bool IsContentClipped() const override { return false; }
+	bool HasAlphaChannel() const override;
 
 	// Operations
 	Rect CalcGraphicRect(bool clipped) override;


Commit: d7375f0772ca3034eddc4fe883cad3419794ef69
    https://github.com/scummvm/scummvm/commit/d7375f0772ca3034eddc4fe883cad3419794ef69
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:21-07:00

Commit Message:
AGS: Fixed GUIObject::SetVisible(), should MarkChanged

>From upstream e5aa27dfb7c4e8cd8dfaf9c418bb56df2b28ae7e

Changed paths:
    engines/ags/shared/gui/gui_object.cpp


diff --git a/engines/ags/shared/gui/gui_object.cpp b/engines/ags/shared/gui/gui_object.cpp
index 50264e0dbcd..aac31a627b7 100644
--- a/engines/ags/shared/gui/gui_object.cpp
+++ b/engines/ags/shared/gui/gui_object.cpp
@@ -110,6 +110,7 @@ void GUIObject::SetVisible(bool on) {
 		Flags |= kGUICtrl_Visible;
 	else
 		Flags &= ~kGUICtrl_Visible;
+	MarkChanged();
 }
 
 // TODO: replace string serialization with StrUtil::ReadString and WriteString


Commit: 50b2b1af05cc416d06834cb9ba0d668f5949c248
    https://github.com/scummvm/scummvm/commit/50b2b1af05cc416d06834cb9ba0d668f5949c248
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:21-07:00

Commit Message:
AGS: Replaced OnControlPositionChanged() with MarkControlsChanged()

>From upstream 2f676fbad2b725fe0089de45ba9f925bc48ab586

Changed paths:
    engines/ags/engine/ac/global_gui.cpp
    engines/ags/engine/ac/gui.cpp
    engines/ags/engine/ac/gui_control.cpp
    engines/ags/shared/gui/gui_main.cpp
    engines/ags/shared/gui/gui_main.h
    engines/ags/shared/gui/gui_object.h


diff --git a/engines/ags/engine/ac/global_gui.cpp b/engines/ags/engine/ac/global_gui.cpp
index c31c7ac3ad0..018b46ee960 100644
--- a/engines/ags/engine/ac/global_gui.cpp
+++ b/engines/ags/engine/ac/global_gui.cpp
@@ -76,7 +76,7 @@ void InterfaceOn(int ifn) {
 	// modal interface
 	if (_GP(guis)[ifn].PopupStyle == kGUIPopupModal) PauseGame();
 	// clear the cached mouse position
-	_GP(guis)[ifn].OnControlPositionChanged();
+	_GP(guis)[ifn].MarkControlsChanged();
 	_GP(guis)[ifn].Poll(_G(mousex), _G(mousey));
 }
 
@@ -93,7 +93,7 @@ void InterfaceOff(int ifn) {
 		_GP(guis)[ifn].GetControl(_GP(guis)[ifn].MouseOverCtrl)->OnMouseLeave();
 		_GP(guis)[ifn].MouseOverCtrl = -1;
 	}
-	_GP(guis)[ifn].OnControlPositionChanged();
+	_GP(guis)[ifn].MarkControlsChanged();
 	// modal interface
 	if (_GP(guis)[ifn].PopupStyle == kGUIPopupModal) UnPauseGame();
 }
diff --git a/engines/ags/engine/ac/gui.cpp b/engines/ags/engine/ac/gui.cpp
index 9fd3dc3e7e8..2a9727a6a1b 100644
--- a/engines/ags/engine/ac/gui.cpp
+++ b/engines/ags/engine/ac/gui.cpp
@@ -462,7 +462,7 @@ void update_gui_disabled_status() {
 		// As controls become enabled we must notify parent GUIs
 		// to let them reset control-under-mouse detection
 		for (int aa = 0; aa < _GP(game).numgui; aa++) {
-			_GP(guis)[aa].OnControlPositionChanged(); // this marks GUI as changed too
+			_GP(guis)[aa].MarkControlsChanged();
 		}
 		if (GUI::Options.DisabledStyle != kGuiDis_Unchanged) {
 			invalidate_screen();
diff --git a/engines/ags/engine/ac/gui_control.cpp b/engines/ags/engine/ac/gui_control.cpp
index be2872d5f88..c34100a1f68 100644
--- a/engines/ags/engine/ac/gui_control.cpp
+++ b/engines/ags/engine/ac/gui_control.cpp
@@ -65,7 +65,6 @@ void GUIControl_SetVisible(GUIObject *guio, int visible) {
 	const bool on = visible != 0;
 	if (on != guio->IsVisible()) {
 		guio->SetVisible(on);
-		_GP(guis)[guio->ParentId].OnControlPositionChanged();
 	}
 }
 
@@ -81,7 +80,8 @@ void GUIControl_SetClickable(GUIObject *guio, int enabled) {
 	else
 		guio->SetClickable(false);
 
-	_GP(guis)[guio->ParentId].OnControlPositionChanged();
+	// clickable property may change control behavior under mouse
+	_GP(guis)[guio->ParentId].MarkControlsChanged();
 }
 
 int GUIControl_GetEnabled(GUIObject *guio) {
@@ -92,7 +92,6 @@ void GUIControl_SetEnabled(GUIObject *guio, int enabled) {
 	const bool on = enabled != 0;
 	if (on != guio->IsEnabled()) {
 		guio->SetEnabled(on);
-		_GP(guis)[guio->ParentId].OnControlPositionChanged();
 	}
 }
 
@@ -153,7 +152,7 @@ int GUIControl_GetX(GUIObject *guio) {
 
 void GUIControl_SetX(GUIObject *guio, int xx) {
 	guio->X = data_to_game_coord(xx);
-	_GP(guis)[guio->ParentId].OnControlPositionChanged();
+	_GP(guis)[guio->ParentId].MarkControlsChanged(); // update control under cursor
 }
 
 int GUIControl_GetY(GUIObject *guio) {
@@ -162,7 +161,7 @@ int GUIControl_GetY(GUIObject *guio) {
 
 void GUIControl_SetY(GUIObject *guio, int yy) {
 	guio->Y = data_to_game_coord(yy);
-	_GP(guis)[guio->ParentId].OnControlPositionChanged();
+	_GP(guis)[guio->ParentId].MarkControlsChanged(); // update control under cursor
 }
 
 int GUIControl_GetZOrder(GUIObject *guio) {
@@ -186,7 +185,6 @@ int GUIControl_GetWidth(GUIObject *guio) {
 void GUIControl_SetWidth(GUIObject *guio, int newwid) {
 	guio->Width = data_to_game_coord(newwid);
 	guio->OnResized();
-	_GP(guis)[guio->ParentId].OnControlPositionChanged();
 }
 
 int GUIControl_GetHeight(GUIObject *guio) {
@@ -196,7 +194,6 @@ int GUIControl_GetHeight(GUIObject *guio) {
 void GUIControl_SetHeight(GUIObject *guio, int newhit) {
 	guio->Height = data_to_game_coord(newhit);
 	guio->OnResized();
-	_GP(guis)[guio->ParentId].OnControlPositionChanged();
 }
 
 void GUIControl_SetSize(GUIObject *guio, int newwid, int newhit) {
diff --git a/engines/ags/shared/gui/gui_main.cpp b/engines/ags/shared/gui/gui_main.cpp
index bf6e35c106f..67012f55e97 100644
--- a/engines/ags/shared/gui/gui_main.cpp
+++ b/engines/ags/shared/gui/gui_main.cpp
@@ -198,6 +198,9 @@ void GUIMain::MarkChanged() {
 
 void GUIMain::MarkControlsChanged() {
 	_hasControlsChanged = true;
+	// force it to re-check for which control is under the mouse
+	MouseWasAt.X = -1;
+	MouseWasAt.Y = -1;
 }
 
 void GUIMain::ClearChanged() {
@@ -432,7 +435,7 @@ bool GUIMain::SetControlZOrder(int32_t index, int zorder) {
 		}
 	}
 	ResortZOrder();
-	OnControlPositionChanged(); // this marks GUI as changed
+	MarkControlsChanged();
 	return true;
 }
 
@@ -456,13 +459,6 @@ void GUIMain::SetVisible(bool on) {
 	MarkChanged();
 }
 
-void GUIMain::OnControlPositionChanged() {
-	// force it to re-check for which control is under the mouse
-	MouseWasAt.X = -1;
-	MouseWasAt.Y = -1;
-	MarkChanged();
-}
-
 void GUIMain::OnMouseButtonDown(int mx, int my) {
 	if (MouseOverCtrl < 0)
 		return;
diff --git a/engines/ags/shared/gui/gui_main.h b/engines/ags/shared/gui/gui_main.h
index 91da669de22..7ad5fcfe988 100644
--- a/engines/ags/shared/gui/gui_main.h
+++ b/engines/ags/shared/gui/gui_main.h
@@ -153,7 +153,6 @@ public:
 	// Events
 	void    OnMouseButtonDown(int mx, int my);
 	void    OnMouseButtonUp();
-	void    OnControlPositionChanged();
 
 	// Serialization
 	void    ReadFromFile(Stream *in, GuiVersion gui_version);
diff --git a/engines/ags/shared/gui/gui_object.h b/engines/ags/shared/gui/gui_object.h
index b66eee51397..c778be2367a 100644
--- a/engines/ags/shared/gui/gui_object.h
+++ b/engines/ags/shared/gui/gui_object.h
@@ -99,8 +99,7 @@ public:
 	virtual void    OnMouseUp() {
 	}
 	// Control was resized
-	virtual void    OnResized() {
-	}
+	virtual void    OnResized() { MarkChanged(); }
 
 	// Serialization
 	virtual void    ReadFromFile(Shared::Stream *in, GuiVersion gui_version);


Commit: 53e1c221856ac1f0112d8e037fedd284d41abfb4
    https://github.com/scummvm/scummvm/commit/53e1c221856ac1f0112d8e037fedd284d41abfb4
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:22-07:00

Commit Message:
AGS: Fixes for the updated sprite batch system

>From upstream db57e5b06aaf60728e1ade30a61f84d5850596dc

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/event.cpp


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 13ecc187c2a..9c43710e3e3 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -2340,8 +2340,7 @@ void construct_game_screen_overlay(bool draw_mouse) {
 	}
 
 	if (_GP(play).screen_is_faded_out != 0 && _G(gfxDriver)->RequiresFullRedrawEachFrame()) {
-		const Rect &main_viewport = _GP(play).GetMainViewport();
-		_G(gfxDriver)->BeginSpriteBatch(main_viewport, SpriteTransform());
+		_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(), SpriteTransform());
 		_G(gfxDriver)->SetScreenFade(_GP(play).fade_to_red, _GP(play).fade_to_green, _GP(play).fade_to_blue);
 		_G(gfxDriver)->EndSpriteBatch();
 	}
@@ -2486,10 +2485,13 @@ void render_graphics(IDriverDependantBitmap *extraBitmap, int extraX, int extraY
 
 	construct_game_scene(false);
 	_G(our_eip) = 5;
-	// NOTE: extraBitmap will always be drawn with the UI render stage
+	// TODO: extraBitmap is a hack, used to place an additional gui element
+	// on top of the screen. Normally this should be a part of the game UI stage.
 	if (extraBitmap != nullptr) {
+		_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetUIViewportAbs(), SpriteTransform(), Point(0, _GP(play).shake_screen_yoff), (GlobalFlipType)_GP(play).screen_flipped);
 		invalidate_sprite(extraX, extraY, extraBitmap, false);
 		_G(gfxDriver)->DrawSprite(extraX, extraY, extraBitmap);
+		_G(gfxDriver)->EndSpriteBatch();
 	}
 	construct_game_screen_overlay(true);
 	render_to_screen();
diff --git a/engines/ags/engine/ac/event.cpp b/engines/ags/engine/ac/event.cpp
index 413d4545ba0..d7cbd2c9a8c 100644
--- a/engines/ags/engine/ac/event.cpp
+++ b/engines/ags/engine/ac/event.cpp
@@ -280,7 +280,9 @@ void process_event(const EventHappened *evp) {
 				if (transparency > 16) {
 					// on last frame of fade (where transparency < 16), don't
 					// draw the old screen on top
+					_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(), SpriteTransform());
 					_G(gfxDriver)->DrawSprite(0, 0, ddb);
+					_G(gfxDriver)->EndSpriteBatch();
 				}
 				render_to_screen();
 				update_polled_stuff_if_runtime();
@@ -314,7 +316,9 @@ void process_event(const EventHappened *evp) {
 				_G(gfxDriver)->UpdateDDBFromBitmap(ddb, _G(saved_viewport_bitmap), false);
 				construct_game_scene(true);
 				construct_game_screen_overlay(false);
+				_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(), SpriteTransform());
 				_G(gfxDriver)->DrawSprite(0, 0, ddb);
+				_G(gfxDriver)->EndSpriteBatch();
 				render_to_screen();
 				update_polled_stuff_if_runtime();
 				WaitForNextFrame();


Commit: 6ef504bba20ac88daaee8a66efe3e2ed87c94c56
    https://github.com/scummvm/scummvm/commit/6ef504bba20ac88daaee8a66efe3e2ed87c94c56
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:22-07:00

Commit Message:
AGS: Store gui draw order in a std::vector

>From upstream fcc873ef0c26439faf67100f4d70d540434ca9f5

Changed paths:
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/ac/game_state.h
    engines/ags/engine/ac/global_gui.cpp
    engines/ags/engine/ac/gui.cpp
    engines/ags/engine/game/game_init.cpp
    engines/ags/engine/game/savegame_v321.cpp
    engines/ags/lib/std/vector.h


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index edd0feeaa00..d5f5874449f 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -444,7 +444,7 @@ void unload_game_file() {
 	ccUnregisterAllObjects();
 
 	free_do_once_tokens();
-	free(_GP(play).gui_draw_order);
+	_GP(play).gui_draw_order.clear();
 
 	resetRoomStatuses();
 
diff --git a/engines/ags/engine/ac/game_state.h b/engines/ags/engine/ac/game_state.h
index 0dfec5c0eba..7f31420a9be 100644
--- a/engines/ags/engine/ac/game_state.h
+++ b/engines/ags/engine/ac/game_state.h
@@ -226,7 +226,7 @@ struct GameState {
 	int   gamma_adjustment = 0;
 	short temporarily_turned_off_character = 0;  // Hide Player Charactr ticked
 	short inv_backwards_compatibility = 0;
-	int32_t *gui_draw_order = 0;
+	std::vector<int> gui_draw_order; // used only for hit detection now
 	std::vector<AGS::Shared::String> do_once_tokens = 0;
 	int   text_min_display_time_ms = 0;
 	int   ignore_user_input_after_text_timeout_ms = 0;
diff --git a/engines/ags/engine/ac/global_gui.cpp b/engines/ags/engine/ac/global_gui.cpp
index 018b46ee960..8d3f2df278f 100644
--- a/engines/ags/engine/ac/global_gui.cpp
+++ b/engines/ags/engine/ac/global_gui.cpp
@@ -243,11 +243,11 @@ int GetGUIObjectAt(int xx, int yy) {
 int GetGUIAt(int xx, int yy) {
 	data_to_game_coords(&xx, &yy);
 
-	int aa, ll;
-	for (ll = _GP(game).numgui - 1; ll >= 0; ll--) {
-		aa = _GP(play).gui_draw_order[ll];
-		if (_GP(guis)[aa].IsInteractableAt(xx, yy))
-			return aa;
+	// Test in the opposite order (from closer to further)
+	for (auto g = _GP(play).gui_draw_order.crbegin();
+			g < _GP(play).gui_draw_order.crend(); ++g) {
+		if (_GP(guis)[*g].IsInteractableAt(xx, yy))
+			return *g;
 	}
 	return -1;
 }
diff --git a/engines/ags/engine/ac/gui.cpp b/engines/ags/engine/ac/gui.cpp
index 2a9727a6a1b..f5e5f996c70 100644
--- a/engines/ags/engine/ac/gui.cpp
+++ b/engines/ags/engine/ac/gui.cpp
@@ -19,6 +19,7 @@
  *
  */
 
+#include "ags/lib/std/algorithm.h"
 #include "ags/engine/ac/gui.h"
 #include "ags/shared/ac/common.h"
 #include "ags/engine/ac/draw.h"
@@ -406,28 +407,13 @@ void replace_macro_tokens(const char *text, String &fixed_text) {
 }
 
 
-void update_gui_zorder() {
-	int numdone = 0, b;
-
-	// for each GUI
-	for (int a = 0; a < _GP(game).numgui; a++) {
-		// find the right place in the draw order array
-		int insertAt = numdone;
-		for (b = 0; b < numdone; b++) {
-			if (_GP(guis)[a].ZOrder < _GP(guis)[_GP(play).gui_draw_order[b]].ZOrder) {
-				insertAt = b;
-				break;
-			}
-		}
-		// insert the new item
-		for (b = numdone - 1; b >= insertAt; b--)
-			_GP(play).gui_draw_order[b + 1] = _GP(play).gui_draw_order[b];
-		_GP(play).gui_draw_order[insertAt] = a;
-		numdone++;
-	}
-
+bool sort_gui_less(const int g1, const int g2) {
+	return _GP(guis)[g1].ZOrder < _GP(guis)[g2].ZOrder;
 }
 
+void update_gui_zorder() {
+	std::sort(_GP(play).gui_draw_order.begin(), _GP(play).gui_draw_order.end(), sort_gui_less);
+}
 
 void export_gui_controls(int ee) {
 	for (int ff = 0; ff < _GP(guis)[ee].GetControlCount(); ff++) {
@@ -529,9 +515,9 @@ int gui_on_mouse_move() {
 	else {
 		// Scan for mouse-y-pos GUIs, and pop one up if appropriate
 		// Also work out the mouse-over GUI while we're at it
-		int ll;
-		for (ll = 0; ll < _GP(game).numgui; ll++) {
-			const int guin = _GP(play).gui_draw_order[ll];
+		// CHECKME: not sure why, but we're testing forward draw order here -
+		// from farthest to nearest (this was in original code?)
+		for (int guin : _GP(play).gui_draw_order) {
 			if (_GP(guis)[guin].IsInteractableAt(_G(mousex), _G(mousey))) mouse_over_gui = guin;
 
 			if (_GP(guis)[guin].PopupStyle != kGUIPopupMouseY) continue;
diff --git a/engines/ags/engine/game/game_init.cpp b/engines/ags/engine/game/game_init.cpp
index cd7421f2993..10d6b506b8a 100644
--- a/engines/ags/engine/game/game_init.cpp
+++ b/engines/ags/engine/game/game_init.cpp
@@ -409,7 +409,10 @@ HGameInitError InitGameState(const LoadedGameEntities &ents, GameDataVersion dat
 		// labels are not clickable by default
 		_GP(guilabels)[i].SetClickable(false);
 	}
-	_GP(play).gui_draw_order = (int32_t *)calloc(game.numgui * sizeof(int), 1);
+	_GP(play).gui_draw_order.resize(game.numgui);
+	for (int i = 0; i < game.numgui; ++i)
+		_GP(play).gui_draw_order[i] = i;
+
 	update_gui_zorder();
 	calculate_reserved_channel_count();
 
diff --git a/engines/ags/engine/game/savegame_v321.cpp b/engines/ags/engine/game/savegame_v321.cpp
index 223223c2757..3ceab96aaad 100644
--- a/engines/ags/engine/game/savegame_v321.cpp
+++ b/engines/ags/engine/game/savegame_v321.cpp
@@ -154,21 +154,18 @@ static void restore_game_play_ex_data(Stream *in) {
 		_GP(play).do_once_tokens[i] = rbuffer;
 	}
 
-	in->ReadArrayOfInt32(&_GP(play).gui_draw_order[0], _GP(game).numgui);
+	in->Seek(_GP(game).numgui * sizeof(int32_t)); // gui_draw_order
 }
 
 static void restore_game_play(Stream *in, RestoredData &r_data) {
 	int screenfadedout_was = _GP(play).screen_is_faded_out;
 	int roomchanges_was = _GP(play).room_changes;
-	// make sure the pointer is preserved
-	int32_t *gui_draw_order_was = _GP(play).gui_draw_order;
 
 	ReadGameState_Aligned(in, r_data);
 	r_data.Cameras[0].Flags = r_data.Camera0_Flags;
 
 	_GP(play).screen_is_faded_out = screenfadedout_was;
 	_GP(play).room_changes = roomchanges_was;
-	_GP(play).gui_draw_order = gui_draw_order_was;
 
 	restore_game_play_ex_data(in);
 }
diff --git a/engines/ags/lib/std/vector.h b/engines/ags/lib/std/vector.h
index 9d7bff210b1..06e93f800e8 100644
--- a/engines/ags/lib/std/vector.h
+++ b/engines/ags/lib/std/vector.h
@@ -76,12 +76,15 @@ public:
 			return *this;
 		}
 
-		bool operator==(const const_reverse_iterator &rhs) {
+		bool operator==(const const_reverse_iterator &rhs) const {
 			return _owner == rhs._owner && _index == rhs._index;
 		}
-		bool operator!=(const const_reverse_iterator &rhs) {
+		bool operator!=(const const_reverse_iterator &rhs) const {
 			return !operator==(rhs);
 		}
+		bool operator<(const const_reverse_iterator &rhs) const {
+			return _index > rhs._index;
+		}
 	};
 
 	using iterator = typename Common::Array<T>::iterator;
@@ -155,6 +158,12 @@ public:
 	const_reverse_iterator rend() const {
 		return const_reverse_iterator(this, -1);
 	}
+	const_reverse_iterator crbegin() const {
+		return const_reverse_iterator(this, (int)Common::Array<T>::size() - 1);
+	}
+	const_reverse_iterator crend() const {
+		return const_reverse_iterator(this, -1);
+	}
 
 	void pop_front() {
 		Common::Array<T>::remove_at(0);


Commit: d8d593079a08662c2d8b32d7ac71dfc84646f19e
    https://github.com/scummvm/scummvm/commit/d8d593079a08662c2d8b32d7ac71dfc84646f19e
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:22-07:00

Commit Message:
AGS: Don't MarkChanged guis changing Transparency or Visible flag

>From upstream d4deb499893109a54ed7e011af22d2619c432549

Changed paths:
    engines/ags/shared/gui/gui_main.cpp
    engines/ags/shared/gui/gui_main.h


diff --git a/engines/ags/shared/gui/gui_main.cpp b/engines/ags/shared/gui/gui_main.cpp
index 67012f55e97..3a280fa0684 100644
--- a/engines/ags/shared/gui/gui_main.cpp
+++ b/engines/ags/shared/gui/gui_main.cpp
@@ -403,7 +403,6 @@ void GUIMain::SetConceal(bool on) {
 		_flags |= kGUIMain_Concealed;
 	else
 		_flags &= ~kGUIMain_Concealed;
-	MarkChanged();
 }
 
 bool GUIMain::SendControlToBack(int32_t index) {
@@ -448,7 +447,6 @@ void GUIMain::SetTextWindow(bool on) {
 
 void GUIMain::SetTransparencyAsPercentage(int percent) {
 	Transparency = GfxDef::Trans100ToLegacyTrans255(percent);
-	MarkChanged();
 }
 
 void GUIMain::SetVisible(bool on) {
@@ -456,7 +454,6 @@ void GUIMain::SetVisible(bool on) {
 		_flags |= kGUIMain_Visible;
 	else
 		_flags &= ~kGUIMain_Visible;
-	MarkChanged();
 }
 
 void GUIMain::OnMouseButtonDown(int mx, int my) {
diff --git a/engines/ags/shared/gui/gui_main.h b/engines/ags/shared/gui/gui_main.h
index 7ad5fcfe988..dce0388725f 100644
--- a/engines/ags/shared/gui/gui_main.h
+++ b/engines/ags/shared/gui/gui_main.h
@@ -102,6 +102,8 @@ public:
 	bool        HasChanged() const;
 	bool        HasControlsChanged() const;
 	// Manually marks GUI as graphically changed
+	// NOTE: this only matters if GUI's own graphic changes (content, size etc),
+	// but not its state (visible) or texture drawing mode (transparency, etc).
 	void        MarkChanged();
 	void        MarkControlsChanged();
 	// Clears changed flag


Commit: ec170ebd2f83d867b29737cff86c8bfaf477bda2
    https://github.com/scummvm/scummvm/commit/ec170ebd2f83d867b29737cff86c8bfaf477bda2
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:22-07:00

Commit Message:
AGS: More strict MarkChanged use for gui controls, on changes only

>From upstream 1383ff33eb4620eae18598f96f7e0cc948977174

Changed paths:
    engines/ags/engine/ac/button.cpp
    engines/ags/engine/ac/listbox.cpp
    engines/ags/engine/ac/slider.cpp
    engines/ags/engine/gui/gui_engine.cpp
    engines/ags/shared/gui/gui_button.cpp
    engines/ags/shared/gui/gui_label.cpp
    engines/ags/shared/gui/gui_listbox.cpp
    engines/ags/shared/gui/gui_object.cpp
    engines/ags/shared/gui/gui_object.h
    engines/ags/shared/gui/gui_slider.cpp


diff --git a/engines/ags/engine/ac/button.cpp b/engines/ags/engine/ac/button.cpp
index 8c3c8b0680f..ae62bb535bc 100644
--- a/engines/ags/engine/ac/button.cpp
+++ b/engines/ags/engine/ac/button.cpp
@@ -47,10 +47,12 @@ using namespace AGS::Shared;
 // Update the actual button's image from the current animation frame
 void UpdateButtonState(const AnimatingGUIButton &abtn) {
 	_GP(guibuts)[abtn.buttonid].Image = _GP(views)[abtn.view].loops[abtn.loop].frames[abtn.frame].pic;
-	_GP(guibuts)[abtn.buttonid].CurrentImage = _GP(guibuts)[abtn.buttonid].Image;
+	if (_GP(guibuts)[abtn.buttonid].CurrentImage != _GP(guibuts)[abtn.buttonid].Image) {
+		_GP(guibuts)[abtn.buttonid].CurrentImage = _GP(guibuts)[abtn.buttonid].Image;
+		_GP(guibuts)[abtn.buttonid].MarkChanged();
+	}
 	_GP(guibuts)[abtn.buttonid].PushedImage = 0;
 	_GP(guibuts)[abtn.buttonid].MouseOverImage = 0;
-	_GP(guibuts)[abtn.buttonid].MarkChanged();
 }
 
 void Button_AnimateEx(GUIButton *butt, int view, int loop, int speed, int repeat, int blocking, int direction, int sframe) {
@@ -170,11 +172,11 @@ int Button_GetMouseOverGraphic(GUIButton *butt) {
 void Button_SetMouseOverGraphic(GUIButton *guil, int slotn) {
 	debug_script_log("GUI %d Button %d mouseover set to slot %d", guil->ParentId, guil->Id, slotn);
 
-	if ((guil->IsMouseOver != 0) && (guil->IsPushed == 0))
+	if ((guil->IsMouseOver != 0) && (guil->IsPushed == 0) && (guil->CurrentImage != slotn)) {
 		guil->CurrentImage = slotn;
+		guil->MarkChanged();
+	}
 	guil->MouseOverImage = slotn;
-
-	guil->MarkChanged();
 	FindAndRemoveButtonAnimation(guil->ParentId, guil->Id);
 }
 
@@ -184,20 +186,26 @@ int Button_GetNormalGraphic(GUIButton *butt) {
 
 void Button_SetNormalGraphic(GUIButton *guil, int slotn) {
 	debug_script_log("GUI %d Button %d normal set to slot %d", guil->ParentId, guil->Id, slotn);
-	// normal pic - update if mouse is not over, or if there's no MouseOverImage
-	if (((guil->IsMouseOver == 0) || (guil->MouseOverImage < 1)) && (guil->IsPushed == 0))
-		guil->CurrentImage = slotn;
-	guil->Image = slotn;
 	// update the clickable area to the same size as the graphic
+	int width, height;
 	if (slotn < 0 || (size_t)slotn >= _GP(game).SpriteInfos.size()) {
-		guil->Width = 0;
-		guil->Height = 0;
+		width = 0;
+		height = 0;
 	} else {
-		guil->Width = _GP(game).SpriteInfos[slotn].Width;
-		guil->Height = _GP(game).SpriteInfos[slotn].Height;
+		width = _GP(game).SpriteInfos[slotn].Width;
+		height = _GP(game).SpriteInfos[slotn].Height;
+	}
+
+	if ((slotn != guil->Image) || (width != guil->Width) || (height != guil->Height)) {
+		// normal pic - update if mouse is not over, or if there's no MouseOverImage
+		if (((guil->IsMouseOver == 0) || (guil->MouseOverImage < 1)) && (guil->IsPushed == 0))
+			guil->CurrentImage = slotn;
+		guil->Image = slotn;
+		guil->Width = width;
+		guil->Height = height;
+		guil->MarkChanged();
 	}
 
-	guil->MarkChanged();
 	FindAndRemoveButtonAnimation(guil->ParentId, guil->Id);
 }
 
@@ -208,11 +216,11 @@ int Button_GetPushedGraphic(GUIButton *butt) {
 void Button_SetPushedGraphic(GUIButton *guil, int slotn) {
 	debug_script_log("GUI %d Button %d pushed set to slot %d", guil->ParentId, guil->Id, slotn);
 
-	if (guil->IsPushed)
+	if (guil->IsPushed && (guil->CurrentImage != slotn)) {
 		guil->CurrentImage = slotn;
+		guil->MarkChanged();
+	}
 	guil->PushedImage = slotn;
-
-	guil->MarkChanged();
 	FindAndRemoveButtonAnimation(guil->ParentId, guil->Id);
 }
 
diff --git a/engines/ags/engine/ac/listbox.cpp b/engines/ags/engine/ac/listbox.cpp
index 0e2667cfdb0..9435527b427 100644
--- a/engines/ags/engine/ac/listbox.cpp
+++ b/engines/ags/engine/ac/listbox.cpp
@@ -338,13 +338,14 @@ int ListBox_GetTopItem(GUIListBox *listbox) {
 }
 
 void ListBox_SetTopItem(GUIListBox *guisl, int item) {
-	if ((guisl->ItemCount == 0) && (item == 0))
-		;  // allow resetting an empty box to the top
-	else if ((item >= guisl->ItemCount) || (item < 0))
-		quit("!ListBoxSetTopItem: tried to set top to beyond top or bottom of list");
-
-	guisl->TopItem = item;
-	guisl->MarkChanged();
+	if ((item >= guisl->ItemCount) || (item < 0)) {
+		item = Math::Clamp(item, 0, guisl->ItemCount);
+		debug_script_warn("ListBoxSetTopItem: tried to set top to beyond top or bottom of list");
+	}
+	if (guisl->TopItem != item) {
+		guisl->TopItem = item;
+		guisl->MarkChanged();
+	}
 }
 
 int ListBox_GetRowCount(GUIListBox *listbox) {
diff --git a/engines/ags/engine/ac/slider.cpp b/engines/ags/engine/ac/slider.cpp
index a9663d276f5..93b15c55ff8 100644
--- a/engines/ags/engine/ac/slider.cpp
+++ b/engines/ags/engine/ac/slider.cpp
@@ -22,13 +22,14 @@
 #include "ags/engine/ac/slider.h"
 #include "ags/shared/ac/common.h"
 #include "ags/shared/debugging/out.h"
+#include "ags/shared/util/math.h"
 #include "ags/engine/script/script_api.h"
 #include "ags/engine/script/script_runtime.h"
 #include "ags/globals.h"
 
 namespace AGS3 {
 
-// *** SLIDER FUNCTIONS
+using namespace AGS::Shared;
 
 void Slider_SetMax(GUISlider *guisl, int valn) {
 
@@ -69,8 +70,7 @@ int Slider_GetMin(GUISlider *guisl) {
 }
 
 void Slider_SetValue(GUISlider *guisl, int valn) {
-	if (valn > guisl->MaxValue) valn = guisl->MaxValue;
-	if (valn < guisl->MinValue) valn = guisl->MinValue;
+	valn = Math::Clamp(valn, guisl->MinValue, guisl->MaxValue);
 
 	if (valn != guisl->Value) {
 		guisl->Value = valn;
diff --git a/engines/ags/engine/gui/gui_engine.cpp b/engines/ags/engine/gui/gui_engine.cpp
index 3b3e2088a70..61a08412193 100644
--- a/engines/ags/engine/gui/gui_engine.cpp
+++ b/engines/ags/engine/gui/gui_engine.cpp
@@ -106,6 +106,10 @@ void GUIObject::MarkChanged() {
 	_GP(guis)[ParentId].MarkControlsChanged();
 }
 
+void GUIObject::NotifyParentChanged() {
+	_GP(guis)[ParentId].MarkControlsChanged();
+}
+
 bool GUIObject::HasChanged() const {
 	return _hasChanged;
 }
diff --git a/engines/ags/shared/gui/gui_button.cpp b/engines/ags/shared/gui/gui_button.cpp
index fdd6a997b16..64dd5e3dc7d 100644
--- a/engines/ags/shared/gui/gui_button.cpp
+++ b/engines/ags/shared/gui/gui_button.cpp
@@ -163,14 +163,18 @@ void GUIButton::Draw(Bitmap *ds, int x, int y) {
 }
 
 void GUIButton::SetClipImage(bool on) {
+	if (on != ((Flags & kGUICtrl_Clip) != 0))
+		MarkChanged();
 	if (on)
 		Flags |= kGUICtrl_Clip;
 	else
 		Flags &= ~kGUICtrl_Clip;
-	MarkChanged();
 }
 
 void GUIButton::SetText(const String &text) {
+	if (_text == text)
+		return;
+
 	_text = text;
 	// Active inventory item placeholders
 	if (_text.CompareNoCase("(INV)") == 0)
diff --git a/engines/ags/shared/gui/gui_label.cpp b/engines/ags/shared/gui/gui_label.cpp
index 26c0c7eafb3..83719a81e59 100644
--- a/engines/ags/shared/gui/gui_label.cpp
+++ b/engines/ags/shared/gui/gui_label.cpp
@@ -102,6 +102,8 @@ void GUILabel::Draw(Bitmap *ds, int x, int y) {
 }
 
 void GUILabel::SetText(const String &text) {
+	if (text == Text)
+		return;
 	Text = text;
 	// Check for macros within text
 	_textMacro = GUI::FindLabelMacros(Text);
diff --git a/engines/ags/shared/gui/gui_listbox.cpp b/engines/ags/shared/gui/gui_listbox.cpp
index 92ff4b3f552..89da68878c7 100644
--- a/engines/ags/shared/gui/gui_listbox.cpp
+++ b/engines/ags/shared/gui/gui_listbox.cpp
@@ -114,6 +114,9 @@ int GUIListBox::AddItem(const String &text) {
 }
 
 void GUIListBox::Clear() {
+	if (Items.size() == 0)
+		return;
+
 	Items.clear();
 	SavedGameIndex.clear();
 	ItemCount = 0;
@@ -223,19 +226,21 @@ void GUIListBox::RemoveItem(int index) {
 }
 
 void GUIListBox::SetShowArrows(bool on) {
+	if (on != ((ListBoxFlags & kListBox_ShowArrows) != 0))
+		MarkChanged();
 	if (on)
 		ListBoxFlags |= kListBox_ShowArrows;
 	else
 		ListBoxFlags &= ~kListBox_ShowArrows;
-	MarkChanged();
 }
 
 void GUIListBox::SetShowBorder(bool on) {
+	if (on != ((ListBoxFlags & kListBox_ShowBorder) != 0))
+		MarkChanged();
 	if (on)
 		ListBoxFlags |= kListBox_ShowBorder;
 	else
 		ListBoxFlags &= ~kListBox_ShowBorder;
-	MarkChanged();
 }
 
 void GUIListBox::SetSvgIndex(bool on) {
@@ -246,13 +251,15 @@ void GUIListBox::SetSvgIndex(bool on) {
 }
 
 void GUIListBox::SetFont(int font) {
+	if (Font == font)
+		return;
 	Font = font;
 	UpdateMetrics();
 	MarkChanged();
 }
 
 void GUIListBox::SetItemText(int index, const String &text) {
-	if (index >= 0 && index < ItemCount) {
+	if ((index >= 0) && (index < ItemCount) && (text != Items[index])) {
 		Items[index] = text;
 		MarkChanged();
 	}
diff --git a/engines/ags/shared/gui/gui_object.cpp b/engines/ags/shared/gui/gui_object.cpp
index aac31a627b7..c5f044c0816 100644
--- a/engines/ags/shared/gui/gui_object.cpp
+++ b/engines/ags/shared/gui/gui_object.cpp
@@ -90,27 +90,30 @@ void GUIObject::SetClickable(bool on) {
 }
 
 void GUIObject::SetEnabled(bool on) {
+	if (on != ((Flags & kGUICtrl_Enabled) != 0))
+		MarkChanged();
 	if (on)
 		Flags |= kGUICtrl_Enabled;
 	else
 		Flags &= ~kGUICtrl_Enabled;
-	MarkChanged();
 }
 
 void GUIObject::SetTranslated(bool on) {
+	if (on != ((Flags & kGUICtrl_Translated) != 0))
+		MarkChanged();
 	if (on)
 		Flags |= kGUICtrl_Translated;
 	else
 		Flags &= ~kGUICtrl_Translated;
-	MarkChanged();
 }
 
 void GUIObject::SetVisible(bool on) {
+	if (on != ((Flags & kGUICtrl_Visible) != 0))
+		NotifyParentChanged(); // for software mode
 	if (on)
 		Flags |= kGUICtrl_Visible;
 	else
 		Flags &= ~kGUICtrl_Visible;
-	MarkChanged();
 }
 
 // TODO: replace string serialization with StrUtil::ReadString and WriteString
diff --git a/engines/ags/shared/gui/gui_object.h b/engines/ags/shared/gui/gui_object.h
index c778be2367a..557a38e9f13 100644
--- a/engines/ags/shared/gui/gui_object.h
+++ b/engines/ags/shared/gui/gui_object.h
@@ -109,8 +109,12 @@ public:
 
 	// TODO: these members are currently public; hide them later
 public:
-	// Notifies parent GUI that this control has changed
+	// Manually marks GUIObject as graphically changed
+	// NOTE: this only matters if control's own graphic changes (content, size etc),
+	// but not its state (visible) or texture drawing mode (transparency, etc).
 	void     MarkChanged();
+	// Notifies parent GUI that this control has changed its state (but not graphic)
+	void     NotifyParentChanged();
 
 	bool     HasChanged() const;
 	void     ClearChanged();
diff --git a/engines/ags/shared/gui/gui_slider.cpp b/engines/ags/shared/gui/gui_slider.cpp
index 2704b3ea7e2..3daac0cc33d 100644
--- a/engines/ags/shared/gui/gui_slider.cpp
+++ b/engines/ags/shared/gui/gui_slider.cpp
@@ -207,13 +207,17 @@ void GUISlider::OnMouseMove(int x, int y) {
 	if (!IsMousePressed)
 		return;
 
+	int value;
 	if (IsHorizontal())
-		Value = (int)(((float)((x - X) - 2) * (float)(MaxValue - MinValue)) / (float)_handleRange) + MinValue;
+		value = (int)(((float)((x - X) - 2) * (float)(MaxValue - MinValue)) / (float)_handleRange) + MinValue;
 	else
-		Value = (int)(((float)(((Y + Height) - y) - 2) * (float)(MaxValue - MinValue)) / (float)_handleRange) + MinValue;
+		value = (int)(((float)(((Y + Height) - y) - 2) * (float)(MaxValue - MinValue)) / (float)_handleRange) + MinValue;
 
-	Value = Math::Clamp(Value, MinValue, MaxValue);
-	MarkChanged();
+	value = Math::Clamp(value, MinValue, MaxValue);
+	if (value != Value) {
+		Value = value;
+		MarkChanged();
+	}
 	IsActivated = true;
 }
 


Commit: fbd328b0ae86c18e3339e3b8299d512d9f818267
    https://github.com/scummvm/scummvm/commit/fbd328b0ae86c18e3339e3b8299d512d9f818267
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:23-07:00

Commit Message:
AGS: On changed or deleted dynamic sprite, also update sliders

>From upstream f675c7ff7eb33bc2969098ab59afffac078b95bb

Changed paths:
    engines/ags/engine/ac/game.cpp


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index d5f5874449f..f169adbe5ee 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -66,6 +66,7 @@
 #include "ags/shared/gfx/bitmap.h"
 #include "ags/engine/gfx/graphics_driver.h"
 #include "ags/shared/gui/gui_button.h"
+#include "ags/shared/gui/gui_slider.h"
 #include "ags/engine/gui/gui_dialog.h"
 #include "ags/engine/main/engine.h"
 #include "ags/engine/media/audio/audio_system.h"
@@ -1375,6 +1376,12 @@ void game_sprite_updated(int sprnum) {
 			_GP(guibuts)[i].MarkChanged();
 		}
 	}
+	// gui sliders
+	for (size_t i = 0; i < (size_t)_G(numguislider); ++i) {
+		if ((_GP(guislider)[i].BgImage == sprnum) || (_GP(guislider)[i].HandleImage == sprnum)) {
+			_GP(guislider)[i].MarkChanged();
+		}
+	}
 }
 
 void game_sprite_deleted(int sprnum) {
@@ -1414,6 +1421,15 @@ void game_sprite_deleted(int sprnum) {
 			_GP(guibuts)[i].MarkChanged();
 		}
 	}
+	// gui sliders
+	for (size_t i = 0; i < (size_t)_G(numguislider); ++i) {
+		if ((_GP(guislider)[i].BgImage == sprnum) || (_GP(guislider)[i].HandleImage == sprnum))
+			_GP(guislider)[i].MarkChanged();
+		if (_GP(guislider)[i].BgImage == sprnum)
+			_GP(guislider)[i].BgImage = 0;
+		if (_GP(guislider)[i].HandleImage == sprnum)
+			_GP(guislider)[i].HandleImage = 0;
+	}
 	// views
 	for (size_t v = 0; v < (size_t)_GP(game).numviews; ++v) {
 		for (size_t l = 0; l < (size_t)_GP(views)[v].numLoops; ++l) {


Commit: b9a9b00974ebb475fa1375f7f2dd352df7109360
    https://github.com/scummvm/scummvm/commit/b9a9b00974ebb475fa1375f7f2dd352df7109360
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:23-07:00

Commit Message:
AGS: Graphic renderers use correct alpha as sprite transparency

>From upstream d2502a5240c6c06eb2fb65a9bf90b30584d79051

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/event.cpp
    engines/ags/engine/gfx/ali_3d_scummvm.cpp
    engines/ags/engine/gfx/ali_3d_scummvm.h
    engines/ags/engine/gfx/ddb.h
    engines/ags/shared/gfx/gfx_def.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 9c43710e3e3..3341bd150ab 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -794,7 +794,7 @@ static void clear_sprite_list() {
 static void add_to_sprite_list(IDriverDependantBitmap *ddb, int x, int y, int zorder, bool isWalkBehind, int id = -1) {
 	assert(ddb);
 	// completely invisible, so don't draw it at all
-	if (ddb->GetTransparency() == 255)
+	if (ddb->GetAlpha() == 0)
 		return;
 
 	SpriteListEntry sprite;
@@ -1517,7 +1517,7 @@ void prepare_objects_for_drawing() {
 				_GP(actspsbmp)[useindx]->SetLightLevel(0);
 		}
 
-		_GP(actspsbmp)[useindx]->SetTransparency(_G(objs)[aa].transparent);
+		_GP(actspsbmp)[useindx]->SetAlpha(GfxDef::LegacyTrans255ToAlpha255(_G(objs)[aa].transparent));
 		add_to_sprite_list(_GP(actspsbmp)[useindx], atxp, atyp, usebasel, false);
 	}
 }
@@ -1810,7 +1810,7 @@ void prepare_characters_for_drawing() {
 		chin->actx = atxp;
 		chin->acty = atyp;
 
-		_GP(actspsbmp)[useindx]->SetTransparency(chin->transparency);
+		_GP(actspsbmp)[useindx]->SetAlpha(GfxDef::LegacyTrans255ToAlpha255(chin->transparency));
 		add_to_sprite_list(_GP(actspsbmp)[useindx], bgX, bgY, usebasel, false);
 	}
 }
@@ -2017,7 +2017,7 @@ void draw_gui_and_overlays() {
 	// Add active overlays to the sprite list
 	for (auto &over : _GP(screenover)) {
 		if (over.transparency == 255) continue; // skip fully transparent
-		over.bmp->SetTransparency(over.transparency);
+		over.bmp->SetAlpha(GfxDef::LegacyTrans255ToAlpha255(over.transparency));
 		int tdxp, tdyp;
 		get_overlay_position(over, &tdxp, &tdyp);
 		add_to_sprite_list(over.bmp, tdxp, tdyp, over.zorder, false, -1);
@@ -2093,7 +2093,7 @@ void draw_gui_and_overlays() {
 			auto *gui_ddb = _GP(guibgbmp)[aa];
 			assert(gui_ddb); // Test for missing texture, might happen if not marked for update
 			if (!gui_ddb) continue;
-			gui_ddb->SetTransparency(_GP(guis)[aa].Transparency);
+			gui_ddb->SetAlpha(GfxDef::LegacyTrans255ToAlpha255(_GP(guis)[aa].Transparency));
 			add_to_sprite_list(gui_ddb, _GP(guis)[aa].X, _GP(guis)[aa].Y, _GP(guis)[aa].ZOrder, false, aa);
 		}
 
@@ -2138,7 +2138,7 @@ void draw_gui_and_overlays() {
 			auto *obj_ddb = _GP(guiobjbmp)[draw_index + obj_id];
 			assert(obj_ddb); // Test for missing texture, might happen if not marked for update
 			if (!obj_ddb) continue;
-			obj_ddb->SetTransparency(_GP(guis)[s.id].Transparency);
+			obj_ddb->SetAlpha(GfxDef::LegacyTrans255ToAlpha255(_GP(guis)[s.id].Transparency));
 			_G(gfxDriver)->DrawSprite(
 				_GP(guiobjoff)[draw_index + obj_id].X,
 				_GP(guiobjoff)[draw_index + obj_id].Y,
@@ -2155,7 +2155,7 @@ void put_sprite_list_on_screen(bool in_room) {
 	for (const auto &t : _GP(thingsToDrawList)) {
 		assert(t.ddb || (t.renderStage >= 0));
 		if (t.ddb) {
-			if (t.ddb->GetTransparency() == 255)
+			if (t.ddb->GetAlpha() == 0)
 				continue; // skip completely invisible things
 			// mark the image's region as dirty
 			invalidate_sprite(t.x, t.y, t.ddb, in_room);
@@ -2419,7 +2419,7 @@ void debug_draw_room_mask(RoomAreaMask mask) {
 	}
 
 	_G(debugRoomMaskDDB) = recycle_ddb_bitmap(_G(debugRoomMaskDDB), bmp, false, true);
-	_G(debugRoomMaskDDB)->SetTransparency(150);
+	_G(debugRoomMaskDDB)->SetAlpha(150);
 	_G(debugRoomMaskDDB)->SetStretch(_GP(thisroom).Width, _GP(thisroom).Height);
 }
 
@@ -2438,7 +2438,7 @@ void update_room_debug() {
 			bmp = _GP(debugRoomMaskBmp).get();
 		}
 		_G(debugRoomMaskDDB) = recycle_ddb_bitmap(_G(debugRoomMaskDDB), bmp, false, true);
-		_G(debugRoomMaskDDB)->SetTransparency(150);
+		_G(debugRoomMaskDDB)->SetAlpha(150);
 		_G(debugRoomMaskDDB)->SetStretch(_GP(thisroom).Width, _GP(thisroom).Height);
 	}
 	if (_G(debugMoveListChar) >= 0) {
@@ -2465,7 +2465,7 @@ void update_room_debug() {
 			}
 		}
 		_G(debugMoveListDDB) = recycle_ddb_bitmap(_G(debugMoveListDDB), _GP(debugMoveListBmp).get(), false, false);
-		_G(debugMoveListDDB)->SetTransparency(150);
+		_G(debugRoomMaskDDB)->SetAlpha(150);
 		_G(debugMoveListDDB)->SetStretch(_GP(thisroom).Width, _GP(thisroom).Height);
 	}
 }
diff --git a/engines/ags/engine/ac/event.cpp b/engines/ags/engine/ac/event.cpp
index d7cbd2c9a8c..924a2cd79a3 100644
--- a/engines/ags/engine/ac/event.cpp
+++ b/engines/ags/engine/ac/event.cpp
@@ -268,18 +268,14 @@ void process_event(const EventHappened *evp) {
 
 			IDriverDependantBitmap *ddb = prepare_screen_for_transition_in();
 
-			int transparency = 254;
-
-			while (transparency > 0) {
+			for (int alpha = 254; alpha > 0; alpha -= 16) {
 				// do the crossfade
-				ddb->SetTransparency(transparency);
+				ddb->SetAlpha(alpha);
 				invalidate_screen();
 				construct_game_scene(true);
 				construct_game_screen_overlay(false);
-
-				if (transparency > 16) {
-					// on last frame of fade (where transparency < 16), don't
-					// draw the old screen on top
+				// draw old screen on top while alpha > 16
+				if (alpha > 16) {
 					_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(), SpriteTransform());
 					_G(gfxDriver)->DrawSprite(0, 0, ddb);
 					_G(gfxDriver)->EndSpriteBatch();
@@ -287,7 +283,6 @@ void process_event(const EventHappened *evp) {
 				render_to_screen();
 				update_polled_stuff_if_runtime();
 				WaitForNextFrame();
-				transparency -= 16;
 			}
 
 			delete _G(saved_viewport_bitmap);
diff --git a/engines/ags/engine/gfx/ali_3d_scummvm.cpp b/engines/ags/engine/gfx/ali_3d_scummvm.cpp
index 2ff6e418d61..30bff013b8f 100644
--- a/engines/ags/engine/gfx/ali_3d_scummvm.cpp
+++ b/engines/ags/engine/gfx/ali_3d_scummvm.cpp
@@ -358,26 +358,25 @@ size_t ScummVMRendererGraphicsDriver::RenderSpriteBatch(const ALSpriteBatch &bat
 		int drawAtX = sprite.x + surf_offx;
 		int drawAtY = sprite.y + surf_offy;
 
-		if (bitmap->_transparency >= 255) {
+		if (bitmap->_alpha == 0) {
 		} // fully transparent, do nothing
-		else if ((bitmap->_opaque) && (bitmap->_bmp == surface) && (bitmap->_transparency == 0)) {
+		else if ((bitmap->_opaque) && (bitmap->_bmp == surface) && (bitmap->_alpha == 255)) {
 		} else if (bitmap->_opaque) {
 			surface->Blit(bitmap->_bmp, 0, 0, drawAtX, drawAtY, bitmap->_bmp->GetWidth(), bitmap->_bmp->GetHeight());
 			// TODO: we need to also support non-masked translucent blend, but...
 			// Allegro 4 **does not have such function ready** :( (only masked blends, where it skips magenta pixels);
 			// I am leaving this problem for the future, as coincidentally software mode does not need this atm.
 		} else if (bitmap->_hasAlpha) {
-			if (bitmap->_transparency == 0) // no global transparency, simple alpha blend
+			if (bitmap->_alpha == 255) // no global transparency, simple alpha blend
 				set_alpha_blender();
 			else
-				// here _transparency is used as alpha (between 1 and 254)
-				set_blender_mode(kArgbToRgbBlender, 0, 0, 0, bitmap->_transparency);
+				set_blender_mode(kArgbToRgbBlender, 0, 0, 0, bitmap->_alpha);
 
 			surface->TransBlendBlt(bitmap->_bmp, drawAtX, drawAtY);
 		} else {
 			// here _transparency is used as alpha (between 1 and 254), but 0 means opaque!
 			GfxUtil::DrawSpriteWithTransparency(surface, bitmap->_bmp, drawAtX, drawAtY,
-				bitmap->_transparency ? bitmap->_transparency : 255);
+				bitmap->_alpha);
 		}
 	}
 	return from;
diff --git a/engines/ags/engine/gfx/ali_3d_scummvm.h b/engines/ags/engine/gfx/ali_3d_scummvm.h
index e4a2f29adb8..b4a95236570 100644
--- a/engines/ags/engine/gfx/ali_3d_scummvm.h
+++ b/engines/ags/engine/gfx/ali_3d_scummvm.h
@@ -59,13 +59,11 @@ enum RendererFlip {
 
 class ALSoftwareBitmap : public BaseDDB {
 public:
-	// Transparency is a bit counter-intuitive
-	// 0=not transparent, 255=invisible, 1..254 barely visible .. mostly visible
-	int  GetTransparency() const override {
-		return _transparency;
+	int  GetAlpha() const override {
+		return _alpha;
 	}
-	void SetTransparency(int transparency) override {
-		_transparency = transparency;
+	void SetAlpha(int alpha) override {
+		_alpha = alpha;
 	}
 	void SetFlippedLeftRight(bool isFlipped) override {
 		_flipped = isFlipped;
@@ -80,9 +78,7 @@ public:
 	Bitmap *_bmp = nullptr;
 	bool _flipped = false;
 	int _stretchToWidth = 0, _stretchToHeight = 0;
-	bool _opaque = false; // no mask color
-	bool _hasAlpha = false;
-	int _transparency = 0;
+	int _alpha = 255;
 
 	ALSoftwareBitmap(int width, int height, int color_depth, bool opaque) {
 		_width = width;
diff --git a/engines/ags/engine/gfx/ddb.h b/engines/ags/engine/gfx/ddb.h
index 2945d6bb93c..804e73219fc 100644
--- a/engines/ags/engine/gfx/ddb.h
+++ b/engines/ags/engine/gfx/ddb.h
@@ -38,8 +38,8 @@ namespace Engine {
 
 class IDriverDependantBitmap {
 public:
-	virtual int  GetTransparency() const = 0;
-	virtual void SetTransparency(int transparency) = 0;  // 0-255
+	virtual int  GetAlpha() const = 0;
+	virtual void SetAlpha(int alpha) = 0;  // 0-255
 	virtual void SetFlippedLeftRight(bool isFlipped) = 0;
 	virtual void SetStretch(int width, int height, bool useResampler = true) = 0;
 	virtual void SetLightLevel(int light_level) = 0;   // 0-255
diff --git a/engines/ags/shared/gfx/gfx_def.h b/engines/ags/shared/gfx/gfx_def.h
index 57162163253..8b89917558e 100644
--- a/engines/ags/shared/gfx/gfx_def.h
+++ b/engines/ags/shared/gfx/gfx_def.h
@@ -43,10 +43,12 @@ enum BlendMode {
 
 namespace GfxDef {
 
+// Converts percentage of transparency into alpha
 inline int Trans100ToAlpha255(int transparency) {
 	return ((100 - transparency) * 255) / 100;
 }
 
+// Converts alpha into percentage of transparency
 inline int Alpha255ToTrans100(int alpha) {
 	return 100 - ((alpha * 100) / 255);
 }
@@ -67,25 +69,29 @@ inline int Alpha250ToTrans100(int alpha) {
 // 255 = invisible,
 // 1 -to- 254 = barely visible -to- mostly visible (as proper alpha)
 inline int Trans100ToLegacyTrans255(int transparency) {
-	if (transparency == 0) {
+	switch (transparency) {
+	case 0:
 		return 0; // this means opaque
-	} else if (transparency == 100) {
+	case 100:
 		return 255; // this means invisible
+	default:
+		// the rest of the range works as alpha
+		return Trans100ToAlpha250(transparency);
 	}
-	// the rest of the range works as alpha
-	return Trans100ToAlpha250(transparency);
 }
 
 // Convert legacy 255-ranged "incorrect" transparency into proper
 // 100-ranged transparency.
 inline int LegacyTrans255ToTrans100(int legacy_transparency) {
-	if (legacy_transparency == 0) {
+	switch (legacy_transparency) {
+	case 0:
 		return 0; // this means opaque
-	} else if (legacy_transparency == 255) {
+	case 255:
 		return 100; // this means invisible
+	default:
+		// the rest of the range works as alpha
+		return Alpha250ToTrans100(legacy_transparency);
 	}
-	// the rest of the range works as alpha
-	return Alpha250ToTrans100(legacy_transparency);
 }
 
 // Convert legacy 100-ranged transparency into proper 255-ranged alpha
@@ -93,13 +99,41 @@ inline int LegacyTrans255ToTrans100(int legacy_transparency) {
 // 100    => alpha 0
 // 1 - 99 => alpha 1 - 244
 inline int LegacyTrans100ToAlpha255(int legacy_transparency) {
-	if (legacy_transparency == 0) {
+	switch (legacy_transparency) {
+	case 0:
 		return 255; // this means opaque
-	} else if (legacy_transparency == 100) {
+	case 100:
 		return 0; // this means invisible
+	default:
+		// the rest of the range works as alpha (only 100-ranged)
+		return legacy_transparency * 255 / 100;
+	}
+}
+
+// Convert legacy 255-ranged transparency into proper 255-ranged alpha
+inline int LegacyTrans255ToAlpha255(int legacy_transparency) {
+	switch (legacy_transparency) {
+	case 0:
+		return 255; // this means opaque
+	case 255:
+		return 0; // this means invisible
+	default:
+		// the rest of the range works as alpha
+		return legacy_transparency;
+	}
+}
+
+// Convert 255-ranged alpha into legacy 255-ranged transparency
+inline int Alpha255ToLegacyTrans255(int alpha) {
+	switch (alpha) {
+	case 255:
+		return 0; // this means opaque
+	case 0:
+		return 255; // this means invisible
+	default:
+		// the rest of the range works as alpha
+		return alpha;
 	}
-	// the rest of the range works as alpha (only 100-ranged)
-	return legacy_transparency * 255 / 100;
 }
 
 } // namespace GfxDef


Commit: 8b027e0d8274ed3e8fb8c7a0f186e001bd98f609
    https://github.com/scummvm/scummvm/commit/8b027e0d8274ed3e8fb8c7a0f186e001bd98f609
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:23-07:00

Commit Message:
AGS: Support sprite batch's alpha

>From upstream 190897b97c8959954fea3f3561923e1b1c73a2b6

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/gfx/graphics_driver.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 3341bd150ab..2cfc265b549 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -2128,7 +2128,8 @@ void draw_gui_and_overlays() {
 		_G(gfxDriver)->DrawSprite(s.x, s.y, s.ddb);
 		if (s.id < 0) continue; // not a group parent (gui)
 		// Create a sub-batch
-		_G(gfxDriver)->BeginSpriteBatch(RectWH(s.x, s.y, s.ddb->GetWidth(), s.ddb->GetHeight()), SpriteTransform());
+		_G(gfxDriver)->BeginSpriteBatch(RectWH(s.x, s.y, s.ddb->GetWidth(), s.ddb->GetHeight()),
+			SpriteTransform(0, 0, 1.f, 1.f, 0.f, s.ddb->GetAlpha()));
 		const int draw_index = _GP(guiobjbmpref)[s.id];
 		for (const auto &obj_id : _GP(guis)[s.id].GetControlsDrawOrder()) {
 			GUIObject *obj = _GP(guis)[s.id].GetControl(obj_id);
@@ -2138,7 +2139,7 @@ void draw_gui_and_overlays() {
 			auto *obj_ddb = _GP(guiobjbmp)[draw_index + obj_id];
 			assert(obj_ddb); // Test for missing texture, might happen if not marked for update
 			if (!obj_ddb) continue;
-			obj_ddb->SetAlpha(GfxDef::LegacyTrans255ToAlpha255(_GP(guis)[s.id].Transparency));
+			obj_ddb->SetAlpha(255);
 			_G(gfxDriver)->DrawSprite(
 				_GP(guiobjoff)[draw_index + obj_id].X,
 				_GP(guiobjoff)[draw_index + obj_id].Y,
diff --git a/engines/ags/engine/gfx/graphics_driver.h b/engines/ags/engine/gfx/graphics_driver.h
index a8fa2d6381a..b893ca31da1 100644
--- a/engines/ags/engine/gfx/graphics_driver.h
+++ b/engines/ags/engine/gfx/graphics_driver.h
@@ -56,20 +56,27 @@ enum TintMethod {
 	TintSpecifyMaximum = 1
 };
 
+struct SpriteColorTransform {
+	int Alpha = 255; // alpha color value (0 - 255)
+
+	SpriteColorTransform() = default;
+	SpriteColorTransform(int alpha) : Alpha(alpha) {
+	}
+};
+
 // Sprite transformation
 // TODO: combine with stretch parameters in the IDriverDependantBitmap?
 struct SpriteTransform {
 	// Translate
-	int X, Y;
-	float ScaleX, ScaleY;
-	float Rotate; // angle, in radians
-
-	SpriteTransform()
-		: X(0), Y(0), ScaleX(1.f), ScaleY(1.f), Rotate(0.f) {
-	}
-
-	SpriteTransform(int x, int y, float scalex = 1.0f, float scaley = 1.0f, float rotate = 0.0f)
-		: X(x), Y(y), ScaleX(scalex), ScaleY(scaley), Rotate(rotate) {
+	int X = 0, Y = 0;
+	float ScaleX = 1.f, ScaleY = 1.f;
+	float Rotate = 0.f; // angle, in radians
+	SpriteColorTransform Color;
+
+	SpriteTransform() = default;
+	SpriteTransform(int x, int y, float scalex = 1.0f, float scaley = 1.0f, float rotate = 0.0f,
+		SpriteColorTransform color = SpriteColorTransform())
+		: X(x), Y(y), ScaleX(scalex), ScaleY(scaley), Rotate(rotate), Color(color) {
 	}
 };
 


Commit: 8319217b7a528f57c4270bdd3b3cb9c9375772c9
    https://github.com/scummvm/scummvm/commit/8319217b7a528f57c4270bdd3b3cb9c9375772c9
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:23-07:00

Commit Message:
AGS: Added GUIControl.Transparency

>From upstream a7c406b1d162bf9ac3896ecb8e26bc6d5327ecfe

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/gui_control.cpp
    engines/ags/engine/game/savegame_components.cpp
    engines/ags/shared/gui/gui_defines.h
    engines/ags/shared/gui/gui_object.cpp
    engines/ags/shared/gui/gui_object.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 2cfc265b549..6f20667e4c8 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -2139,7 +2139,7 @@ void draw_gui_and_overlays() {
 			auto *obj_ddb = _GP(guiobjbmp)[draw_index + obj_id];
 			assert(obj_ddb); // Test for missing texture, might happen if not marked for update
 			if (!obj_ddb) continue;
-			obj_ddb->SetAlpha(255);
+			obj_ddb->SetAlpha(GfxDef::LegacyTrans255ToAlpha255(obj->GetTransparency()));
 			_G(gfxDriver)->DrawSprite(
 				_GP(guiobjoff)[draw_index + obj_id].X,
 				_GP(guiobjoff)[draw_index + obj_id].Y,
diff --git a/engines/ags/engine/ac/gui_control.cpp b/engines/ags/engine/ac/gui_control.cpp
index c34100a1f68..a88eddb098b 100644
--- a/engines/ags/engine/ac/gui_control.cpp
+++ b/engines/ags/engine/ac/gui_control.cpp
@@ -213,6 +213,16 @@ void GUIControl_BringToFront(GUIObject *guio) {
 	_GP(guis)[guio->ParentId].BringControlToFront(guio->Id);
 }
 
+int GUIControl_GetTransparency(GUIObject *guio) {
+	return GfxDef::LegacyTrans255ToTrans100(guio->GetTransparency());
+}
+
+void GUIControl_SetTransparency(GUIObject *guio, int trans) {
+	if ((trans < 0) | (trans > 100))
+		quit("!SetGUITransparency: transparency value must be between 0 and 100");
+	guio->SetTransparency(GfxDef::Trans100ToLegacyTrans255(trans));
+}
+
 //=============================================================================
 //
 // Script API Functions
@@ -362,6 +372,13 @@ RuntimeScriptValue Sc_GUIControl_SetZOrder(void *self, const RuntimeScriptValue
 	API_OBJCALL_VOID_PINT(GUIObject, GUIControl_SetZOrder);
 }
 
+RuntimeScriptValue Sc_GUIControl_GetTransparency(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+	API_OBJCALL_INT(GUIObject, GUIControl_GetTransparency);
+}
+
+RuntimeScriptValue Sc_GUIControl_SetTransparency(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+	API_OBJCALL_VOID_PINT(GUIObject, GUIControl_SetTransparency);
+}
 
 
 void RegisterGUIControlAPI() {
@@ -394,6 +411,8 @@ void RegisterGUIControlAPI() {
 	ccAddExternalObjectFunction("GUIControl::set_Y", Sc_GUIControl_SetY);
 	ccAddExternalObjectFunction("GUIControl::get_ZOrder", Sc_GUIControl_GetZOrder);
 	ccAddExternalObjectFunction("GUIControl::set_ZOrder", Sc_GUIControl_SetZOrder);
+	ccAddExternalObjectFunction("GUIControl::get_Transparency", Sc_GUIControl_GetTransparency);
+	ccAddExternalObjectFunction("GUIControl::set_Transparency", Sc_GUIControl_SetTransparency);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 0348c8bb4a1..52e4793ebe7 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "ags/lib/std/map.h"
+#include "ags/engine/game/savegame_components.h"
 #include "ags/shared/ac/audio_clip_type.h"
 #include "ags/shared/ac/common.h"
 #include "ags/shared/ac/dialog_topic.h"
@@ -41,7 +42,6 @@
 #include "ags/engine/ac/system.h"
 #include "ags/engine/ac/dynobj/cc_serializer.h"
 #include "ags/shared/debugging/out.h"
-#include "ags/engine/game/savegame_components.h"
 #include "ags/engine/game/savegame_internal.h"
 #include "ags/shared/gfx/bitmap.h"
 #include "ags/engine/gui/animating_gui_button.h"
@@ -1048,7 +1048,7 @@ ComponentHandler ComponentHandlers[] = {
 	},
 	{
 		"GUI",
-		kGuiSvgVersion_36020,
+		kGuiSvgVersion_36023,
 		kGuiSvgVersion_Initial,
 		WriteGUI,
 		ReadGUI
diff --git a/engines/ags/shared/gui/gui_defines.h b/engines/ags/shared/gui/gui_defines.h
index 10621fbaee2..bb1c44d6d70 100644
--- a/engines/ags/shared/gui/gui_defines.h
+++ b/engines/ags/shared/gui/gui_defines.h
@@ -185,7 +185,8 @@ enum GUITextBoxFlags {
 enum GuiSvgVersion {
 	kGuiSvgVersion_Initial = 0,
 	kGuiSvgVersion_350,
-	kGuiSvgVersion_36020
+	kGuiSvgVersion_36020,
+	kGuiSvgVersion_36023
 };
 
 enum GuiDisableStyle {
diff --git a/engines/ags/shared/gui/gui_object.cpp b/engines/ags/shared/gui/gui_object.cpp
index c5f044c0816..aaa1e49fc0a 100644
--- a/engines/ags/shared/gui/gui_object.cpp
+++ b/engines/ags/shared/gui/gui_object.cpp
@@ -38,6 +38,7 @@ GUIObject::GUIObject() {
 	Height = 0;
 	ZOrder = -1;
 	IsActivated = false;
+	_transparency = 0;
 	_scEventCount = 0;
 	_hasChanged = true;
 }
@@ -116,6 +117,13 @@ void GUIObject::SetVisible(bool on) {
 		Flags &= ~kGUICtrl_Visible;
 }
 
+void GUIObject::SetTransparency(int trans) {
+	if (_transparency != trans) {
+		_transparency = trans;
+		NotifyParentChanged(); // for software mode
+	}
+}
+
 // TODO: replace string serialization with StrUtil::ReadString and WriteString
 // methods in the future, to keep this organized.
 void GUIObject::WriteToFile(Stream *out) const {
@@ -177,6 +185,12 @@ void GUIObject::ReadFromSavegame(Stream *in, GuiSvgVersion svg_ver) {
 	ZOrder = in->ReadInt32();
 	// Dynamic state
 	IsActivated = in->ReadBool() ? 1 : 0;
+	if (svg_ver >= kGuiSvgVersion_36023) {
+		_transparency = in->ReadInt32();
+		in->ReadInt32(); // reserve 3 ints
+		in->ReadInt32();
+		in->ReadInt32();
+	}
 }
 
 void GUIObject::WriteToSavegame(Stream *out) const {
@@ -189,6 +203,10 @@ void GUIObject::WriteToSavegame(Stream *out) const {
 	out->WriteInt32(ZOrder);
 	// Dynamic state
 	out->WriteBool(IsActivated != 0);
+	out->WriteInt32(_transparency);
+	out->WriteInt32(0); // reserve 3 ints
+	out->WriteInt32(0);
+	out->WriteInt32(0);
 }
 
 
diff --git a/engines/ags/shared/gui/gui_object.h b/engines/ags/shared/gui/gui_object.h
index 557a38e9f13..1e8381e2e5f 100644
--- a/engines/ags/shared/gui/gui_object.h
+++ b/engines/ags/shared/gui/gui_object.h
@@ -60,7 +60,8 @@ public:
 	bool            IsVisible() const;
 	// implemented separately in engine and editor
 	bool            IsClickable() const;
-    // Compatibility: should the control's graphic be clipped to its x,y,w,h
+	int             GetTransparency() const { return _transparency; }
+	// Compatibility: should the control's graphic be clipped to its x,y,w,h
     virtual bool    IsContentClipped() const { return true; }
 	// Tells if the object image supports alpha channel
 	virtual bool    HasAlphaChannel() const { return false; }
@@ -78,6 +79,7 @@ public:
 	void            SetEnabled(bool on);
 	void            SetTranslated(bool on);
 	void            SetVisible(bool on);
+	void            SetTransparency(int trans);
 
 	// Events
 	// Key pressed for control
@@ -134,6 +136,7 @@ public:
 
 protected:
 	uint32_t Flags;      // generic style and behavior flags
+	int32_t  _transparency; // "incorrect" alpha (in legacy 255-range units)
 	bool     _hasChanged;
 
 	// TODO: explicit event names & handlers for every event


Commit: 3b20393a177cc9bb9b5ab449ab313d5b6e6da820
    https://github.com/scummvm/scummvm/commit/3b20393a177cc9bb9b5ab449ab313d5b6e6da820
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:23-07:00

Commit Message:
AGS: Implemented gui control transparency in software mode

>From upstream bcffc2cdeb24a426e2b048e8a98df9c84cb7c6f2

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/draw.h
    engines/ags/shared/gui/gui_main.cpp
    engines/ags/shared/gui/gui_main.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 6f20667e4c8..24473fad228 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -999,23 +999,30 @@ void repair_alpha_channel(Bitmap *dest, Bitmap *bgpic) {
 
 
 // used by GUI renderer to draw images
+// NOTE: use_alpha arg is for backward compatibility (legacy draw modes)
 void draw_gui_sprite(Bitmap *ds, int pic, int x, int y, bool use_alpha, BlendMode blend_mode) {
-	Bitmap *sprite = _GP(spriteset)[pic];
-	const bool ds_has_alpha  = ds->GetColorDepth() == 32;
-	const bool src_has_alpha = (_GP(game).SpriteInfos[pic].Flags & SPF_ALPHACHANNEL) != 0;
+	draw_gui_sprite(ds, use_alpha, x, y, _GP(spriteset)[pic],
+		(_GP(game).SpriteInfos[pic].Flags & SPF_ALPHACHANNEL) != 0, blend_mode);
+}
+
+void draw_gui_sprite(Bitmap *ds, bool use_alpha, int x, int y, Bitmap *sprite, bool src_has_alpha,
+	BlendMode blend_mode, int alpha) {
+	if (alpha <= 0)
+		return;
 
-	if (use_alpha && _GP(game).options[OPT_NEWGUIALPHA] == kGuiAlphaRender_Proper) {
-		GfxUtil::DrawSpriteBlend(ds, Point(x, y), sprite, blend_mode, ds_has_alpha, src_has_alpha);
+	const bool ds_has_alpha = use_alpha && (ds->GetColorDepth() == 32);
+	if (_GP(game).options[OPT_NEWGUIALPHA] == kGuiAlphaRender_Proper) {
+		GfxUtil::DrawSpriteBlend(ds, Point(x, y), sprite, blend_mode, ds_has_alpha, src_has_alpha, alpha);
 	}
 	// Backwards-compatible drawing
-	else if (use_alpha && ds_has_alpha && _GP(game).options[OPT_NEWGUIALPHA] == kGuiAlphaRender_AdditiveAlpha) {
+	else if (ds_has_alpha && (_GP(game).options[OPT_NEWGUIALPHA] == kGuiAlphaRender_AdditiveAlpha) && (alpha == 0xFF)) {
 		if (src_has_alpha)
 			set_additive_alpha_blender();
 		else
 			set_opaque_alpha_blender();
 		ds->TransBlendBlt(sprite, x, y);
 	} else {
-		GfxUtil::DrawSpriteWithTransparency(ds, sprite, x, y);
+		GfxUtil::DrawSpriteWithTransparency(ds, sprite, x, y, alpha);
 	}
 }
 
diff --git a/engines/ags/engine/ac/draw.h b/engines/ags/engine/ac/draw.h
index d6d4e09b9a8..5008276d141 100644
--- a/engines/ags/engine/ac/draw.h
+++ b/engines/ags/engine/ac/draw.h
@@ -139,6 +139,9 @@ void draw_sprite_slot_support_alpha(Shared::Bitmap *ds, bool ds_has_alpha, int x
                                     Shared::BlendMode blend_mode = Shared::kBlendMode_Alpha, int alpha = 0xFF);
 void draw_gui_sprite(Shared::Bitmap *ds, int pic, int x, int y, bool use_alpha = true, Shared::BlendMode blend_mode = Shared::kBlendMode_Alpha);
 void draw_gui_sprite_v330(Shared::Bitmap *ds, int pic, int x, int y, bool use_alpha = true, Shared::BlendMode blend_mode = Shared::kBlendMode_Alpha);
+void draw_gui_sprite(Shared::Bitmap *ds, bool use_alpha, int xpos, int ypos,
+	Shared::Bitmap *image, bool src_has_alpha, Shared::BlendMode blend_mode = Shared::kBlendMode_Alpha, int alpha = 0xFF);
+
 // Render game on screen
 void render_to_screen();
 // Callbacks for the graphics driver
diff --git a/engines/ags/shared/gui/gui_main.cpp b/engines/ags/shared/gui/gui_main.cpp
index 3a280fa0684..73d401f497f 100644
--- a/engines/ags/shared/gui/gui_main.cpp
+++ b/engines/ags/shared/gui/gui_main.cpp
@@ -261,6 +261,7 @@ void GUIMain::DrawWithControls(Bitmap *ds) {
 	if ((_G(all_buttons_disabled) >= 0) && (GUI::Options.DisabledStyle == kGuiDis_Blackout))
 		return; // don't draw GUI controls
 
+	Bitmap tempbmp; // in case we need transforms
 	for (size_t ctrl_index = 0; ctrl_index < _controls.size(); ++ctrl_index) {
 		set_eip_guiobj(_ctrlDrawOrder[ctrl_index]);
 
@@ -271,11 +272,20 @@ void GUIMain::DrawWithControls(Bitmap *ds) {
 		if (!objToDraw->IsVisible())
 			continue;
 
-		if (GUI::Options.ClipControls && objToDraw->IsContentClipped())
-			ds->SetClip(RectWH(objToDraw->X, objToDraw->Y, objToDraw->Width, objToDraw->Height));
-		else
-			ds->ResetClip();
-		objToDraw->Draw(ds, objToDraw->X, objToDraw->Y);
+		// Depending on draw properties - draw directly on the gui surface, or use a buffer
+		if (objToDraw->GetTransparency() == 0) {
+			if (GUI::Options.ClipControls && objToDraw->IsContentClipped())
+				ds->SetClip(RectWH(objToDraw->X, objToDraw->Y, objToDraw->Width, objToDraw->Height));
+			else
+				ds->ResetClip();
+			objToDraw->Draw(ds, objToDraw->X, objToDraw->Y);
+		} else {
+			const Rect rc = objToDraw->CalcGraphicRect(GUI::Options.ClipControls && objToDraw->IsContentClipped());
+			tempbmp.CreateTransparent(rc.GetWidth(), rc.GetHeight());
+			objToDraw->Draw(&tempbmp, objToDraw->X - rc.Left, objToDraw->Y - rc.Top);
+			draw_gui_sprite(ds, true, objToDraw->X, objToDraw->Y, &tempbmp, objToDraw->HasAlphaChannel(), kBlendMode_Alpha,
+				GfxDef::LegacyTrans255ToAlpha255(objToDraw->GetTransparency()));
+		}
 
 		int selectedColour = 14;
 
diff --git a/engines/ags/shared/gui/gui_main.h b/engines/ags/shared/gui/gui_main.h
index dce0388725f..4e111a19a25 100644
--- a/engines/ags/shared/gui/gui_main.h
+++ b/engines/ags/shared/gui/gui_main.h
@@ -258,6 +258,10 @@ extern int get_adjusted_spritewidth(int spr);
 extern int get_adjusted_spriteheight(int spr);
 extern bool is_sprite_alpha(int spr);
 
+extern void draw_gui_sprite(Shared::Bitmap *ds, bool use_alpha, int x, int y,
+	Shared::Bitmap *image, bool src_has_alpha,
+	Shared::BlendMode blend_mode, int alpha);
+
 #define SET_EIP(x) set_our_eip(x);
 extern void set_eip_guiobj(int eip);
 extern int get_eip_guiobj();


Commit: b2d5b6e58610ed9496de648c0f23e6d80fd2e4c1
    https://github.com/scummvm/scummvm/commit/b2d5b6e58610ed9496de648c0f23e6d80fd2e4c1
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:24-07:00

Commit Message:
AGS: Implemented Overlay.Width & Height working as scaling

>From upstream 3a27e81a710a1c3de4b745d84dca39554dc64385

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/overlay.cpp
    engines/ags/engine/ac/screen_overlay.cpp
    engines/ags/engine/ac/screen_overlay.h
    engines/ags/engine/game/savegame_components.cpp
    engines/ags/engine/main/update.cpp
    engines/ags/globals.cpp
    engines/ags/globals.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 24473fad228..686273e7e22 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -753,7 +753,7 @@ void draw_sprite_slot_support_alpha(Bitmap *ds, bool ds_has_alpha, int xpos, int
 IDriverDependantBitmap *recycle_ddb_bitmap(IDriverDependantBitmap *bimp, Bitmap *source, bool hasAlpha, bool opaque) {
 	if (bimp != nullptr) {
 		// same colour depth, width and height -> reuse
-		if (((bimp->GetColorDepth() + 1) / 8 == source->GetBPP()) &&
+		if ((bimp->GetColorDepth() == source->GetColorDepth()) &&
 		        (bimp->GetWidth() == source->GetWidth()) && (bimp->GetHeight() == source->GetHeight())) {
 			_G(gfxDriver)->UpdateDDBFromBitmap(bimp, source, hasAlpha);
 			return bimp;
@@ -2021,13 +2021,30 @@ void draw_gui_and_overlays() {
 
 	clear_sprite_list();
 
+	const bool is_software_mode = !_G(gfxDriver)->HasAcceleratedTransform();
 	// Add active overlays to the sprite list
-	for (auto &over : _GP(screenover)) {
+	if (_GP(overlaybmp).size() < _GP(screenover).size())
+		_GP(overlaybmp).resize(_GP(screenover).size());
+	for (size_t i = 0; i < _GP(screenover).size(); ++i) {
+		auto &over = _GP(screenover)[i];
 		if (over.transparency == 255) continue; // skip fully transparent
-		over.bmp->SetAlpha(GfxDef::LegacyTrans255ToAlpha255(over.transparency));
+		if (_GP(screenover)[i].HasChanged()) {
+			// For software mode - prepare transformed bitmap if necessary
+			Bitmap *use_bmp = over.pic;
+			if (is_software_mode && (over.pic->GetSize() != Size(over.scaleWidth, over.scaleHeight))) {
+				_GP(overlaybmp)[i] = recycle_bitmap(_GP(overlaybmp)[i], over.pic->GetColorDepth(), over.scaleWidth, over.scaleHeight);
+				_GP(overlaybmp)[i]->StretchBlt(over.pic, RectWH(_GP(overlaybmp)[i]->GetSize()));
+				use_bmp = _GP(overlaybmp)[i];
+			}
+			over.ddb = recycle_ddb_bitmap(over.ddb, use_bmp, over.hasAlphaChannel);
+			over.ClearChanged();
+		}
+
+		over.ddb->SetStretch(over.scaleWidth, over.scaleHeight);
+		over.ddb->SetAlpha(GfxDef::LegacyTrans255ToAlpha255(over.transparency));
 		int tdxp, tdyp;
 		get_overlay_position(over, &tdxp, &tdyp);
-		add_to_sprite_list(over.bmp, tdxp, tdyp, over.zorder, false, -1);
+		add_to_sprite_list(over.ddb, tdxp, tdyp, over.zorder, false, -1);
 	}
 
 	// Add GUIs
diff --git a/engines/ags/engine/ac/overlay.cpp b/engines/ags/engine/ac/overlay.cpp
index 60563c34a98..35a5d0bf138 100644
--- a/engines/ags/engine/ac/overlay.cpp
+++ b/engines/ags/engine/ac/overlay.cpp
@@ -33,6 +33,7 @@
 #include "ags/engine/ac/runtime_defines.h"
 #include "ags/engine/ac/screen_overlay.h"
 #include "ags/engine/ac/string.h"
+#include "ags/engine/debugging/debug_log.h"
 #include "ags/engine/gfx/graphics_driver.h"
 #include "ags/shared/gfx/bitmap.h"
 #include "ags/engine/script/runtime_script_value.h"
@@ -106,14 +107,41 @@ int Overlay_GetWidth(ScriptOverlay *scover) {
 	int ovri = find_overlay_of_type(scover->overlayId);
 	if (ovri < 0)
 		quit("!invalid overlay ID specified");
-	return game_to_data_coord(_GP(screenover)[ovri].pic->GetWidth());
+	return game_to_data_coord(_GP(screenover)[ovri].scaleWidth);
 }
 
 int Overlay_GetHeight(ScriptOverlay *scover) {
 	int ovri = find_overlay_of_type(scover->overlayId);
 	if (ovri < 0)
 		quit("!invalid overlay ID specified");
-	return game_to_data_coord(_GP(screenover)[ovri].pic->GetHeight());
+	return game_to_data_coord(_GP(screenover)[ovri].scaleHeight);
+}
+
+void Overlay_SetScaledSize(ScreenOverlay &over, int width, int height) {
+	data_to_game_coords(&width, &height);
+	if (width < 1 || height < 1) {
+		debug_script_warn("Overlay.SetSize: invalid dimensions: %d x %d", width, height);
+		return;
+	}
+	if ((width == over.scaleWidth) && (height == over.scaleHeight))
+		return; // no change
+	over.scaleWidth = width;
+	over.scaleHeight = height;
+	over.MarkChanged();
+}
+
+void Overlay_SetWidth(ScriptOverlay *scover, int width) {
+	int ovri = find_overlay_of_type(scover->overlayId);
+	if (ovri < 0)
+		quit("!invalid overlay ID specified");
+	Overlay_SetScaledSize(_GP(screenover)[ovri], width, game_to_data_coord(_GP(screenover)[ovri].scaleHeight));
+}
+
+void Overlay_SetHeight(ScriptOverlay *scover, int height) {
+	int ovri = find_overlay_of_type(scover->overlayId);
+	if (ovri < 0)
+		quit("!invalid overlay ID specified");
+	Overlay_SetScaledSize(_GP(screenover)[ovri], game_to_data_coord(_GP(screenover)[ovri].scaleWidth), height);
 }
 
 int Overlay_GetValid(ScriptOverlay *scover) {
@@ -211,9 +239,9 @@ static void invalidate_and_subref(ScreenOverlay &over, ScriptOverlay *&scover) {
 static void dispose_overlay(ScreenOverlay &over) {
 	delete over.pic;
 	over.pic = nullptr;
-	if (over.bmp != nullptr)
-		_G(gfxDriver)->DestroyDDB(over.bmp);
-	over.bmp = nullptr;
+	if (over.ddb != nullptr)
+		_G(gfxDriver)->DestroyDDB(over.ddb);
+	over.ddb = nullptr;
 	if (over.associatedOverlayHandle) // dispose script object if there are no more refs
 		ccAttemptDisposeObject(over.associatedOverlayHandle);
 }
@@ -272,11 +300,13 @@ size_t add_screen_overlay(int x, int y, int type, Shared::Bitmap *piccy, int pic
 
 	ScreenOverlay over;
 	over.pic = piccy;
-	over.bmp = _G(gfxDriver)->CreateDDBFromBitmap(piccy, alphaChannel);
+	over.ddb = nullptr; // is generated during first draw pass
 	over.x = x;
 	over.y = y;
 	over.offsetX = pic_offx;
 	over.offsetY = pic_offy;
+	over.scaleWidth = piccy->GetWidth();
+	over.scaleHeight = piccy->GetHeight();
 	// by default draw speech and portraits over GUI, and the rest under GUI
 	over.zorder = (type == OVER_TEXTMSG || type == OVER_PICTURE || type == OVER_TEXTSPEECH) ?
 		INT_MAX : INT_MIN;
@@ -298,6 +328,7 @@ size_t add_screen_overlay(int x, int y, int type, Shared::Bitmap *piccy, int pic
 		_GP(play).speech_face_scover = create_scriptobj_addref(over);
 	}
 
+	over.MarkChanged();
 	_GP(screenover).push_back(std::move(over));
 	return _GP(screenover).size() - 1;
 }
@@ -346,12 +377,10 @@ void get_overlay_position(const ScreenOverlay &over, int *x, int *y) {
 
 void recreate_overlay_ddbs() {
 	for (auto &over : _GP(screenover)) {
-		if (over.bmp)
-			_G(gfxDriver)->DestroyDDB(over.bmp);
-		if (over.pic)
-			over.bmp = _G(gfxDriver)->CreateDDBFromBitmap(over.pic, false);
-		else
-			over.bmp = nullptr;
+		if (over.ddb)
+			_G(gfxDriver)->DestroyDDB(over.ddb);
+		over.ddb = nullptr; // is generated during first draw pass
+		over.MarkChanged();
 	}
 }
 
@@ -415,10 +444,18 @@ RuntimeScriptValue Sc_Overlay_GetWidth(void *self, const RuntimeScriptValue *par
 	API_OBJCALL_INT(ScriptOverlay, Overlay_GetWidth);
 }
 
+RuntimeScriptValue Sc_Overlay_SetWidth(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+	API_OBJCALL_VOID_PINT(ScriptOverlay, Overlay_SetWidth);
+}
+
 RuntimeScriptValue Sc_Overlay_GetHeight(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_OBJCALL_INT(ScriptOverlay, Overlay_GetHeight);
 }
 
+RuntimeScriptValue Sc_Overlay_SetHeight(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+	API_OBJCALL_VOID_PINT(ScriptOverlay, Overlay_SetHeight);
+}
+
 RuntimeScriptValue Sc_Overlay_GetTransparency(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_OBJCALL_INT(ScriptOverlay, Overlay_GetTransparency);
 }
@@ -459,7 +496,9 @@ void RegisterOverlayAPI() {
 	ccAddExternalObjectFunction("Overlay::get_Y", Sc_Overlay_GetY);
 	ccAddExternalObjectFunction("Overlay::set_Y", Sc_Overlay_SetY);
 	ccAddExternalObjectFunction("Overlay::get_Width", Sc_Overlay_GetWidth);
+	ccAddExternalObjectFunction("Overlay::set_Width", Sc_Overlay_SetWidth);
 	ccAddExternalObjectFunction("Overlay::get_Height", Sc_Overlay_GetHeight);
+	ccAddExternalObjectFunction("Overlay::set_Height", Sc_Overlay_SetHeight);
 	ccAddExternalObjectFunction("Overlay::get_Transparency", Sc_Overlay_GetTransparency);
 	ccAddExternalObjectFunction("Overlay::set_Transparency", Sc_Overlay_SetTransparency);
 	ccAddExternalObjectFunction("Overlay::get_ZOrder", Sc_Overlay_GetZOrder);
diff --git a/engines/ags/engine/ac/screen_overlay.cpp b/engines/ags/engine/ac/screen_overlay.cpp
index 2074bf78e37..275b8eb60c2 100644
--- a/engines/ags/engine/ac/screen_overlay.cpp
+++ b/engines/ags/engine/ac/screen_overlay.cpp
@@ -27,12 +27,11 @@ namespace AGS3 {
 using AGS::Shared::Stream;
 
 void ScreenOverlay::ReadFromFile(Stream *in, int32_t cmp_ver) {
-	// Skipping bmp and pic pointer values
-	// TODO: find out if it's safe to just drop these pointers!! replace with unique_ptr?
-	bmp = nullptr;
 	pic = nullptr;
-	in->ReadInt32(); // bmp
-	hasSerializedBitmap = in->ReadInt32() != 0;
+	ddb = nullptr;
+	// Skipping pointers (were saved by old engine)
+	in->ReadInt32(); // ddb
+	hasSerializedBitmap = in->ReadInt32() != 0; // pic
 	type = in->ReadInt32();
 	x = in->ReadInt32();
 	y = in->ReadInt32();
@@ -48,14 +47,14 @@ void ScreenOverlay::ReadFromFile(Stream *in, int32_t cmp_ver) {
 	if (cmp_ver >= 2) {
 		zorder = in->ReadInt32();
 		transparency = in->ReadInt32();
-		in->ReadInt32(); // reserve 2 ints
-		in->ReadInt32();
+		scaleWidth = in->ReadInt32();
+		scaleHeight = in->ReadInt32();
 	}
 }
 
 void ScreenOverlay::WriteToFile(Stream *out) const {
 	// Writing bitmap "pointers" to correspond to full structure writing
-	out->WriteInt32(0); // bmp
+	out->WriteInt32(0); // ddb
 	out->WriteInt32(pic ? 1 : 0); // pic
 	out->WriteInt32(type);
 	out->WriteInt32(x);
@@ -71,8 +70,8 @@ void ScreenOverlay::WriteToFile(Stream *out) const {
 	// since cmp_ver = 2
 	out->WriteInt32(zorder);
 	out->WriteInt32(transparency);
-	out->WriteInt32(0); // reserve 2 ints
-	out->WriteInt32(0);
+	out->WriteInt32(scaleWidth);
+	out->WriteInt32(scaleHeight);
 }
 
 } // namespace AGS3
diff --git a/engines/ags/engine/ac/screen_overlay.h b/engines/ags/engine/ac/screen_overlay.h
index 31b90384c4f..19214bcbe72 100644
--- a/engines/ags/engine/ac/screen_overlay.h
+++ b/engines/ags/engine/ac/screen_overlay.h
@@ -42,8 +42,18 @@ class IDriverDependantBitmap;
 
 using namespace AGS; // FIXME later
 
+// Overlay class.
+// TODO: currently overlay creates and stores its own bitmap, even if
+// created using existing sprite. As a side-effect, changing that sprite
+// (if it were a dynamic one) will not affect overlay (unlike other objects).
+// For future perfomance optimization it may be desired to store sprite index
+// instead; but that would mean that overlay will have to receive sprite
+// changes. For backward compatibility there may be a game switch that
+// forces it to make a copy.
 struct ScreenOverlay {
-	Engine::IDriverDependantBitmap *bmp = nullptr;
+	// Texture
+	Engine::IDriverDependantBitmap *ddb = nullptr;
+	// Original bitmap
 	Shared::Bitmap *pic = nullptr;
 	bool hasAlphaChannel = false;
 	int type = 0, timeout = 0;
@@ -52,6 +62,8 @@ struct ScreenOverlay {
 	int x = 0, y = 0;
 	// Border/padding offset for the tiled text windows
 	int offsetX = 0, offsetY = 0;
+	// Width and height to stretch the texture to
+	int scaleWidth = 0, scaleHeight = 0;
 	int bgSpeechForChar = -1;
 	int associatedOverlayHandle = 0;
 	int zorder = INT_MIN;
@@ -59,8 +71,24 @@ struct ScreenOverlay {
 	bool hasSerializedBitmap = false;
 	int transparency = 0;
 
+	// Tells if Overlay has graphically changed recently
+	bool HasChanged() const {
+		return _hasChanged;
+	}
+	// Manually marks GUI as graphically changed
+	void MarkChanged() {
+		_hasChanged = true;
+	}
+	// Clears changed flag
+	void ClearChanged() {
+		_hasChanged = false;
+	}
+
 	void ReadFromFile(Shared::Stream *in, int32_t cmp_ver);
 	void WriteToFile(Shared::Stream *out) const;
+
+private:
+	bool _hasChanged = false;
 };
 
 } // namespace AGS3
diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp
index 52e4793ebe7..ee5fe72990b 100644
--- a/engines/ags/engine/game/savegame_components.cpp
+++ b/engines/ags/engine/game/savegame_components.cpp
@@ -774,6 +774,10 @@ HSaveError ReadOverlays(Stream *in, int32_t cmp_ver, const PreservedParams & /*p
 		over.ReadFromFile(in, cmp_ver);
 		if (over.hasSerializedBitmap)
 			over.pic = read_serialized_bitmap(in);
+		if (over.scaleWidth <= 0 || over.scaleHeight <= 0) {
+			over.scaleWidth = over.pic->GetWidth();
+			over.scaleHeight = over.pic->GetHeight();
+		}
 		_GP(screenover).push_back(over);
 	}
 	return HSaveError::None();
diff --git a/engines/ags/engine/main/update.cpp b/engines/ags/engine/main/update.cpp
index 4847bf06252..37c4c72cbec 100644
--- a/engines/ags/engine/main/update.cpp
+++ b/engines/ags/engine/main/update.cpp
@@ -410,7 +410,8 @@ void update_sierra_speech() {
 				DrawViewFrame(frame_pic, blink_vf, view_frame_x, view_frame_y, face_has_alpha);
 			}
 
-			_G(gfxDriver)->UpdateDDBFromBitmap(_GP(screenover)[_G(face_talking)].bmp, _GP(screenover)[_G(face_talking)].pic, face_has_alpha);
+			_GP(screenover)[_G(face_talking)].hasAlphaChannel = face_has_alpha;
+			_GP(screenover)[_G(face_talking)].MarkChanged();
 		}  // end if updatedFrame
 	}
 }
diff --git a/engines/ags/globals.cpp b/engines/ags/globals.cpp
index 5201df62621..3b453d90062 100644
--- a/engines/ags/globals.cpp
+++ b/engines/ags/globals.cpp
@@ -192,6 +192,7 @@ Globals::Globals() {
 	_guiobjbmp = new std::vector<Engine::IDriverDependantBitmap *>();
 	_guiobjoff = new std::vector<Point>();
 	_guiobjbmpref = new std::vector<int>();
+	_overlaybmp = new std::vector<Shared::Bitmap *>();
 
 	// draw_software.cpp globals
 	_BlackRects = new DirtyRects();
@@ -446,6 +447,7 @@ Globals::~Globals() {
 	delete _guiobjbmp;
 	delete _guiobjoff;
 	delete _guiobjbmpref;
+	delete _overlaybmp;
 
 	// draw_software.cpp globals
 	delete _BlackRects;
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index bc893abc87e..ab527217f13 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -612,6 +612,8 @@ public:
 	std::vector<Engine::IDriverDependantBitmap *> *_guiobjbmp;
 	std::vector<Point> *_guiobjoff; // because surface may be larger than logical position
 	std::vector<int> *_guiobjbmpref; // first control texture index of each GUI
+	// Overlay's cached transformed bitmap, for software mode
+	std::vector<Shared::Bitmap *> *_overlaybmp;
 
 	/**@}*/
 


Commit: 684463c903dcd20dfced1a89d79ff59cda59f9a4
    https://github.com/scummvm/scummvm/commit/684463c903dcd20dfced1a89d79ff59cda59f9a4
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:24-07:00

Commit Message:
AGS: Implemented readonly Overlay.GraphicWidth & GraphicHeight

>From upstream dbfa017cf808beb4522c260f4a9cce20ac0009ee

Changed paths:
    engines/ags/engine/ac/overlay.cpp


diff --git a/engines/ags/engine/ac/overlay.cpp b/engines/ags/engine/ac/overlay.cpp
index 35a5d0bf138..2966b1c4daa 100644
--- a/engines/ags/engine/ac/overlay.cpp
+++ b/engines/ags/engine/ac/overlay.cpp
@@ -117,6 +117,20 @@ int Overlay_GetHeight(ScriptOverlay *scover) {
 	return game_to_data_coord(_GP(screenover)[ovri].scaleHeight);
 }
 
+int Overlay_GetGraphicWidth(ScriptOverlay *scover) {
+	int ovri = find_overlay_of_type(scover->overlayId);
+	if (ovri < 0)
+		quit("!invalid overlay ID specified");
+	return game_to_data_coord(_GP(screenover)[ovri].pic->GetWidth());
+}
+
+int Overlay_GetGraphicHeight(ScriptOverlay *scover) {
+	int ovri = find_overlay_of_type(scover->overlayId);
+	if (ovri < 0)
+		quit("!invalid overlay ID specified");
+	return game_to_data_coord(_GP(screenover)[ovri].pic->GetHeight());
+}
+
 void Overlay_SetScaledSize(ScreenOverlay &over, int width, int height) {
 	data_to_game_coords(&width, &height);
 	if (width < 1 || height < 1) {
@@ -456,6 +470,14 @@ RuntimeScriptValue Sc_Overlay_SetHeight(void *self, const RuntimeScriptValue *pa
 	API_OBJCALL_VOID_PINT(ScriptOverlay, Overlay_SetHeight);
 }
 
+RuntimeScriptValue Sc_Overlay_GetGraphicWidth(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+	API_OBJCALL_INT(ScriptOverlay, Overlay_GetGraphicWidth);
+}
+
+RuntimeScriptValue Sc_Overlay_GetGraphicHeight(void *self, const RuntimeScriptValue *params, int32_t param_count) {
+	API_OBJCALL_INT(ScriptOverlay, Overlay_GetGraphicHeight);
+}
+
 RuntimeScriptValue Sc_Overlay_GetTransparency(void *self, const RuntimeScriptValue *params, int32_t param_count) {
 	API_OBJCALL_INT(ScriptOverlay, Overlay_GetTransparency);
 }
@@ -499,6 +521,8 @@ void RegisterOverlayAPI() {
 	ccAddExternalObjectFunction("Overlay::set_Width", Sc_Overlay_SetWidth);
 	ccAddExternalObjectFunction("Overlay::get_Height", Sc_Overlay_GetHeight);
 	ccAddExternalObjectFunction("Overlay::set_Height", Sc_Overlay_SetHeight);
+	ccAddExternalObjectFunction("Overlay::get_GraphicWidth", Sc_Overlay_GetGraphicWidth);
+	ccAddExternalObjectFunction("Overlay::get_GraphicHeight", Sc_Overlay_GetGraphicHeight);
 	ccAddExternalObjectFunction("Overlay::get_Transparency", Sc_Overlay_GetTransparency);
 	ccAddExternalObjectFunction("Overlay::set_Transparency", Sc_Overlay_SetTransparency);
 	ccAddExternalObjectFunction("Overlay::get_ZOrder", Sc_Overlay_GetZOrder);


Commit: a94adb59683fe0ab666d6c116ad1ac4f2f44c3d1
    https://github.com/scummvm/scummvm/commit/a94adb59683fe0ab666d6c116ad1ac4f2f44c3d1
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:24-07:00

Commit Message:
AGS: Fixed TextBox duplicating letter input

>From upstream 6aead72e207b33a991a5e3d2d32f7991007f786c

Changed paths:
    engines/ags/shared/gui/gui_textbox.cpp


diff --git a/engines/ags/shared/gui/gui_textbox.cpp b/engines/ags/shared/gui/gui_textbox.cpp
index 29393a1ea44..1f7a7ade5e5 100644
--- a/engines/ags/shared/gui/gui_textbox.cpp
+++ b/engines/ags/shared/gui/gui_textbox.cpp
@@ -72,28 +72,29 @@ static void Backspace(String &text) {
 }
 
 void GUITextBox::OnKeyPress(const KeyInput &ki) {
-	eAGSKeyCode keycode = ki.Key;
-
-	// other key, continue
-	if ((keycode >= 128) && (!font_supports_extended_characters(Font)))
-		return;
-	// return/enter
-	if (keycode == eAGSKeyCodeReturn) {
+	switch (ki.Key) {
+	case eAGSKeyCodeReturn:
 		IsActivated = true;
 		return;
-	}
-
-	MarkChanged();
-	// backspace, remove character
-	if (keycode == eAGSKeyCodeBackspace) {
+	case eAGSKeyCodeBackspace:
 		Backspace(Text);
+		MarkChanged();
 		return;
+	default: break;
 	}
 
-	Text.AppendChar(keycode);
+	if (ki.UChar == 0)
+		return; // not a textual event
+	if ((ki.UChar >= 128) && (!font_supports_extended_characters(Font)))
+		return; // unsupported letter
+
+	(get_uformat() == U_UTF8) ?
+		Text.Append(ki.Text) :
+		Text.AppendChar(ki.UChar);
 	// if the new string is too long, remove the new character
 	if (get_text_width(Text.GetCStr(), Font) > (Width - (6 + get_fixed_pixel_size(5))))
 		Backspace(Text);
+	MarkChanged();
 }
 
 void GUITextBox::SetShowBorder(bool on) {


Commit: 598acd02b8e588478c0088e8bf152d17d939e5a3
    https://github.com/scummvm/scummvm/commit/598acd02b8e588478c0088e8bf152d17d939e5a3
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:24-07:00

Commit Message:
AGS: Updated build version (3.6.0.23)

>From upstream 13499a1bd26822fd25e32b0855281e1534b30bd9

Changed paths:
    engines/ags/shared/core/def_version.h


diff --git a/engines/ags/shared/core/def_version.h b/engines/ags/shared/core/def_version.h
index 838d76323eb..81073776aeb 100644
--- a/engines/ags/shared/core/def_version.h
+++ b/engines/ags/shared/core/def_version.h
@@ -22,9 +22,9 @@
 #ifndef AGS_SHARED_CORE_DEFVERSION_H
 #define AGS_SHARED_CORE_DEFVERSION_H
 
-#define ACI_VERSION_STR      "3.6.0.22"
+#define ACI_VERSION_STR      "3.6.0.23"
 #if defined (RC_INVOKED) // for MSVC resource compiler
-#define ACI_VERSION_MSRC_DEF  3.6.0.22
+#define ACI_VERSION_MSRC_DEF  3.6.0.23
 #endif
 
 #define SPECIAL_VERSION ""


Commit: f84022353345c61009679a0fe2df9df8f15a76a1
    https://github.com/scummvm/scummvm/commit/f84022353345c61009679a0fe2df9df8f15a76a1
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:24-07:00

Commit Message:
AGS: Fixed GUI's background image with alpha channel

>From upstream d3820e0fd369516008633bbc12fc3da359067798

Changed paths:
    engines/ags/engine/ac/draw.cpp


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 686273e7e22..6f57f22c9ae 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -1010,12 +1010,12 @@ void draw_gui_sprite(Bitmap *ds, bool use_alpha, int x, int y, Bitmap *sprite, b
 	if (alpha <= 0)
 		return;
 
-	const bool ds_has_alpha = use_alpha && (ds->GetColorDepth() == 32);
-	if (_GP(game).options[OPT_NEWGUIALPHA] == kGuiAlphaRender_Proper) {
+	const bool ds_has_alpha = (ds->GetColorDepth() == 32);
+	if (use_alpha && _GP(game).options[OPT_NEWGUIALPHA] == kGuiAlphaRender_Proper) {
 		GfxUtil::DrawSpriteBlend(ds, Point(x, y), sprite, blend_mode, ds_has_alpha, src_has_alpha, alpha);
 	}
 	// Backwards-compatible drawing
-	else if (ds_has_alpha && (_GP(game).options[OPT_NEWGUIALPHA] == kGuiAlphaRender_AdditiveAlpha) && (alpha == 0xFF)) {
+	else if (use_alpha && ds_has_alpha && (_GP(game).options[OPT_NEWGUIALPHA] == kGuiAlphaRender_AdditiveAlpha) && (alpha == 0xFF)) {
 		if (src_has_alpha)
 			set_additive_alpha_blender();
 		else


Commit: 87994139f64c849ef79e220f1bf8210d08bded25
    https://github.com/scummvm/scummvm/commit/87994139f64c849ef79e220f1bf8210d08bded25
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:24-07:00

Commit Message:
AGS: Tidied gui drawing code a little

>From upstream c1bb2fb846c8ffe997ce2c1dda690467bc28edc4

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/globals.cpp
    engines/ags/globals.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 6f57f22c9ae..95c7c5b52e5 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -370,17 +370,17 @@ void init_game_drawdata() {
 	_GP(actspswbbmp).resize(actsps_num);
 	_GP(actspswbcache).resize(actsps_num);
 	_GP(guibg).resize(_GP(game).numgui);
-	_GP(guibgbmp).resize(_GP(game).numgui);
+	_GP(guibgddb).resize(_GP(game).numgui);
 
 	size_t guio_num = 0;
 	// Prepare GUI cache lists and build the quick reference for controls cache
-	_GP(guiobjbmpref).resize(_GP(game).numgui);
+	_GP(guiobjddbref).resize(_GP(game).numgui);
 	for (const auto &gui : _GP(guis)) {
-		_GP(guiobjbmpref)[gui.ID] = guio_num;
+		_GP(guiobjddbref)[gui.ID] = guio_num;
 		guio_num += gui.GetControlCount();
 	}
 	_GP(guiobjbg).resize(guio_num);
-	_GP(guiobjbmp).resize(guio_num);
+	_GP(guiobjddb).resize(guio_num);
 	_GP(guiobjoff).resize(guio_num);
 }
 
@@ -393,11 +393,11 @@ void dispose_game_drawdata() {
 	_GP(actspswbbmp).clear();
 	_GP(actspswbcache).clear();
 	_GP(guibg).clear();
-	_GP(guibgbmp).clear();
+	_GP(guibgddb).clear();
 
 	_GP(guiobjbg).clear();
-	_GP(guiobjbmp).clear();
-	_GP(guiobjbmpref).clear();
+	_GP(guiobjddb).clear();
+	_GP(guiobjddbref).clear();
 	_GP(guiobjoff).clear();
 }
 
@@ -444,17 +444,17 @@ void clear_drawobj_cache() {
 	for (int i = 0; i < _GP(game).numgui; ++i) {
 		delete _GP(guibg)[i];
 		_GP(guibg)[i] = nullptr;
-		if (_GP(guibgbmp)[i])
-			_G(gfxDriver)->DestroyDDB(_GP(guibgbmp)[i]);
-		_GP(guibgbmp)[i] = nullptr;
+		if (_GP(guibgddb)[i])
+			_G(gfxDriver)->DestroyDDB(_GP(guibgddb)[i]);
+		_GP(guibgddb)[i] = nullptr;
 	}
 
 	for (size_t i = 0; i < _GP(guiobjbg).size(); ++i) {
 		delete _GP(guiobjbg)[i];
 		_GP(guiobjbg)[i] = nullptr;
-		if (_GP(guiobjbmp)[i])
-			_G(gfxDriver)->DestroyDDB(_GP(guiobjbmp)[i]);
-		_GP(guiobjbmp)[i] = nullptr;
+		if (_GP(guiobjddb)[i])
+			_G(gfxDriver)->DestroyDDB(_GP(guiobjddb)[i]);
+		_GP(guiobjddb)[i] = nullptr;
 	}
 
 	dispose_debug_room_drawdata();
@@ -1049,16 +1049,6 @@ Bitmap *recycle_bitmap(Bitmap *bimp, int coldep, int wid, int hit, bool make_tra
 	return bimp;
 }
 
-// Allocates texture for the GUI
-void recreate_drawobj_bitmap(Bitmap *&raw, IDriverDependantBitmap *&ddb, int width, int height) {
-	delete raw;
-	raw = CreateCompatBitmap(width, height);
-	if (ddb != nullptr) {
-		_G(gfxDriver)->DestroyDDB(ddb);
-		ddb = nullptr;
-	}
-}
-
 // Get the local tint at the specified X & Y co-ordinates, based on
 // room regions and SetAmbientTint
 // tint_amnt will be set to 0 if there is no tint enabled
@@ -1979,7 +1969,7 @@ void draw_gui_controls(GUIMain &gui) {
 	if (_G(all_buttons_disabled) && (GUI::Options.DisabledStyle == kGuiDis_Blackout))
 		return; // don't draw GUI controls
 
-	int draw_index = _GP(guiobjbmpref)[gui.ID];
+	int draw_index = _GP(guiobjddbref)[gui.ID];
 	for (int i = 0; i < gui.GetControlCount(); ++i, ++draw_index) {
 		GUIObject *obj = gui.GetControl(i);
 		if (!obj->IsVisible() ||
@@ -1987,23 +1977,17 @@ void draw_gui_controls(GUIMain &gui) {
 			continue;
 		if (!obj->HasChanged())
 			continue;
-		obj->ClearChanged();
 
+		auto *&objbg_bmp = _GP(guiobjbg)[draw_index];
+		auto *&objbg_ddb = _GP(guiobjddb)[draw_index];
 		Rect obj_surf = obj->CalcGraphicRect(GUI::Options.ClipControls);
-		if (_GP(guiobjbg)[draw_index] == nullptr ||
-			_GP(guiobjbg)[draw_index]->GetSize() != obj_surf.GetSize()) {
-			recreate_drawobj_bitmap(_GP(guiobjbg)[draw_index], _GP(guiobjbmp)[draw_index],
-				obj_surf.GetWidth(), obj_surf.GetHeight());
-		}
-
-		_GP(guiobjbg)[draw_index]->ClearTransparent();
-		obj->Draw(_GP(guiobjbg)[draw_index], obj->X - obj_surf.Left, obj->Y - obj_surf.Top);
+		objbg_bmp = recycle_bitmap(objbg_bmp, _GP(game).GetColorDepth(), obj_surf.GetWidth(), obj_surf.GetHeight());
+		objbg_bmp->ClearTransparent();
+		obj->Draw(objbg_bmp, obj->X - obj_surf.Left, obj->Y - obj_surf.Top);
 
-		if (_GP(guiobjbmp)[draw_index] != nullptr)
-			_G(gfxDriver)->UpdateDDBFromBitmap(_GP(guiobjbmp)[draw_index], _GP(guiobjbg)[draw_index], obj->HasAlphaChannel());
-		else
-			_GP(guiobjbmp)[draw_index] = _G(gfxDriver)->CreateDDBFromBitmap(_GP(guiobjbg)[draw_index], obj->HasAlphaChannel());
+		objbg_ddb = recycle_ddb_bitmap(objbg_ddb, objbg_bmp, obj->HasAlphaChannel());
 		_GP(guiobjoff)[draw_index] = Point(obj_surf.GetLT());
+		obj->ClearChanged();
 	}
 }
 
@@ -2059,80 +2043,78 @@ void draw_gui_and_overlays() {
 		_G(our_eip) = 37;
 		// Prepare and update GUI textures
 		{
-			for (int aa = 0; aa < _GP(game).numgui; aa++) {
-				if (!_GP(guis)[aa].IsDisplayed()) continue; // not on screen
-				if (!_GP(guis)[aa].HasChanged() && !_GP(guis)[aa].HasControlsChanged()) continue; // no changes: no need to update image
-				if (_GP(guis)[aa].Transparency == 255) continue; // 100% transparent
-
-				if (_GP(guibg)[aa] == nullptr ||
-					_GP(guibg)[aa]->GetSize() != Size(_GP(guis)[aa].Width, _GP(guis)[aa].Height)) {
-					recreate_drawobj_bitmap(_GP(guibg)[aa], _GP(guibgbmp)[aa], _GP(guis)[aa].Width, _GP(guis)[aa].Height);
-				}
+			for (int index = 0; index < _GP(game).numgui; ++index) {
+				auto &gui = _GP(guis)[index];
+				if (!gui.IsDisplayed()) continue; // not on screen
+				if (!gui.HasChanged() && !gui.HasControlsChanged()) continue; // no changes: no need to update image
+				if (gui.Transparency == 255) continue; // 100% transparent
 
-				_G(eip_guinum) = aa;
+				auto *&guibg_bmp = _GP(guibg)[index];
+				auto *&guibg_ddb = _GP(guibgddb)[index];
+				guibg_bmp = recycle_bitmap(guibg_bmp, _GP(game).GetColorDepth(), gui.Width, gui.Height);
+
+				_G(eip_guinum) = index;
 				_G(our_eip) = 372;
 				const bool draw_with_controls = !draw_controls_as_textures;
-				if (_GP(guis)[aa].HasChanged() || (draw_with_controls && _GP(guis)[aa].HasControlsChanged())) {
-					_GP(guibg)[aa]->ClearTransparent();
+				if (gui.HasChanged() || (draw_with_controls && gui.HasControlsChanged())) {
+					guibg_bmp->ClearTransparent();
 					if (draw_with_controls)
-						_GP(guis)[aa].DrawWithControls(_GP(guibg)[aa]);
+						gui.DrawWithControls(guibg_bmp);
 					else
-						_GP(guis)[aa].DrawSelf(_GP(guibg)[aa]);
+						gui.DrawSelf(guibg_bmp);
 
-					const bool is_alpha = _GP(guis)[aa].HasAlphaChannel();
+					const bool is_alpha = gui.HasAlphaChannel();
 					if (is_alpha) {
-						if ((_GP(game).options[OPT_NEWGUIALPHA] == kGuiAlphaRender_Legacy) && (_GP(guis)[aa].BgImage > 0)) {
+						if ((_GP(game).options[OPT_NEWGUIALPHA] == kGuiAlphaRender_Legacy) && (gui.BgImage > 0)) {
 							// old-style (pre-3.0.2) GUI alpha rendering
-							repair_alpha_channel(_GP(guibg)[aa], _GP(spriteset)[_GP(guis)[aa].BgImage]);
+							repair_alpha_channel(guibg_bmp, _GP(spriteset)[gui.BgImage]);
 						}
 					}
 
-					if (_GP(guibgbmp)[aa])
-						_G(gfxDriver)->UpdateDDBFromBitmap(_GP(guibgbmp)[aa], _GP(guibg)[aa], is_alpha);
-					else
-						_GP(guibgbmp)[aa] = _G(gfxDriver)->CreateDDBFromBitmap(_GP(guibg)[aa], is_alpha);
+					guibg_ddb = recycle_ddb_bitmap(guibg_ddb, guibg_bmp, is_alpha);
 				}
 
 				_G(our_eip) = 373;
-				if (!draw_with_controls && _GP(guis)[aa].HasControlsChanged()) {
-					draw_gui_controls(_GP(guis)[aa]);
+				if (!draw_with_controls && gui.HasControlsChanged()) {
+					draw_gui_controls(gui);
 				}
 				_G(our_eip) = 374;
 
-				_GP(guis)[aa].ClearChanged();
+				gui.ClearChanged();
 			}
 		}
 		_G(our_eip) = 38;
 		// Draw the GUIs
-		for (int aa = 0; aa < _GP(game).numgui; ++aa) {
-			if (!_GP(guis)[aa].IsDisplayed()) continue; // not on screen
-			if (_GP(guis)[aa].Transparency == 255) continue; // 100% transparent
+		for (int index = 0; index < _GP(game).numgui; ++index) {
+			const auto &gui = _GP(guis)[index];
+			if (!gui.IsDisplayed()) continue; // not on screen
+			if (gui.Transparency == 255) continue; // 100% transparent
 
 			// Don't draw GUI if "GUIs Turn Off When Disabled"
 			if ((_GP(game).options[OPT_DISABLEOFF] == kGuiDis_Off) &&
 				(_G(all_buttons_disabled) >= 0) &&
-				(_GP(guis)[aa].PopupStyle != kGUIPopupNoAutoRemove))
+				(gui.PopupStyle != kGUIPopupNoAutoRemove))
 				continue;
 
-			auto *gui_ddb = _GP(guibgbmp)[aa];
+			auto *gui_ddb = _GP(guibgddb)[index];
 			assert(gui_ddb); // Test for missing texture, might happen if not marked for update
 			if (!gui_ddb) continue;
-			gui_ddb->SetAlpha(GfxDef::LegacyTrans255ToAlpha255(_GP(guis)[aa].Transparency));
-			add_to_sprite_list(gui_ddb, _GP(guis)[aa].X, _GP(guis)[aa].Y, _GP(guis)[aa].ZOrder, false, aa);
+			gui_ddb->SetAlpha(GfxDef::LegacyTrans255ToAlpha255(gui.Transparency));
+			add_to_sprite_list(gui_ddb, gui.X, gui.Y, gui.ZOrder, false, index);
 		}
 
 		// Poll the GUIs
 		// TODO: move this out of the draw routine into game update!!
 		if (IsInterfaceEnabled()) // only poll if the interface is enabled
 		{
-			for (int gg = 0; gg < _GP(game).numgui; gg++) {
-				if (!_GP(guis)[gg].IsDisplayed()) continue; // not on screen
+			for (auto &gui : _GP(guis)) {
+				if (!gui.IsDisplayed()) continue; // not on screen
 				// Don't touch GUI if "GUIs Turn Off When Disabled"
 				if ((_GP(game).options[OPT_DISABLEOFF] == kGuiDis_Off) &&
 					(_G(all_buttons_disabled) >= 0) &&
-					(_GP(guis)[gg].PopupStyle != kGUIPopupNoAutoRemove))
+					(gui.PopupStyle != kGUIPopupNoAutoRemove))
 					continue;
-				_GP(guis)[gg].Poll(_G(mousex), _G(mousey));
+				gui.Poll(_G(mousex), _G(mousey));
 			}
 		}
 	}
@@ -2154,13 +2136,13 @@ void draw_gui_and_overlays() {
 		// Create a sub-batch
 		_G(gfxDriver)->BeginSpriteBatch(RectWH(s.x, s.y, s.ddb->GetWidth(), s.ddb->GetHeight()),
 			SpriteTransform(0, 0, 1.f, 1.f, 0.f, s.ddb->GetAlpha()));
-		const int draw_index = _GP(guiobjbmpref)[s.id];
+		const int draw_index = _GP(guiobjddbref)[s.id];
 		for (const auto &obj_id : _GP(guis)[s.id].GetControlsDrawOrder()) {
 			GUIObject *obj = _GP(guis)[s.id].GetControl(obj_id);
 			if (!obj->IsVisible() ||
 				(!obj->IsEnabled() && (GUI::Options.DisabledStyle == kGuiDis_Blackout)))
 				continue;
-			auto *obj_ddb = _GP(guiobjbmp)[draw_index + obj_id];
+			auto *obj_ddb = _GP(guiobjddb)[draw_index + obj_id];
 			assert(obj_ddb); // Test for missing texture, might happen if not marked for update
 			if (!obj_ddb) continue;
 			obj_ddb->SetAlpha(GfxDef::LegacyTrans255ToAlpha255(obj->GetTransparency()));
diff --git a/engines/ags/globals.cpp b/engines/ags/globals.cpp
index 3b453d90062..d62a174ab24 100644
--- a/engines/ags/globals.cpp
+++ b/engines/ags/globals.cpp
@@ -179,7 +179,7 @@ Globals::Globals() {
 	_actspswbbmp = new std::vector<Engine::IDriverDependantBitmap *>();
 	_actspswbcache = new std::vector<CachedActSpsData>();
 	_guibg = new std::vector<Shared::Bitmap *>();
-	_guibgbmp = new std::vector<Engine::IDriverDependantBitmap *>();
+	_guibgddb = new std::vector<Engine::IDriverDependantBitmap *>();
 	_debugRoomMaskBmp = new std::unique_ptr<Shared::Bitmap>();
 	_debugMoveListBmp = new std::unique_ptr<Shared::Bitmap>();
 
@@ -189,9 +189,9 @@ Globals::Globals() {
 		_palette[i].clear();
 
 	_guiobjbg = new std::vector<Shared::Bitmap *>();
-	_guiobjbmp = new std::vector<Engine::IDriverDependantBitmap *>();
+	_guiobjddb = new std::vector<Engine::IDriverDependantBitmap *>();
 	_guiobjoff = new std::vector<Point>();
-	_guiobjbmpref = new std::vector<int>();
+	_guiobjddbref = new std::vector<int>();
 	_overlaybmp = new std::vector<Shared::Bitmap *>();
 
 	// draw_software.cpp globals
@@ -437,16 +437,16 @@ Globals::~Globals() {
 	delete _actspswbbmp;
 	delete _actspswbcache;
 	delete _guibg;
-	delete _guibgbmp;
+	delete _guibgddb;
 	delete _debugRoomMaskBmp;
 	delete _debugMoveListBmp;
 	delete[] _dynamicallyCreatedSurfaces;
 	delete[] _palette;
 	delete _maincoltable;
 	delete _guiobjbg;
-	delete _guiobjbmp;
+	delete _guiobjddb;
 	delete _guiobjoff;
-	delete _guiobjbmpref;
+	delete _guiobjddbref;
 	delete _overlaybmp;
 
 	// draw_software.cpp globals
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index ab527217f13..609a5542226 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -586,7 +586,7 @@ public:
 	std::vector<CachedActSpsData> *_actspswbcache;
 	// GUI surfaces
 	std::vector<Shared::Bitmap *> *_guibg;
-	std::vector<Engine::IDriverDependantBitmap *> *_guibgbmp;
+	std::vector<Engine::IDriverDependantBitmap *> *_guibgddb;
 	// For debugging room masks
 	RoomAreaMask _debugRoomMask = kRoomAreaNone;
 	std::unique_ptr<Shared::Bitmap> *_debugRoomMaskBmp;
@@ -609,9 +609,9 @@ public:
 
 	// GUI control surfaces
 	std::vector<Shared::Bitmap *> *_guiobjbg;
-	std::vector<Engine::IDriverDependantBitmap *> *_guiobjbmp;
+	std::vector<Engine::IDriverDependantBitmap *> *_guiobjddb;
 	std::vector<Point> *_guiobjoff; // because surface may be larger than logical position
-	std::vector<int> *_guiobjbmpref; // first control texture index of each GUI
+	std::vector<int> *_guiobjddbref; // first control texture index of each GUI
 	// Overlay's cached transformed bitmap, for software mode
 	std::vector<Shared::Bitmap *> *_overlaybmp;
 


Commit: 3b91a1124f0467bbab39d9b163049d87540ae18d
    https://github.com/scummvm/scummvm/commit/3b91a1124f0467bbab39d9b163049d87540ae18d
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:25-07:00

Commit Message:
AGS: Made internal BitmapFlip enum match the script, for convenience

>From upstream 8be85c08ed87a56265d2f18fb798ec463edb3ef7

Changed paths:
    engines/ags/engine/ac/dynamic_sprite.cpp
    engines/ags/shared/gfx/allegro_bitmap.cpp
    engines/ags/shared/gfx/bitmap.h


diff --git a/engines/ags/engine/ac/dynamic_sprite.cpp b/engines/ags/engine/ac/dynamic_sprite.cpp
index 208c11ad1c8..de63ee39363 100644
--- a/engines/ags/engine/ac/dynamic_sprite.cpp
+++ b/engines/ags/engine/ac/dynamic_sprite.cpp
@@ -127,13 +127,8 @@ void DynamicSprite_Flip(ScriptDynamicSprite *sds, int direction) {
 	// resize the sprite to the requested size
 	Bitmap *newPic = BitmapHelper::CreateTransparentBitmap(_GP(game).SpriteInfos[sds->slot].Width, _GP(game).SpriteInfos[sds->slot].Height, _GP(spriteset)[sds->slot]->GetColorDepth());
 
-	if (direction == 1)
-		newPic->FlipBlt(_GP(spriteset)[sds->slot], 0, 0, Shared::kBitmap_HFlip);
-	else if (direction == 2)
-		newPic->FlipBlt(_GP(spriteset)[sds->slot], 0, 0, Shared::kBitmap_VFlip);
-	else if (direction == 3)
-		newPic->FlipBlt(_GP(spriteset)[sds->slot], 0, 0, Shared::kBitmap_HVFlip);
-
+	// AGS script FlipDirection corresponds to internal BitmapFlip
+	newPic->FlipBlt(_GP(spriteset)[sds->slot], 0, 0, static_cast<BitmapFlip>(direction));
 	delete _GP(spriteset)[sds->slot];
 
 	// replace the bitmap in the sprite set
diff --git a/engines/ags/shared/gfx/allegro_bitmap.cpp b/engines/ags/shared/gfx/allegro_bitmap.cpp
index f8119bd7665..ee43959f64b 100644
--- a/engines/ags/shared/gfx/allegro_bitmap.cpp
+++ b/engines/ags/shared/gfx/allegro_bitmap.cpp
@@ -284,12 +284,19 @@ void Bitmap::LitBlendBlt(Bitmap *src, int dst_x, int dst_y, int light_amount) {
 
 void Bitmap::FlipBlt(Bitmap *src, int dst_x, int dst_y, BitmapFlip flip) {
 	BITMAP *al_src_bmp = src->_alBitmap;
-	if (flip == kBitmap_HFlip) {
+	switch (flip) {
+	case kBitmap_HFlip:
 		draw_sprite_h_flip(_alBitmap, al_src_bmp, dst_x, dst_y);
-	} else if (flip == kBitmap_VFlip) {
+		break;
+	case kBitmap_VFlip:
 		draw_sprite_v_flip(_alBitmap, al_src_bmp, dst_x, dst_y);
-	} else if (flip == kBitmap_HVFlip) {
+		break;
+	case kBitmap_HVFlip:
 		draw_sprite_vh_flip(_alBitmap, al_src_bmp, dst_x, dst_y);
+		break;
+	default: // blit with no transform
+		Blit(src, dst_x, dst_y);
+		break;
 	}
 }
 
diff --git a/engines/ags/shared/gfx/bitmap.h b/engines/ags/shared/gfx/bitmap.h
index 7eb732ef85b..4f1147ca6b2 100644
--- a/engines/ags/shared/gfx/bitmap.h
+++ b/engines/ags/shared/gfx/bitmap.h
@@ -44,6 +44,7 @@ enum BitmapMaskOption {
 };
 
 enum BitmapFlip {
+	kBitmap_NoFlip,
 	kBitmap_HFlip,
 	kBitmap_VFlip,
 	kBitmap_HVFlip


Commit: d3f862767830bc56465de599a2a6f2b1c56fea34
    https://github.com/scummvm/scummvm/commit/d3f862767830bc56465de599a2a6f2b1c56fea34
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:25-07:00

Commit Message:
AGS: Refactored scale_and_flip_sprite() into transform_sprite()

>From upstream 3a3f301709b65e6293028541df8fea084f9da7f9

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/draw.h
    engines/ags/engine/ac/object_cache.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index 95c7c5b52e5..be982afc28a 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -1201,87 +1201,58 @@ void apply_tint_or_light(int actspsindex, int light_level,
 
 }
 
-// Draws the specified 'sppic' sprite onto _GP(actsps)[useindx] at the
-// specified width and height, and flips the sprite if necessary.
-// Returns 1 if something was drawn to actsps; returns 0 if no
-// scaling or stretching was required, in which case nothing was done
-int scale_and_flip_sprite(int useindx, int coldept, int zoom_level,
-                          int sppic, int newwidth, int newheight,
-                          int isMirrored) {
-
-	int actsps_used = 1;
-
-	// create and blank out the new sprite
-	_GP(actsps)[useindx] = recycle_bitmap(_GP(actsps)[useindx], coldept, newwidth, newheight, true);
-	Bitmap *active_spr = _GP(actsps)[useindx];
-
-	if (zoom_level != 100) {
-		// Scaled character
-
-		_G(our_eip) = 334;
-
-		// Ensure that anti-aliasing routines have a palette to
-		// use for mapping while faded out
-		if (_G(in_new_room))
+Bitmap *transform_sprite(Bitmap *src, bool src_has_alpha, Bitmap *&dst, const Size dst_sz, BitmapFlip flip) {
+	if ((src->GetSize() == dst_sz) && (flip == kFlip_None))
+		return src; // No transform: return source image
+
+	dst = recycle_bitmap(dst, src->GetColorDepth(), dst_sz.Width, dst_sz.Height, true);
+	_G(our_eip) = 339;
+
+	// If scaled: first scale then optionally mirror
+	if (src->GetSize() != dst_sz) {
+		// 8-bit support: ensure that anti-aliasing routines have a palette
+		// to use for mapping while faded out.
+		// TODO: find out if this may be moved out and not repeated?
+		if (_G(in_new_room) > 0)
 			select_palette(_G(palette));
 
-
-		if (isMirrored) {
-			Bitmap *tempspr = BitmapHelper::CreateBitmap(newwidth, newheight, coldept);
-			tempspr->Fill(_GP(actsps)[useindx]->GetMaskColor());
-			if ((IS_ANTIALIAS_SPRITES) && ((_GP(game).SpriteInfos[sppic].Flags & SPF_ALPHACHANNEL) == 0))
-				tempspr->AAStretchBlt(_GP(spriteset)[sppic], RectWH(0, 0, newwidth, newheight), Shared::kBitmap_Transparency);
+		if (flip != kFlip_None) {
+			Bitmap tempbmp;
+			tempbmp.CreateTransparent(dst_sz.Width, dst_sz.Height, src->GetColorDepth());
+			if ((IS_ANTIALIAS_SPRITES) && !src_has_alpha)
+				tempbmp.AAStretchBlt(src, RectWH(dst_sz), kBitmap_Transparency);
 			else
-				tempspr->StretchBlt(_GP(spriteset)[sppic], RectWH(0, 0, newwidth, newheight), Shared::kBitmap_Transparency);
-			active_spr->FlipBlt(tempspr, 0, 0, Shared::kBitmap_HFlip);
-			delete tempspr;
-		} else if ((IS_ANTIALIAS_SPRITES) && ((_GP(game).SpriteInfos[sppic].Flags & SPF_ALPHACHANNEL) == 0))
-			active_spr->AAStretchBlt(_GP(spriteset)[sppic], RectWH(0, 0, newwidth, newheight), Shared::kBitmap_Transparency);
-		else
-			active_spr->StretchBlt(_GP(spriteset)[sppic], RectWH(0, 0, newwidth, newheight), Shared::kBitmap_Transparency);
-
-		/*  AASTR2 version of code (doesn't work properly, gives black borders)
-		if (IS_ANTIALIAS_SPRITES) {
-		int aa_mode = AA_MASKED;
-		if (_GP(game).spriteflags[sppic] & SPF_ALPHACHANNEL)
-		aa_mode |= AA_ALPHA | AA_RAW_ALPHA;
-		if (isMirrored)
-		aa_mode |= AA_HFLIP;
-
-		aa_set_mode(aa_mode);
-		->AAStretchBlt(_GP(actsps)[useindx],_GP(spriteset)[sppic],0,0,newwidth,newheight);
-		}
-		else if (isMirrored) {
-		Bitmap *tempspr = BitmapHelper::CreateBitmap_ (coldept, newwidth, newheight);
-		->Clear (tempspr, ->GetMaskColor(_GP(actsps)[useindx]));
-		->StretchBlt (tempspr, _GP(spriteset)[sppic], 0, 0, newwidth, newheight);
-		->FlipBlt(Shared::kBitmap_HFlip, (_GP(actsps)[useindx], tempspr, 0, 0);
-		wfreeblock (tempspr);
+				tempbmp.StretchBlt(src, RectWH(dst_sz), kBitmap_Transparency);
+			dst->FlipBlt(&tempbmp, 0, 0, kBitmap_HFlip);
+		} else {
+			if ((IS_ANTIALIAS_SPRITES) && !src_has_alpha)
+				dst->AAStretchBlt(src, RectWH(dst_sz), kBitmap_Transparency);
+			else
+				dst->StretchBlt(src, RectWH(dst_sz), kBitmap_Transparency);
 		}
-		else
-		->StretchBlt(_GP(actsps)[useindx],_GP(spriteset)[sppic],0,0,newwidth,newheight);
-		*/
-		if (_G(in_new_room))
-			unselect_palette();
 
+		if (_G(in_new_room) > 0)
+			unselect_palette();
 	} else {
-		// Not a scaled character, draw at normal size
-
-		_G(our_eip) = 339;
-
-		if (isMirrored)
-			active_spr->FlipBlt(_GP(spriteset)[sppic], 0, 0, Shared::kBitmap_HFlip);
-		else
-			actsps_used = 0;
-		//->Blit (_GP(spriteset)[sppic], _GP(actsps)[useindx], 0, 0, 0, 0, _GP(actsps)[useindx]->GetWidth(), _GP(actsps)[useindx]->GetHeight());
+		// If not scaled, then simply blit mirrored
+		dst->FlipBlt(src, 0, 0, kBitmap_HFlip);
 	}
-
-	return actsps_used;
+	return dst; // return transformed result
 }
 
+// Draws the specified 'sppic' sprite onto _GP(actsps)[useindx] at the
+// specified width and height, and flips the sprite if necessary.
+// Returns 1 if something was drawn to actsps; returns 0 if no
+// scaling or stretching was required, in which case nothing was done
+static bool scale_and_flip_sprite(int useindx, int sppic, int newwidth, int newheight, bool hmirror) {
+	Bitmap *src = _GP(spriteset)[sppic];
+	Bitmap *&dst = _GP(actsps)[useindx];
+	Bitmap *result = transform_sprite(src, (_GP(game).SpriteInfos[sppic].Flags & SPF_ALPHACHANNEL) != 0,
+		dst, Size(newwidth, newheight), hmirror ? kBitmap_HFlip : kBitmap_NoFlip);
+	return result != src;
+}
 
-
-// create the _GP(actsps)[aa] image with the object drawn correctly
+// create the actsps[aa] image with the object drawn correctly
 // returns 1 if nothing at all has changed and actsps is still
 // intact from last time; 0 otherwise
 int construct_object_gfx(int aa, int *drawnWidth, int *drawnHeight, bool alwaysUseSoftware) {
@@ -1292,8 +1263,10 @@ int construct_object_gfx(int aa, int *drawnWidth, int *drawnHeight, bool alwaysU
 		quitprintf("There was an error drawing object %d. Its current sprite, %d, is invalid.", aa, _G(objs)[aa].num);
 
 	int coldept = _GP(spriteset)[_G(objs)[aa].num]->GetColorDepth();
-	int sprwidth = _GP(game).SpriteInfos[_G(objs)[aa].num].Width;
-	int sprheight = _GP(game).SpriteInfos[_G(objs)[aa].num].Height;
+	const int src_sprwidth = _GP(game).SpriteInfos[_G(objs)[aa].num].Width;
+	const int src_sprheight = _GP(game).SpriteInfos[_G(objs)[aa].num].Height;
+	int sprwidth = src_sprwidth;
+	int sprheight = src_sprheight;
 
 	int tint_red, tint_green, tint_blue;
 	int tint_level, tint_light, light_level;
@@ -1350,11 +1323,11 @@ int construct_object_gfx(int aa, int *drawnWidth, int *drawnHeight, bool alwaysU
 	}
 
 	// check whether the image should be flipped
-	int isMirrored = 0;
+	bool isMirrored = false;
 	if ((_G(objs)[aa].view != (uint16_t)-1) &&
-	        (_GP(views)[_G(objs)[aa].view].loops[_G(objs)[aa].loop].frames[_G(objs)[aa].frame].pic == _G(objs)[aa].num) &&
-	        ((_GP(views)[_G(objs)[aa].view].loops[_G(objs)[aa].loop].frames[_G(objs)[aa].frame].flags & VFLG_FLIPSPRITE) != 0)) {
-		isMirrored = 1;
+		(_GP(views)[_G(objs)[aa].view].loops[_G(objs)[aa].loop].frames[_G(objs)[aa].frame].pic == _G(objs)[aa].num) &&
+		((_GP(views)[_G(objs)[aa].view].loops[_G(objs)[aa].loop].frames[_G(objs)[aa].frame].flags & VFLG_FLIPSPRITE) != 0)) {
+		isMirrored = true;
 	}
 
 	if ((hardwareAccelerated) &&
@@ -1410,14 +1383,14 @@ int construct_object_gfx(int aa, int *drawnWidth, int *drawnHeight, bool alwaysU
 
 	// Not cached, so draw the image
 
-	int actspsUsed = 0;
+	bool actspsUsed = false;
 	if (!hardwareAccelerated) {
 		// draw the base sprite, scaled and flipped as appropriate
-		actspsUsed = scale_and_flip_sprite(useindx, coldept, zoom_level,
-		                                   _G(objs)[aa].num, sprwidth, sprheight, isMirrored);
-	} else {
-		// ensure actsps exists
-		_GP(actsps)[useindx] = recycle_bitmap(_GP(actsps)[useindx], coldept, _GP(game).SpriteInfos[_G(objs)[aa].num].Width, _GP(game).SpriteInfos[_G(objs)[aa].num].Height);
+		actspsUsed = scale_and_flip_sprite(useindx, _G(objs)[aa].num, sprwidth, sprheight, isMirrored);
+	}
+	if (!actspsUsed) {
+		// ensure actsps exists // CHECKME: why do we need this in hardware accel mode too?
+		_GP(actsps)[useindx] = recycle_bitmap(_GP(actsps)[useindx], coldept, src_sprwidth, src_sprheight);
 	}
 
 	// direct read from source bitmap, where possible
@@ -1429,10 +1402,10 @@ int construct_object_gfx(int aa, int *drawnWidth, int *drawnHeight, bool alwaysU
 	// the source bitmap
 	if (!hardwareAccelerated && ((tint_level > 0) || (light_level != 0))) {
 		apply_tint_or_light(useindx, light_level, tint_level, tint_red,
-		                    tint_green, tint_blue, tint_light, coldept,
-		                    comeFrom);
+			tint_green, tint_blue, tint_light, coldept,
+			comeFrom);
 	} else if (!actspsUsed) {
-		_GP(actsps)[useindx]->Blit(_GP(spriteset)[_G(objs)[aa].num], 0, 0, 0, 0, _GP(game).SpriteInfos[_G(objs)[aa].num].Width, _GP(game).SpriteInfos[_G(objs)[aa].num].Height);
+		_GP(actsps)[useindx]->Blit(_GP(spriteset)[_G(objs)[aa].num], 0, 0);
 	}
 
 	// Re-use the bitmap if it's the same size
@@ -1451,9 +1424,6 @@ int construct_object_gfx(int aa, int *drawnWidth, int *drawnHeight, bool alwaysU
 	return 0;
 }
 
-
-
-
 // This is only called from draw_screen_background, but it's separated
 // to help with profiling the program
 void prepare_objects_for_drawing() {
@@ -1644,7 +1614,8 @@ void prepare_characters_for_drawing() {
 		}
 
 		_G(our_eip) = 3330;
-		int isMirrored = 0, specialpic = sppic;
+		bool isMirrored = false;
+		int specialpic = sppic;
 		bool usingCachedImage = false;
 
 		coldept = _GP(spriteset)[sppic]->GetColorDepth();
@@ -1652,7 +1623,7 @@ void prepare_characters_for_drawing() {
 		// adjust the sppic if mirrored, so it doesn't accidentally
 		// cache the mirrored frame as the real one
 		if (_GP(views)[chin->view].loops[chin->loop].frames[chin->frame].flags & VFLG_FLIPSPRITE) {
-			isMirrored = 1;
+			isMirrored = true;
 			specialpic = -sppic;
 		}
 
@@ -1686,6 +1657,9 @@ void prepare_characters_for_drawing() {
 
 		_G(our_eip) = 3332;
 
+		const int src_sprwidth = _GP(game).SpriteInfos[sppic].Width;
+		const int src_sprheight = _GP(game).SpriteInfos[sppic].Height;
+
 		if (zoom_level != 100) {
 			// it needs to be stretched, so calculate the new dimensions
 
@@ -1697,8 +1671,8 @@ void prepare_characters_for_drawing() {
 			// TODO: store width and height always, that's much simplier to use for reference!
 			_G(charextra)[aa].width = 0;
 			_G(charextra)[aa].height = 0;
-			newwidth = _GP(game).SpriteInfos[sppic].Width;
-			newheight = _GP(game).SpriteInfos[sppic].Height;
+			newwidth = src_sprwidth;
+			newheight = src_sprheight;
 		}
 
 		_G(our_eip) = 3336;
@@ -1723,14 +1697,13 @@ void prepare_characters_for_drawing() {
 
 			// create the base sprite in _GP(actsps)[useindx], which will
 			// be scaled and/or flipped, as appropriate
-			int actspsUsed = 0;
+			bool actspsUsed = false;
 			if (!_G(gfxDriver)->HasAcceleratedTransform()) {
-				actspsUsed = scale_and_flip_sprite(
-				                 useindx, coldept, zoom_level, sppic,
-				                 newwidth, newheight, isMirrored);
-			} else {
-				// ensure actsps exists
-				_GP(actsps)[useindx] = recycle_bitmap(_GP(actsps)[useindx], coldept, _GP(game).SpriteInfos[sppic].Width, _GP(game).SpriteInfos[sppic].Height);
+				actspsUsed = scale_and_flip_sprite(useindx, sppic, newwidth, newheight, isMirrored);
+			}
+			if (!actspsUsed) {
+				// ensure actsps exists // CHECKME: why do we need this in hardware accel mode too?
+				_GP(actsps)[useindx] = recycle_bitmap(_GP(actsps)[useindx], coldept, src_sprwidth, src_sprheight);
 			}
 
 			_G(our_eip) = 335;
@@ -1748,7 +1721,7 @@ void prepare_characters_for_drawing() {
 				                    comeFrom);
 			} else if (!actspsUsed) {
 				// no scaling, flipping or tinting was done, so just blit it normally
-				_GP(actsps)[useindx]->Blit(_GP(spriteset)[sppic], 0, 0, 0, 0, _GP(actsps)[useindx]->GetWidth(), _GP(actsps)[useindx]->GetHeight());
+				_GP(actsps)[useindx]->Blit(_GP(spriteset)[sppic], 0, 0);
 			}
 
 			// update the character cache with the new image
@@ -1785,7 +1758,7 @@ void prepare_characters_for_drawing() {
 
 		if (_G(gfxDriver)->HasAcceleratedTransform()) {
 			_GP(actspsbmp)[useindx]->SetStretch(newwidth, newheight);
-			_GP(actspsbmp)[useindx]->SetFlippedLeftRight(isMirrored != 0);
+			_GP(actspsbmp)[useindx]->SetFlippedLeftRight(isMirrored);
 			_GP(actspsbmp)[useindx]->SetTint(tint_red, tint_green, tint_blue, (tint_amount * 256) / 100);
 
 			if (tint_amount != 0) {
@@ -1799,7 +1772,6 @@ void prepare_characters_for_drawing() {
 				_GP(actspsbmp)[useindx]->SetLightLevel((light_level * 25) / 10 + 256);
 			else
 				_GP(actspsbmp)[useindx]->SetLightLevel(0);
-
 		}
 
 		_G(our_eip) = 337;
diff --git a/engines/ags/engine/ac/draw.h b/engines/ags/engine/ac/draw.h
index 5008276d141..9250f0079a2 100644
--- a/engines/ags/engine/ac/draw.h
+++ b/engines/ags/engine/ac/draw.h
@@ -26,12 +26,12 @@
 #include "ags/shared/core/types.h"
 #include "ags/shared/ac/common_defines.h"
 #include "ags/shared/gfx/gfx_def.h"
+#include "ags/shared/gfx/bitmap.h"
 #include "ags/shared/game/room_struct.h"
 
 namespace AGS3 {
 namespace AGS {
 namespace Shared {
-class Bitmap;
 typedef std::shared_ptr<Shared::Bitmap> PBitmap;
 } // namespace Shared
 
@@ -142,6 +142,11 @@ void draw_gui_sprite_v330(Shared::Bitmap *ds, int pic, int x, int y, bool use_al
 void draw_gui_sprite(Shared::Bitmap *ds, bool use_alpha, int xpos, int ypos,
 	Shared::Bitmap *image, bool src_has_alpha, Shared::BlendMode blend_mode = Shared::kBlendMode_Alpha, int alpha = 0xFF);
 
+// Generates a transformed sprite, using src image and parameters;
+// * if transformation is necessary - writes into dst and returns dst;
+// * if no transformation is necessary - simply returns src;
+Shared::Bitmap *transform_sprite(Shared::Bitmap *src, bool src_has_alpha, Shared::Bitmap *&dst,
+	const Size dst_sz, Shared::BitmapFlip flip = Shared::kBitmap_NoFlip);
 // Render game on screen
 void render_to_screen();
 // Callbacks for the graphics driver
diff --git a/engines/ags/engine/ac/object_cache.h b/engines/ags/engine/ac/object_cache.h
index 34a480be200..c0c09cc845d 100644
--- a/engines/ags/engine/ac/object_cache.h
+++ b/engines/ags/engine/ac/object_cache.h
@@ -19,8 +19,8 @@
  *
  */
 
-#ifndef AGS_ENGINE_AC_OBJECTCACHE_H
-#define AGS_ENGINE_AC_OBJECTCACHE_H
+#ifndef AGS_ENGINE_AC_OBJECT_CACHE_H
+#define AGS_ENGINE_AC_OBJECT_CACHE_H
 
 namespace AGS3 {
 
@@ -30,9 +30,11 @@ struct ObjectCache {
 	int   sppic = 0;
 	short tintredwas = 0, tintgrnwas = 0, tintbluwas = 0;
 	short tintamntwas = 0, tintlightwas = 0;
-	short lightlevwas = 0, mirroredWas = 0, zoomWas = 0;
+	short lightlevwas = 0, zoomWas = 0;
+	bool  mirroredWas = false;
+
 	// The following are used to determine if the character has moved
-	int   xwas, ywas;
+	int   xwas = 0, ywas = 0;
 };
 
 } // namespace AGS3


Commit: 8ac8c037c9c564548a70e3d16743792e1cb6e03b
    https://github.com/scummvm/scummvm/commit/8ac8c037c9c564548a70e3d16743792e1cb6e03b
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:25-07:00

Commit Message:
AGS: Use transform_sprite() with overlays for consistency

>From upstream f0a1c70836d10330f16723eb304821211b1f1bdf

Changed paths:
    engines/ags/engine/ac/draw.cpp


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index be982afc28a..fb328370fd3 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -1986,12 +1986,9 @@ void draw_gui_and_overlays() {
 		if (over.transparency == 255) continue; // skip fully transparent
 		if (_GP(screenover)[i].HasChanged()) {
 			// For software mode - prepare transformed bitmap if necessary
-			Bitmap *use_bmp = over.pic;
-			if (is_software_mode && (over.pic->GetSize() != Size(over.scaleWidth, over.scaleHeight))) {
-				_GP(overlaybmp)[i] = recycle_bitmap(_GP(overlaybmp)[i], over.pic->GetColorDepth(), over.scaleWidth, over.scaleHeight);
-				_GP(overlaybmp)[i]->StretchBlt(over.pic, RectWH(_GP(overlaybmp)[i]->GetSize()));
-				use_bmp = _GP(overlaybmp)[i];
-			}
+			Bitmap *use_bmp = is_software_mode ?
+				transform_sprite(over.pic, over.hasAlphaChannel, overlaybmp[i], Size(over.scaleWidth, over.scaleHeight)) :
+				over.pic;
 			over.ddb = recycle_ddb_bitmap(over.ddb, use_bmp, over.hasAlphaChannel);
 			over.ClearChanged();
 		}


Commit: d9da1e3759286fd8eaf919bc9d7880bfeda52ae0
    https://github.com/scummvm/scummvm/commit/d9da1e3759286fd8eaf919bc9d7880bfeda52ae0
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:25-07:00

Commit Message:
AGS: Don't try rendering gui controls with zero size

>From upstream 84179aecc419cc8feb56b947bb7519e69062fe70

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/shared/gui/gui_main.cpp


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index fb328370fd3..f93539ef0d9 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -1945,6 +1945,7 @@ void draw_gui_controls(GUIMain &gui) {
 	for (int i = 0; i < gui.GetControlCount(); ++i, ++draw_index) {
 		GUIObject *obj = gui.GetControl(i);
 		if (!obj->IsVisible() ||
+			(obj->Width <= 0 || obj->Height <= 0) ||
 			(!obj->IsEnabled() && (GUI::Options.DisabledStyle == kGuiDis_Blackout)))
 			continue;
 		if (!obj->HasChanged())
@@ -1987,7 +1988,7 @@ void draw_gui_and_overlays() {
 		if (_GP(screenover)[i].HasChanged()) {
 			// For software mode - prepare transformed bitmap if necessary
 			Bitmap *use_bmp = is_software_mode ?
-				transform_sprite(over.pic, over.hasAlphaChannel, overlaybmp[i], Size(over.scaleWidth, over.scaleHeight)) :
+				transform_sprite(over.pic, over.hasAlphaChannel, _GP(overlaybmp)[i], Size(over.scaleWidth, over.scaleHeight)) :
 				over.pic;
 			over.ddb = recycle_ddb_bitmap(over.ddb, use_bmp, over.hasAlphaChannel);
 			over.ClearChanged();
@@ -2109,6 +2110,7 @@ void draw_gui_and_overlays() {
 		for (const auto &obj_id : _GP(guis)[s.id].GetControlsDrawOrder()) {
 			GUIObject *obj = _GP(guis)[s.id].GetControl(obj_id);
 			if (!obj->IsVisible() ||
+				(obj->Width <= 0 || obj->Height <= 0) ||
 				(!obj->IsEnabled() && (GUI::Options.DisabledStyle == kGuiDis_Blackout)))
 				continue;
 			auto *obj_ddb = _GP(guiobjddb)[draw_index + obj_id];
diff --git a/engines/ags/shared/gui/gui_main.cpp b/engines/ags/shared/gui/gui_main.cpp
index 73d401f497f..f1507ac601a 100644
--- a/engines/ags/shared/gui/gui_main.cpp
+++ b/engines/ags/shared/gui/gui_main.cpp
@@ -267,9 +267,9 @@ void GUIMain::DrawWithControls(Bitmap *ds) {
 
 		GUIObject *objToDraw = _controls[_ctrlDrawOrder[ctrl_index]];
 
-		if (!objToDraw->IsEnabled() && (GUI::Options.DisabledStyle == kGuiDis_Blackout))
+		if (!objToDraw->IsVisible() || (objToDraw->Width <= 0 || objToDraw->Height <= 0))
 			continue;
-		if (!objToDraw->IsVisible())
+		if (!objToDraw->IsEnabled() && (GUI::Options.DisabledStyle == kGuiDis_Blackout))
 			continue;
 
 		// Depending on draw properties - draw directly on the gui surface, or use a buffer


Commit: 3be37608f04e20d67b704751b6b455f0d6815ff2
    https://github.com/scummvm/scummvm/commit/3be37608f04e20d67b704751b6b455f0d6815ff2
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:25-07:00

Commit Message:
AGS: Fixed potential division by zero in GUISlider

>From upstream 36d21686e4a815a22b99e4fbceff11852241a25b

Changed paths:
    engines/ags/shared/gui/gui_slider.cpp


diff --git a/engines/ags/shared/gui/gui_slider.cpp b/engines/ags/shared/gui/gui_slider.cpp
index 3daac0cc33d..3456fc8629a 100644
--- a/engines/ags/shared/gui/gui_slider.cpp
+++ b/engines/ags/shared/gui/gui_slider.cpp
@@ -136,7 +136,7 @@ void GUISlider::UpdateMetrics() {
 
 	_cachedBar = bar;
 	_cachedHandle = handle;
-	_handleRange = handle_range;
+	_handleRange = std::max(1, handle_range);
 }
 
 void GUISlider::Draw(Bitmap *ds, int x, int y) {
@@ -208,6 +208,7 @@ void GUISlider::OnMouseMove(int x, int y) {
 		return;
 
 	int value;
+	assert(_handleRange > 0);
 	if (IsHorizontal())
 		value = (int)(((float)((x - X) - 2) * (float)(MaxValue - MinValue)) / (float)_handleRange) + MinValue;
 	else


Commit: 4cd0ca9a274e03d489126b0341a094755602f688
    https://github.com/scummvm/scummvm/commit/4cd0ca9a274e03d489126b0341a094755602f688
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:25-07:00

Commit Message:
AGS: Removed DrawAsSeparateCharSprite walk-behind method

>From upstream e075376fd42c95ea2399809b7cc492cbc116b0db

Changed paths:
    engines/ags/engine/ac/draw.cpp
    engines/ags/engine/ac/draw.h
    engines/ags/engine/ac/global_walk_behind.cpp
    engines/ags/engine/ac/room.cpp
    engines/ags/engine/ac/walk_behind.h
    engines/ags/globals.cpp
    engines/ags/globals.h


diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp
index f93539ef0d9..34e22352cb3 100644
--- a/engines/ags/engine/ac/draw.cpp
+++ b/engines/ags/engine/ac/draw.cpp
@@ -366,9 +366,6 @@ void init_game_drawdata() {
 	size_t actsps_num = _GP(game).numcharacters + MAX_ROOM_OBJECTS;
 	_GP(actsps).resize(actsps_num);
 	_GP(actspsbmp).resize(actsps_num);
-	_GP(actspswb).resize(actsps_num);
-	_GP(actspswbbmp).resize(actsps_num);
-	_GP(actspswbcache).resize(actsps_num);
 	_GP(guibg).resize(_GP(game).numgui);
 	_GP(guibgddb).resize(_GP(game).numgui);
 
@@ -389,9 +386,6 @@ void dispose_game_drawdata() {
 
 	_GP(actsps).clear();
 	_GP(actspsbmp).clear();
-	_GP(actspswb).clear();
-	_GP(actspswbbmp).clear();
-	_GP(actspswbcache).clear();
 	_GP(guibg).clear();
 	_GP(guibgddb).clear();
 
@@ -431,13 +425,6 @@ void clear_drawobj_cache() {
 		if (_GP(actspsbmp)[i] != nullptr)
 			_G(gfxDriver)->DestroyDDB(_GP(actspsbmp)[i]);
 		_GP(actspsbmp)[i] = nullptr;
-
-		delete _GP(actspswb)[i];
-		_GP(actspswb)[i] = nullptr;
-		if (_GP(actspswbbmp)[i] != nullptr)
-			_G(gfxDriver)->DestroyDDB(_GP(actspswbbmp)[i]);
-		_GP(actspswbbmp)[i] = nullptr;
-		_GP(actspswbcache)[i].valid = 0;
 	}
 
 	// cleanup GUI backgrounds
@@ -841,14 +828,10 @@ void put_sprite_list_on_screen(bool in_room);
 //
 //------------------------------------------------------------------------
 
-void invalidate_cached_walkbehinds() {
-	memset(&_GP(actspswbcache)[0], 0, sizeof(CachedActSpsData) * _GP(actspswbcache).size());
-}
-
 // sort_out_walk_behinds: modifies the supplied sprite by overwriting parts
 // of it with transparent pixels where there are walk-behind areas
 // Returns whether any pixels were updated
-int sort_out_walk_behinds(Bitmap *sprit, int xx, int yy, int basel, Bitmap *copyPixelsFrom = nullptr, Bitmap *checkPixelsFrom = nullptr, int zoom = 100) {
+int sort_out_walk_behinds(Bitmap *sprit, int xx, int yy, int basel, int zoom = 100) {
 	if (_G(noWalkBehindsAtAll))
 		return 0;
 
@@ -862,15 +845,12 @@ int sort_out_walk_behinds(Bitmap *sprit, int xx, int yy, int basel, Bitmap *copy
 	int spcoldep = sprit->GetColorDepth();
 	int screenhit = _GP(thisroom).WalkBehindMask->GetHeight();
 	short *shptr, *shptr2;
-	int *loptr, *loptr2;
+	int *loptr;
 	int pixelsChanged = 0;
 	int ee = 0;
 	if (xx < 0)
 		ee = 0 - xx;
 
-	if ((checkPixelsFrom != nullptr) && (checkPixelsFrom->GetColorDepth() != spcoldep))
-		quit("sprite colour depth does not match background colour depth");
-
 	for (; ee < sprit->GetWidth(); ee++) {
 		if (ee + xx >= _GP(thisroom).WalkBehindMask->GetWidth())
 			break;
@@ -898,7 +878,6 @@ int sort_out_walk_behinds(Bitmap *sprit, int xx, int yy, int basel, Bitmap *copy
 			rr = 0;
 
 		for (; rr < toheight; rr++) {
-
 			// we're ok with _getpixel because we've checked the screen edges
 			//tmm = _getpixel(_GP(thisroom).WalkBehindMask,ee+xx,rr+yy);
 			// actually, _getpixel is well inefficient, do it ourselves
@@ -907,82 +886,25 @@ int sort_out_walk_behinds(Bitmap *sprit, int xx, int yy, int basel, Bitmap *copy
 			if (tmm < 1) continue;
 			if (_G(croom)->walkbehind_base[tmm] <= basel) continue;
 
-			if (copyPixelsFrom != nullptr) {
-				if (spcoldep <= 8) {
-					if (checkPixelsFrom->GetScanLine((rr * 100) / zoom)[(ee * 100) / zoom] != maskcol) {
-						sprit->GetScanLineForWriting(rr)[ee] = copyPixelsFrom->GetScanLine(rr + yy)[ee + xx];
-						pixelsChanged = 1;
-					}
-				} else if (spcoldep <= 16) {
-					shptr = (short *)&sprit->GetScanLine(rr)[0];
-					shptr2 = (short *)&checkPixelsFrom->GetScanLine((rr * 100) / zoom)[0];
-					if (shptr2[(ee * 100) / zoom] != maskcol) {
-						shptr[ee] = ((short *)(&copyPixelsFrom->GetScanLine(rr + yy)[0]))[ee + xx];
-						pixelsChanged = 1;
-					}
-				} else if (spcoldep == 24) {
-					char *chptr = (char *)&sprit->GetScanLine(rr)[0];
-					char *chptr2 = (char *)&checkPixelsFrom->GetScanLine((rr * 100) / zoom)[0];
-					if (memcmp(&chptr2[((ee * 100) / zoom) * 3], &maskcol, 3) != 0) {
-						memcpy(&chptr[ee * 3], &copyPixelsFrom->GetScanLine(rr + yy)[(ee + xx) * 3], 3);
-						pixelsChanged = 1;
-					}
-				} else if (spcoldep <= 32) {
-					loptr = (int *)&sprit->GetScanLine(rr)[0];
-					loptr2 = (int *)&checkPixelsFrom->GetScanLine((rr * 100) / zoom)[0];
-					if (loptr2[(ee * 100) / zoom] != maskcol) {
-						loptr[ee] = ((int *)(&copyPixelsFrom->GetScanLine(rr + yy)[0]))[ee + xx];
-						pixelsChanged = 1;
-					}
-				}
-			} else {
-				pixelsChanged = 1;
-				if (spcoldep <= 8)
-					sprit->GetScanLineForWriting(rr)[ee] = maskcol;
-				else if (spcoldep <= 16) {
-					shptr = (short *)&sprit->GetScanLine(rr)[0];
-					shptr[ee] = maskcol;
-				} else if (spcoldep == 24) {
-					char *chptr = (char *)&sprit->GetScanLine(rr)[0];
-					memcpy(&chptr[ee * 3], &maskcol, 3);
-				} else if (spcoldep <= 32) {
-					loptr = (int *)&sprit->GetScanLine(rr)[0];
-					loptr[ee] = maskcol;
-				} else
-					quit("!Sprite colour depth >32 ??");
-			}
+			pixelsChanged = 1;
+			if (spcoldep <= 8)
+				sprit->GetScanLineForWriting(rr)[ee] = maskcol;
+			else if (spcoldep <= 16) {
+				shptr = (short *)&sprit->GetScanLine(rr)[0];
+				shptr[ee] = maskcol;
+			} else if (spcoldep == 24) {
+				char *chptr = (char *)&sprit->GetScanLine(rr)[0];
+				memcpy(&chptr[ee * 3], &maskcol, 3);
+			} else if (spcoldep <= 32) {
+				loptr = (int *)&sprit->GetScanLine(rr)[0];
+				loptr[ee] = maskcol;
+			} else
+				quit("!Sprite colour depth >32 ??");
 		}
 	}
 	return pixelsChanged;
 }
 
-void sort_out_char_sprite_walk_behind(int actspsIndex, int xx, int yy, int basel, int zoom, int width, int height) {
-	if (_G(noWalkBehindsAtAll))
-		return;
-
-	if ((!_GP(actspswbcache)[actspsIndex].valid) ||
-	        (_GP(actspswbcache)[actspsIndex].xWas != xx) ||
-	        (_GP(actspswbcache)[actspsIndex].yWas != yy) ||
-	        (_GP(actspswbcache)[actspsIndex].baselineWas != basel)) {
-		_GP(actspswb)[actspsIndex] = recycle_bitmap(_GP(actspswb)[actspsIndex], _GP(thisroom).BgFrames[_GP(play).bg_frame].Graphic->GetColorDepth(), width, height, true);
-		Bitmap *wbSprite = _GP(actspswb)[actspsIndex];
-
-		_GP(actspswbcache)[actspsIndex].isWalkBehindHere = sort_out_walk_behinds(wbSprite, xx, yy, basel, _GP(thisroom).BgFrames[_GP(play).bg_frame].Graphic.get(), _GP(actsps)[actspsIndex], zoom);
-		_GP(actspswbcache)[actspsIndex].xWas = xx;
-		_GP(actspswbcache)[actspsIndex].yWas = yy;
-		_GP(actspswbcache)[actspsIndex].baselineWas = basel;
-		_GP(actspswbcache)[actspsIndex].valid = 1;
-
-		if (_GP(actspswbcache)[actspsIndex].isWalkBehindHere) {
-			_GP(actspswbbmp)[actspsIndex] = recycle_ddb_bitmap(_GP(actspswbbmp)[actspsIndex], _GP(actspswb)[actspsIndex], false);
-		}
-	}
-
-	if (_GP(actspswbcache)[actspsIndex].isWalkBehindHere) {
-		add_to_sprite_list(_GP(actspswbbmp)[actspsIndex], xx, yy, basel, true);
-	}
-}
-
 void repair_alpha_channel(Bitmap *dest, Bitmap *bgpic) {
 	// Repair the alpha channel, because sprites may have been drawn
 	// over it by the buttons, etc
@@ -1452,8 +1374,6 @@ void prepare_objects_for_drawing() {
 			if (_G(walkBehindMethod) == DrawAsSeparateSprite) {
 				usebasel += _GP(thisroom).Height;
 			}
-		} else if (_G(walkBehindMethod) == DrawAsSeparateCharSprite) {
-			sort_out_char_sprite_walk_behind(useindx, atxp, atyp, usebasel, _G(objs)[aa].zoom, _G(objs)[aa].last_width, _G(objs)[aa].last_height);
 		} else if ((!actspsIntact) && (_G(walkBehindMethod) == DrawOverCharSprite)) {
 			sort_out_walk_behinds(_GP(actsps)[useindx], atxp, atyp, usebasel);
 		}
@@ -1744,8 +1664,6 @@ void prepare_characters_for_drawing() {
 			if (_G(walkBehindMethod) == DrawAsSeparateSprite) {
 				usebasel += _GP(thisroom).Height;
 			}
-		} else if (_G(walkBehindMethod) == DrawAsSeparateCharSprite) {
-			sort_out_char_sprite_walk_behind(useindx, bgX, bgY, usebasel, _G(charextra)[aa].zoom, newwidth, newheight);
 		} else if (_G(walkBehindMethod) == DrawOverCharSprite) {
 			sort_out_walk_behinds(_GP(actsps)[useindx], bgX, bgY, usebasel);
 		}
diff --git a/engines/ags/engine/ac/draw.h b/engines/ags/engine/ac/draw.h
index 9250f0079a2..fb1648a841b 100644
--- a/engines/ags/engine/ac/draw.h
+++ b/engines/ags/engine/ac/draw.h
@@ -44,13 +44,6 @@ using namespace AGS; // FIXME later
 
 #define IS_ANTIALIAS_SPRITES _GP(usetup).enable_antialiasing && (_GP(play).disable_antialiasing == 0)
 
-struct CachedActSpsData {
-	int xWas, yWas;
-	int baselineWas;
-	int isWalkBehindHere;
-	int valid;
-};
-
 /**
  * Buffer and info flags for viewport/camera pairs rendering in software mode
  */
@@ -112,7 +105,6 @@ void invalidate_camera_frame(int index);
 void invalidate_rect(int x1, int y1, int x2, int y2, bool in_room);
 
 void mark_current_background_dirty();
-void invalidate_cached_walkbehinds();
 
 // Avoid freeing and reallocating the memory if possible
 Shared::Bitmap *recycle_bitmap(Shared::Bitmap *bimp, int coldep, int wid, int hit, bool make_transparent = false);
diff --git a/engines/ags/engine/ac/global_walk_behind.cpp b/engines/ags/engine/ac/global_walk_behind.cpp
index b8f4cec71d4..1fe7ce1e307 100644
--- a/engines/ags/engine/ac/global_walk_behind.cpp
+++ b/engines/ags/engine/ac/global_walk_behind.cpp
@@ -36,7 +36,6 @@ void SetWalkBehindBase(int wa, int bl) {
 
 	if (bl != _G(croom)->walkbehind_base[wa]) {
 		_G(walk_behind_baselines_changed) = 1;
-		invalidate_cached_walkbehinds();
 		_G(croom)->walkbehind_base[wa] = bl;
 		debug_script_log("Walk-behind %d baseline changed to %d", wa, bl);
 	}
diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp
index 9854d5f22eb..301764865cd 100644
--- a/engines/ags/engine/ac/room.cpp
+++ b/engines/ags/engine/ac/room.cpp
@@ -1012,10 +1012,8 @@ void compile_room_script() {
 }
 
 void on_background_frame_change() {
-
 	invalidate_screen();
 	mark_current_background_dirty();
-	invalidate_cached_walkbehinds();
 
 	// get the new frame's palette
 	memcpy(_G(palette), _GP(thisroom).BgFrames[_GP(play).bg_frame].Palette, sizeof(RGB) * 256);
diff --git a/engines/ags/engine/ac/walk_behind.h b/engines/ags/engine/ac/walk_behind.h
index acffb76c66e..a259a08534d 100644
--- a/engines/ags/engine/ac/walk_behind.h
+++ b/engines/ags/engine/ac/walk_behind.h
@@ -25,19 +25,15 @@
 namespace AGS3 {
 
 // A method of rendering walkbehinds on screen:
-// DrawAsSeparateSprite - draws whole walkbehind as a sprite; this
-//     method is most simple and is optimal for 3D renderers.
-// DrawOverCharSprite and DrawAsSeparateCharSprite - are alternatives
-//     optimized for software render.
+// DrawAsSeparateSprite - draws whole walkbehind as a sprite;
+//     this method is most simple and is optimal for 3D renderers.
 // DrawOverCharSprite - turns parts of the character and object sprites
 //     transparent when they are covered by walkbehind (walkbehind itself
-//     is not drawn separately in this case).
-// DrawAsSeparateCharSprite - draws smaller *parts* of walkbehind as
-//     separate sprites, only ones that cover characters or objects.
+//     is not drawn separately in this case);
+//     this method is optimized for software render.
 enum WalkBehindMethodEnum {
 	DrawOverCharSprite,
-	DrawAsSeparateSprite,
-	DrawAsSeparateCharSprite
+	DrawAsSeparateSprite
 };
 
 void update_walk_behind_images();
diff --git a/engines/ags/globals.cpp b/engines/ags/globals.cpp
index d62a174ab24..4f15ea49e56 100644
--- a/engines/ags/globals.cpp
+++ b/engines/ags/globals.cpp
@@ -175,9 +175,6 @@ Globals::Globals() {
 
 	_actsps = new std::vector<Shared::Bitmap *>();
 	_actspsbmp = new std::vector<Engine::IDriverDependantBitmap *>();
-	_actspswb = new	std::vector<Shared::Bitmap *>();
-	_actspswbbmp = new std::vector<Engine::IDriverDependantBitmap *>();
-	_actspswbcache = new std::vector<CachedActSpsData>();
 	_guibg = new std::vector<Shared::Bitmap *>();
 	_guibgddb = new std::vector<Engine::IDriverDependantBitmap *>();
 	_debugRoomMaskBmp = new std::unique_ptr<Shared::Bitmap>();
@@ -433,9 +430,6 @@ Globals::~Globals() {
 	delete _thingsToDrawList;
 	delete _actsps;
 	delete _actspsbmp;
-	delete _actspswb;
-	delete _actspswbbmp;
-	delete _actspswbcache;
 	delete _guibg;
 	delete _guibgddb;
 	delete _debugRoomMaskBmp;
diff --git a/engines/ags/globals.h b/engines/ags/globals.h
index 609a5542226..a34069c72dd 100644
--- a/engines/ags/globals.h
+++ b/engines/ags/globals.h
@@ -581,9 +581,6 @@ public:
 	std::vector<Shared::Bitmap *> *_actsps;
 	std::vector<Engine::IDriverDependantBitmap *> *_actspsbmp;
 	// temporary cache of walk-behind for this actsps image
-	std::vector<Shared::Bitmap *> *_actspswb;
-	std::vector<Engine::IDriverDependantBitmap *> *_actspswbbmp;
-	std::vector<CachedActSpsData> *_actspswbcache;
 	// GUI surfaces
 	std::vector<Shared::Bitmap *> *_guibg;
 	std::vector<Engine::IDriverDependantBitmap *> *_guibgddb;


Commit: ebe2096af75e02e50b95ea45a57d864eb13eeef8
    https://github.com/scummvm/scummvm/commit/ebe2096af75e02e50b95ea45a57d864eb13eeef8
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2022-05-01T17:23:26-07:00

Commit Message:
AGS: Call unload_game_file() in quit()

>From upstream 1118be9d3f09037b995554d7aad47a400a76fafc

Changed paths:
    engines/ags/engine/ac/game.cpp
    engines/ags/engine/main/quit.cpp


diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp
index f169adbe5ee..25e1b264d15 100644
--- a/engines/ags/engine/ac/game.cpp
+++ b/engines/ags/engine/ac/game.cpp
@@ -364,18 +364,16 @@ void unload_game_file() {
 
 	_GP(characterScriptObjNames).clear();
 	free(_G(charextra));
+	_G(charextra) = nullptr;
 	free(_G(mls));
+	_G(mls) = nullptr;
 
 	dispose_game_drawdata();
 
-	if ((_G(gameinst) != nullptr) && (_G(gameinst)->pc != 0)) {
-		quit("Error: unload_game called while script still running");
-	} else {
-		delete _G(gameinstFork);
-		delete _G(gameinst);
-		_G(gameinstFork) = nullptr;
-		_G(gameinst) = nullptr;
-	}
+	delete _G(gameinstFork);
+	delete _G(gameinst);
+	_G(gameinstFork) = nullptr;
+	_G(gameinst) = nullptr;
 
 	_GP(gamescript).reset();
 
diff --git a/engines/ags/engine/main/quit.cpp b/engines/ags/engine/main/quit.cpp
index 3d61ba31d62..23de13a9473 100644
--- a/engines/ags/engine/main/quit.cpp
+++ b/engines/ags/engine/main/quit.cpp
@@ -25,6 +25,7 @@
 
 #include "ags/shared/core/platform.h"
 #include "ags/engine/ac/cd_audio.h"
+#include "ags/engine/ac/game.h"
 #include "ags/engine/ac/game_setup.h"
 #include "ags/shared/ac/game_setup_struct.h"
 #include "ags/engine/ac/game_state.h"
@@ -148,7 +149,7 @@ void quit_release_data() {
 	resetRoomStatuses();
 	_GP(thisroom).Free();
 	_GP(play).Free();
-	_GP(AssetMgr).reset();
+	unload_game_file();
 }
 
 void quit_delete_temp_files() {
@@ -224,20 +225,16 @@ void quit_free() {
 
 	_GP(spriteset).Reset();
 
-	_G(our_eip) = 9907;
-
-	close_translation();
-
 	_G(our_eip) = 9908;
 
 	shutdown_pathfinder();
 
+	quit_release_data();
+
 	engine_shutdown_gfxmode();
 
 	quit_message_on_exit(quitmsg, alertis, qreason);
 
-	quit_release_data();
-
 	_G(platform)->PreBackendExit();
 
 	// release backed library




More information about the Scummvm-git-logs mailing list