1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "ags/lib/std/algorithm.h"
24 #include "ags/lib/std/math.h"
25 #include "ags/lib/aastr-0.1.1/aastr.h"
26 #include "ags/shared/core/platform.h"
27 #include "ags/shared/ac/common.h"
28 #include "ags/shared/util/compress.h"
29 #include "ags/shared/util/wgt2_allg.h"
30 #include "ags/shared/ac/view.h"
31 #include "ags/engine/ac/character_cache.h"
32 #include "ags/engine/ac/character_extras.h"
33 #include "ags/shared/ac/character_info.h"
34 #include "ags/engine/ac/display.h"
35 #include "ags/engine/ac/draw.h"
36 #include "ags/engine/ac/draw_software.h"
37 #include "ags/engine/ac/game_setup.h"
38 #include "ags/shared/ac/game_setup_struct.h"
39 #include "ags/engine/ac/game_state.h"
40 #include "ags/engine/ac/global_game.h"
41 #include "ags/engine/ac/global_gui.h"
42 #include "ags/engine/ac/global_region.h"
43 #include "ags/engine/ac/gui.h"
44 #include "ags/engine/ac/mouse.h"
45 #include "ags/engine/ac/object_cache.h"
46 #include "ags/engine/ac/overlay.h"
47 #include "ags/engine/ac/sys_events.h"
48 #include "ags/engine/ac/room_object.h"
49 #include "ags/engine/ac/room_status.h"
50 #include "ags/engine/ac/runtime_defines.h"
51 #include "ags/engine/ac/screen_overlay.h"
52 #include "ags/engine/ac/sprite.h"
53 #include "ags/engine/ac/sprite_list_entry.h"
54 #include "ags/engine/ac/string.h"
55 #include "ags/engine/ac/system.h"
56 #include "ags/engine/ac/view_frame.h"
57 #include "ags/engine/ac/walkable_area.h"
58 #include "ags/engine/ac/walk_behind.h"
59 #include "ags/engine/ac/dynobj/script_system.h"
60 #include "ags/engine/debugging/debugger.h"
61 #include "ags/engine/debugging/debug_log.h"
62 #include "ags/shared/font/fonts.h"
63 #include "ags/shared/gui/gui_main.h"
64 #include "ags/engine/platform/base/ags_platform_driver.h"
65 #include "ags/plugins/ags_plugin.h"
66 #include "ags/plugins/plugin_engine.h"
67 #include "ags/shared/ac/sprite_cache.h"
68 #include "ags/engine/gfx/gfx_util.h"
69 #include "ags/engine/gfx/graphics_driver.h"
70 #include "ags/engine/gfx/blender.h"
71 #include "ags/engine/media/audio/audio_system.h"
72 #include "ags/engine/ac/game.h"
73 #include "ags/ags.h"
74 #include "ags/globals.h"
75 
76 namespace AGS3 {
77 
78 using namespace AGS::Shared;
79 using namespace AGS::Engine;
80 
SpriteListEntry()81 SpriteListEntry::SpriteListEntry()
82 	: bmp(nullptr)
83 	, pic(nullptr)
84 	, baseline(0), x(0), y(0)
85 	, transparent(0)
86 	, takesPriorityIfEqual(false), hasAlphaChannel(false) {
87 }
88 
setpal()89 void setpal() {
90 	set_palette_range(_G(palette), 0, 255, 0);
91 }
92 
93 int _places_r = 3, _places_g = 2, _places_b = 3;
94 
95 // convert RGB to BGR for strange graphics cards
convert_16_to_16bgr(Bitmap * tempbl)96 Bitmap *convert_16_to_16bgr(Bitmap *tempbl) {
97 
98 	int x, y;
99 	unsigned short c, r, ds, b;
100 
101 	for (y = 0; y < tempbl->GetHeight(); y++) {
102 		unsigned short *p16 = (unsigned short *)tempbl->GetScanLine(y);
103 
104 		for (x = 0; x < tempbl->GetWidth(); x++) {
105 			c = p16[x];
106 			if (c != MASK_COLOR_16) {
107 				b = _rgb_scale_5[c & 0x1F];
108 				ds = _rgb_scale_6[(c >> 5) & 0x3F];
109 				r = _rgb_scale_5[(c >> 11) & 0x1F];
110 				// allegro assumes 5-6-5 for 16-bit
111 				p16[x] = (((r >> _places_r) << _G(_rgb_r_shift_16)) |
112 				          ((ds >> _places_g) << _G(_rgb_g_shift_16)) |
113 				          ((b >> _places_b) << _G(_rgb_b_shift_16)));
114 
115 			}
116 		}
117 	}
118 
119 	return tempbl;
120 }
121 
122 // PSP: convert 32 bit RGB to BGR.
convert_32_to_32bgr(Bitmap * tempbl)123 Bitmap *convert_32_to_32bgr(Bitmap *tempbl) {
124 
125 	int i = 0;
126 	int j = 0;
127 	unsigned char *current;
128 	while (i < tempbl->GetHeight()) {
129 		current = tempbl->GetScanLineForWriting(i);
130 		while (j < tempbl->GetWidth()) {
131 			current[0] ^= current[2];
132 			current[2] ^= current[0];
133 			current[0] ^= current[2];
134 			current += 4;
135 			j++;
136 		}
137 		i++;
138 		j = 0;
139 	}
140 
141 	return tempbl;
142 }
143 
144 // NOTE: Some of these conversions are required  even when using
145 // D3D and OpenGL rendering, for two reasons:
146 // 1) certain raw drawing operations are still performed by software
147 // Allegro methods, hence bitmaps should be kept compatible to any native
148 // software operations, such as blitting two bitmaps of different formats.
149 // 2) mobile ports feature an OpenGL renderer built in Allegro library,
150 // that assumes native bitmaps are in OpenGL-compatible format, so that it
151 // could copy them to texture without additional changes.
152 // AGS own OpenGL renderer tries to sync its behavior with the former one.
153 //
154 // TODO: make gfxDriver->GetCompatibleBitmapFormat describe all necessary
155 // conversions, so that we did not have to guess.
156 //
AdjustBitmapForUseWithDisplayMode(Bitmap * bitmap,bool has_alpha)157 Bitmap *AdjustBitmapForUseWithDisplayMode(Bitmap *bitmap, bool has_alpha) {
158 	const int bmp_col_depth = bitmap->GetColorDepth();
159 	// const int sys_col_depth = System_GetColorDepth();
160 	const int game_col_depth = _GP(game).GetColorDepth();
161 	Bitmap *new_bitmap = bitmap;
162 
163 	//
164 	// The only special case when bitmap needs to be prepared for graphics driver
165 	//
166 	// In 32-bit display mode, 32-bit bitmaps may require component conversion
167 	// to match graphics driver expectation about pixel format.
168 	// TODO: make GetCompatibleBitmapFormat tell this somehow
169 #if defined (AGS_INVERTED_COLOR_ORDER)
170 	if (sys_col_depth > 16 && bmp_col_depth == 32) {
171 		// Convert RGB to BGR.
172 		new_bitmap = convert_32_to_32bgr(bitmap);
173 	}
174 #endif
175 
176 	//
177 	// The rest is about bringing bitmaps to the native game's format
178 	// (has no dependency on display mode).
179 	//
180 	// In 32-bit game 32-bit bitmaps should have transparent pixels marked
181 	// (this adjustment is probably needed for DrawingSurface ops)
182 	if (game_col_depth == 32 && bmp_col_depth == 32) {
183 		if (has_alpha)
184 			set_rgb_mask_using_alpha_channel(new_bitmap);
185 	}
186 	// In 32-bit game hicolor bitmaps must be converted to the true color
187 	else if (game_col_depth == 32 && (bmp_col_depth > 8 && bmp_col_depth <= 16)) {
188 		new_bitmap = BitmapHelper::CreateBitmapCopy(bitmap, game_col_depth);
189 	}
190 	// In non-32-bit game truecolor bitmaps must be downgraded
191 	else if (game_col_depth <= 16 && bmp_col_depth > 16) {
192 		if (has_alpha) // if has valid alpha channel, convert it to regular transparency mask
193 			new_bitmap = remove_alpha_channel(bitmap);
194 		else // else simply convert bitmap
195 			new_bitmap = BitmapHelper::CreateBitmapCopy(bitmap, game_col_depth);
196 	}
197 	// Special case when we must convert 16-bit RGB to BGR
198 	else if (_G(convert_16bit_bgr) == 1 && bmp_col_depth == 16) {
199 		new_bitmap = convert_16_to_16bgr(bitmap);
200 	}
201 	return new_bitmap;
202 }
203 
ReplaceBitmapWithSupportedFormat(Bitmap * bitmap)204 Bitmap *ReplaceBitmapWithSupportedFormat(Bitmap *bitmap) {
205 	Bitmap *new_bitmap = GfxUtil::ConvertBitmap(bitmap, _G(gfxDriver)->GetCompatibleBitmapFormat(bitmap->GetColorDepth()));
206 	if (new_bitmap != bitmap)
207 		delete bitmap;
208 	return new_bitmap;
209 }
210 
PrepareSpriteForUse(Bitmap * bitmap,bool has_alpha)211 Bitmap *PrepareSpriteForUse(Bitmap *bitmap, bool has_alpha) {
212 	bool must_switch_palette = bitmap->GetColorDepth() == 8 && _GP(game).GetColorDepth() > 8;
213 	if (must_switch_palette)
214 		select_palette(_G(palette));
215 
216 	Bitmap *new_bitmap = AdjustBitmapForUseWithDisplayMode(bitmap, has_alpha);
217 	if (new_bitmap != bitmap)
218 		delete bitmap;
219 	new_bitmap = ReplaceBitmapWithSupportedFormat(new_bitmap);
220 
221 	if (must_switch_palette)
222 		unselect_palette();
223 	return new_bitmap;
224 }
225 
PrepareSpriteForUse(PBitmap bitmap,bool has_alpha)226 PBitmap PrepareSpriteForUse(PBitmap bitmap, bool has_alpha) {
227 	bool must_switch_palette = bitmap->GetColorDepth() == 8 && System_GetColorDepth() > 8;
228 	if (must_switch_palette)
229 		select_palette(_G(palette));
230 
231 	Bitmap *new_bitmap = AdjustBitmapForUseWithDisplayMode(bitmap.get(), has_alpha);
232 	new_bitmap = ReplaceBitmapWithSupportedFormat(new_bitmap);
233 
234 	if (must_switch_palette)
235 		unselect_palette();
236 	return new_bitmap == bitmap.get() ? bitmap : PBitmap(new_bitmap); // if bitmap is same, don't create new smart ptr!
237 }
238 
CopyScreenIntoBitmap(int width,int height,bool at_native_res)239 Bitmap *CopyScreenIntoBitmap(int width, int height, bool at_native_res) {
240 	Bitmap *dst = new Bitmap(width, height, _GP(game).GetColorDepth());
241 	GraphicResolution want_fmt;
242 	// If the size and color depth are supported we may copy right into our bitmap
243 	if (_G(gfxDriver)->GetCopyOfScreenIntoBitmap(dst, at_native_res, &want_fmt))
244 		return dst;
245 	// Otherwise we might need to copy between few bitmaps...
246 	Bitmap *buf_screenfmt = new Bitmap(want_fmt.Width, want_fmt.Height, want_fmt.ColorDepth);
247 	_G(gfxDriver)->GetCopyOfScreenIntoBitmap(buf_screenfmt, at_native_res);
248 	// If at least size matches then we may blit
249 	if (dst->GetSize() == buf_screenfmt->GetSize()) {
250 		dst->Blit(buf_screenfmt);
251 	}
252 	// Otherwise we need to go through another bitmap of the matching format
253 	else {
254 		Bitmap *buf_dstfmt = new Bitmap(buf_screenfmt->GetWidth(), buf_screenfmt->GetHeight(), dst->GetColorDepth());
255 		buf_dstfmt->Blit(buf_screenfmt);
256 		dst->StretchBlt(buf_dstfmt, RectWH(dst->GetSize()));
257 		delete buf_dstfmt;
258 	}
259 	delete buf_screenfmt;
260 	return dst;
261 }
262 
263 
264 // Begin resolution system functions
265 
266 // Multiplies up the number of pixels depending on the current
267 // resolution, to give a relatively fixed size at any game res
get_fixed_pixel_size(int pixels)268 AGS_INLINE int get_fixed_pixel_size(int pixels) {
269 	return pixels * _GP(game).GetRelativeUIMult();
270 }
271 
data_to_game_coord(int coord)272 AGS_INLINE int data_to_game_coord(int coord) {
273 	return coord * _GP(game).GetDataUpscaleMult();
274 }
275 
data_to_game_coords(int * x,int * y)276 AGS_INLINE void data_to_game_coords(int *x, int *y) {
277 	const int mul = _GP(game).GetDataUpscaleMult();
278 	x[0] *= mul;
279 	y[0] *= mul;
280 }
281 
data_to_game_round_up(int * x,int * y)282 AGS_INLINE void data_to_game_round_up(int *x, int *y) {
283 	const int mul = _GP(game).GetDataUpscaleMult();
284 	x[0] = x[0] * mul + (mul - 1);
285 	y[0] = y[0] * mul + (mul - 1);
286 }
287 
game_to_data_coord(int coord)288 AGS_INLINE int game_to_data_coord(int coord) {
289 	return coord / _GP(game).GetDataUpscaleMult();
290 }
291 
game_to_data_coords(int & x,int & y)292 AGS_INLINE void game_to_data_coords(int &x, int &y) {
293 	const int mul = _GP(game).GetDataUpscaleMult();
294 	x /= mul;
295 	y /= mul;
296 }
297 
game_to_data_round_up(int coord)298 AGS_INLINE int game_to_data_round_up(int coord) {
299 	const int mul = _GP(game).GetDataUpscaleMult();
300 	return (coord / mul) + (mul - 1);
301 }
302 
ctx_data_to_game_coord(int & x,int & y,bool hires_ctx)303 AGS_INLINE void ctx_data_to_game_coord(int &x, int &y, bool hires_ctx) {
304 	if (hires_ctx && !_GP(game).IsLegacyHiRes()) {
305 		x /= HIRES_COORD_MULTIPLIER;
306 		y /= HIRES_COORD_MULTIPLIER;
307 	} else if (!hires_ctx && _GP(game).IsLegacyHiRes()) {
308 		x *= HIRES_COORD_MULTIPLIER;
309 		y *= HIRES_COORD_MULTIPLIER;
310 	}
311 }
312 
ctx_data_to_game_size(int & w,int & h,bool hires_ctx)313 AGS_INLINE void ctx_data_to_game_size(int &w, int &h, bool hires_ctx) {
314 	if (hires_ctx && !_GP(game).IsLegacyHiRes()) {
315 		w = Math::Max(1, (w / HIRES_COORD_MULTIPLIER));
316 		h = Math::Max(1, (h / HIRES_COORD_MULTIPLIER));
317 	} else if (!hires_ctx && _GP(game).IsLegacyHiRes()) {
318 		w *= HIRES_COORD_MULTIPLIER;
319 		h *= HIRES_COORD_MULTIPLIER;
320 	}
321 }
322 
ctx_data_to_game_size(int size,bool hires_ctx)323 AGS_INLINE int ctx_data_to_game_size(int size, bool hires_ctx) {
324 	if (hires_ctx && !_GP(game).IsLegacyHiRes())
325 		return Math::Max(1, (size / HIRES_COORD_MULTIPLIER));
326 	if (!hires_ctx && _GP(game).IsLegacyHiRes())
327 		return size * HIRES_COORD_MULTIPLIER;
328 	return size;
329 }
330 
game_to_ctx_data_size(int size,bool hires_ctx)331 AGS_INLINE int game_to_ctx_data_size(int size, bool hires_ctx) {
332 	if (hires_ctx && !_GP(game).IsLegacyHiRes())
333 		return size * HIRES_COORD_MULTIPLIER;
334 	else if (!hires_ctx && _GP(game).IsLegacyHiRes())
335 		return Math::Max(1, (size / HIRES_COORD_MULTIPLIER));
336 	return size;
337 }
338 
defgame_to_finalgame_coords(int & x,int & y)339 AGS_INLINE void defgame_to_finalgame_coords(int &x, int &y) {
340 	// Note we support only upscale now
341 	x *= _GP(game).GetScreenUpscaleMult();
342 	y *= _GP(game).GetScreenUpscaleMult();
343 }
344 
345 // End resolution system functions
346 
347 // Create blank (black) images used to repaint borders around game frame
create_blank_image(int coldepth)348 void create_blank_image(int coldepth) {
349 	// this is the first time that we try to use the graphics driver,
350 	// so it's the most likey place for a crash
351 	//try
352 	//{
353 	Bitmap *blank = BitmapHelper::CreateBitmap(16, 16, coldepth);
354 	blank = ReplaceBitmapWithSupportedFormat(blank);
355 	blank->Clear();
356 	_G(blankImage) = _G(gfxDriver)->CreateDDBFromBitmap(blank, false, true);
357 	_G(blankSidebarImage) = _G(gfxDriver)->CreateDDBFromBitmap(blank, false, true);
358 	delete blank;
359 	/*}
360 	catch (Ali3DException gfxException)
361 	{
362 	    quit((char*)gfxException._message);
363 	}*/
364 }
365 
destroy_blank_image()366 void destroy_blank_image() {
367 	if (_G(blankImage))
368 		_G(gfxDriver)->DestroyDDB(_G(blankImage));
369 	if (_G(blankSidebarImage))
370 		_G(gfxDriver)->DestroyDDB(_G(blankSidebarImage));
371 	_G(blankImage) = nullptr;
372 	_G(blankSidebarImage) = nullptr;
373 }
374 
MakeColor(int color_index)375 int MakeColor(int color_index) {
376 	color_t real_color = 0;
377 	__my_setcolor(&real_color, color_index, _GP(game).GetColorDepth());
378 	return real_color;
379 }
380 
init_draw_method()381 void init_draw_method() {
382 	if (_G(gfxDriver)->HasAcceleratedTransform()) {
383 		_G(walkBehindMethod) = DrawAsSeparateSprite;
384 		create_blank_image(_GP(game).GetColorDepth());
385 	} else {
386 		_G(walkBehindMethod) = DrawOverCharSprite;
387 	}
388 
389 	on_mainviewport_changed();
390 	init_room_drawdata();
391 	if (_G(gfxDriver)->UsesMemoryBackBuffer())
392 		_G(gfxDriver)->GetMemoryBackBuffer()->Clear();
393 }
394 
dispose_draw_method()395 void dispose_draw_method() {
396 	dispose_room_drawdata();
397 	dispose_invalid_regions(false);
398 	destroy_blank_image();
399 }
400 
dispose_room_drawdata()401 void dispose_room_drawdata() {
402 	_GP(CameraDrawData).clear();
403 	dispose_invalid_regions(true);
404 }
405 
on_mainviewport_changed()406 void on_mainviewport_changed() {
407 	if (!_G(gfxDriver)->RequiresFullRedrawEachFrame()) {
408 		const auto &view = _GP(play).GetMainViewport();
409 		set_invalidrects_globaloffs(view.Left, view.Top);
410 		// the black background region covers whole game screen
411 		init_invalid_regions(-1, _GP(game).GetGameRes(), RectWH(_GP(game).GetGameRes()));
412 		if (_GP(game).GetGameRes().ExceedsByAny(view.GetSize()))
413 			clear_letterbox_borders();
414 	}
415 }
416 
417 // Allocates a bitmap for rendering camera/viewport pair (software render mode)
prepare_roomview_frame(Viewport * view)418 void prepare_roomview_frame(Viewport *view) {
419 	const int view_index = view->GetID();
420 	const Size view_sz = view->GetRect().GetSize();
421 	const Size cam_sz = view->GetCamera()->GetRect().GetSize();
422 	RoomCameraDrawData &draw_dat = _GP(CameraDrawData)[view_index];
423 	// We use intermediate bitmap to render camera/viewport pair in software mode under these conditions:
424 	// * camera size and viewport size are different (this may be suboptimal to paint dirty rects stretched,
425 	//   and also Allegro backend cannot stretch background of different colour depth).
426 	// * viewport is located outside of the virtual screen (even if partially): subbitmaps cannot contain
427 	//   regions outside of master bitmap, and we must not clamp surface size to virtual screen because
428 	//   plugins may want to also use viewport bitmap, therefore it should retain full size.
429 	if (cam_sz == view_sz && !draw_dat.IsOffscreen) {
430 		// note we keep the buffer allocated in case it will become useful later
431 		draw_dat.Frame.reset();
432 	} else {
433 		PBitmap &camera_frame = draw_dat.Frame;
434 		PBitmap &camera_buffer = draw_dat.Buffer;
435 		if (!camera_buffer || camera_buffer->GetWidth() < cam_sz.Width || camera_buffer->GetHeight() < cam_sz.Height) {
436 			// Allocate new buffer bitmap with an extra size in case they will want to zoom out
437 			int room_width = data_to_game_coord(_GP(thisroom).Width);
438 			int room_height = data_to_game_coord(_GP(thisroom).Height);
439 			Size alloc_sz = Size::Clamp(cam_sz * 2, Size(1, 1), Size(room_width, room_height));
440 			camera_buffer.reset(new Bitmap(alloc_sz.Width, alloc_sz.Height, _G(gfxDriver)->GetMemoryBackBuffer()->GetColorDepth()));
441 		}
442 
443 		if (!camera_frame || camera_frame->GetSize() != cam_sz) {
444 			camera_frame.reset(BitmapHelper::CreateSubBitmap(camera_buffer.get(), RectWH(cam_sz)));
445 		}
446 	}
447 }
448 
449 // Syncs room viewport and camera in case either size has changed
sync_roomview(Viewport * view)450 void sync_roomview(Viewport *view) {
451 	if (view->GetCamera() == nullptr)
452 		return;
453 	// Note the dirty regions' viewport is found using absolute offset on game screen
454 	init_invalid_regions(view->GetID(),
455 		view->GetCamera()->GetRect().GetSize(),
456 		_GP(play).GetRoomViewportAbs(view->GetID()));
457 	prepare_roomview_frame(view);
458 }
459 
init_room_drawdata()460 void init_room_drawdata() {
461 	if (_G(gfxDriver)->RequiresFullRedrawEachFrame())
462 		return;
463 	// Make sure all frame buffers are created for software drawing
464 	int view_count = _GP(play).GetRoomViewportCount();
465 	_GP(CameraDrawData).resize(view_count);
466 	for (int i = 0; i < _GP(play).GetRoomViewportCount(); ++i)
467 		sync_roomview(_GP(play).GetRoomViewport(i).get());
468 }
469 
on_roomviewport_created(int index)470 void on_roomviewport_created(int index) {
471 	if (!_G(gfxDriver) || _G(gfxDriver)->RequiresFullRedrawEachFrame())
472 		return;
473 	if ((size_t)index < _GP(CameraDrawData).size())
474 		return;
475 	_GP(CameraDrawData).resize(index + 1);
476 }
477 
on_roomviewport_deleted(int index)478 void on_roomviewport_deleted(int index) {
479 	if (_G(gfxDriver)->RequiresFullRedrawEachFrame())
480 		return;
481 	_GP(CameraDrawData).erase(_GP(CameraDrawData).begin() + index);
482 	delete_invalid_regions(index);
483 }
484 
on_roomviewport_changed(Viewport * view)485 void on_roomviewport_changed(Viewport *view) {
486 	if (_G(gfxDriver)->RequiresFullRedrawEachFrame())
487 		return;
488 	if (!view->IsVisible() || view->GetCamera() == nullptr)
489 		return;
490 	const bool off = !IsRectInsideRect(RectWH(_G(gfxDriver)->GetMemoryBackBuffer()->GetSize()), view->GetRect());
491 	const bool off_changed = off != _GP(CameraDrawData)[view->GetID()].IsOffscreen;
492 	_GP(CameraDrawData)[view->GetID()].IsOffscreen = off;
493 	if (view->HasChangedSize())
494 		sync_roomview(view);
495 	else if (off_changed)
496 		prepare_roomview_frame(view);
497 	// TODO: don't have to do this all the time, perhaps do "dirty rect" method
498 	// and only clear previous viewport location?
499 	invalidate_screen();
500 	_G(gfxDriver)->GetMemoryBackBuffer()->Clear();
501 }
502 
detect_roomviewport_overlaps(size_t z_index)503 void detect_roomviewport_overlaps(size_t z_index) {
504 	if (_G(gfxDriver)->RequiresFullRedrawEachFrame())
505 		return;
506 	// Find out if we overlap or are overlapped by anything;
507 	const auto &viewports = _GP(play).GetRoomViewportsZOrdered();
508 	for (; z_index < viewports.size(); ++z_index) {
509 		auto this_view = viewports[z_index];
510 		const int this_id = this_view->GetID();
511 		bool is_overlap = false;
512 		if (!this_view->IsVisible()) continue;
513 		for (size_t z_index2 = 0; z_index2 < z_index; ++z_index2) {
514 			if (!viewports[z_index2]->IsVisible()) continue;
515 			if (AreRectsIntersecting(this_view->GetRect(), viewports[z_index2]->GetRect())) {
516 				is_overlap = true;
517 				break;
518 			}
519 		}
520 		if (_GP(CameraDrawData)[this_id].IsOverlap != is_overlap) {
521 			_GP(CameraDrawData)[this_id].IsOverlap = is_overlap;
522 			prepare_roomview_frame(this_view.get());
523 		}
524 	}
525 }
526 
on_roomcamera_changed(Camera * cam)527 void on_roomcamera_changed(Camera *cam) {
528 	if (_G(gfxDriver)->RequiresFullRedrawEachFrame())
529 		return;
530 	if (cam->HasChangedSize()) {
531 		auto viewrefs = cam->GetLinkedViewports();
532 		for (auto vr : viewrefs) {
533 			PViewport vp = vr.lock();
534 			if (vp)
535 				sync_roomview(vp.get());
536 		}
537 	}
538 	// TODO: only invalidate what this particular camera sees
539 	invalidate_screen();
540 }
541 
mark_screen_dirty()542 void mark_screen_dirty() {
543 	_G(screen_is_dirty) = true;
544 }
545 
is_screen_dirty()546 bool is_screen_dirty() {
547 	return _G(screen_is_dirty);
548 }
549 
invalidate_screen()550 void invalidate_screen() {
551 	invalidate_all_rects();
552 }
553 
invalidate_camera_frame(int index)554 void invalidate_camera_frame(int index) {
555 	invalidate_all_camera_rects(index);
556 }
557 
invalidate_rect(int x1,int y1,int x2,int y2,bool in_room)558 void invalidate_rect(int x1, int y1, int x2, int y2, bool in_room) {
559 	invalidate_rect_ds(x1, y1, x2, y2, in_room);
560 }
561 
invalidate_sprite(int x1,int y1,IDriverDependantBitmap * pic,bool in_room)562 void invalidate_sprite(int x1, int y1, IDriverDependantBitmap *pic, bool in_room) {
563 	invalidate_rect_ds(x1, y1, x1 + pic->GetWidth(), y1 + pic->GetHeight(), in_room);
564 }
565 
invalidate_sprite_glob(int x1,int y1,IDriverDependantBitmap * pic)566 void invalidate_sprite_glob(int x1, int y1, IDriverDependantBitmap *pic) {
567 	invalidate_rect_global(x1, y1, x1 + pic->GetWidth(), y1 + pic->GetHeight());
568 }
569 
mark_current_background_dirty()570 void mark_current_background_dirty() {
571 	_G(current_background_is_dirty) = true;
572 }
573 
574 
draw_and_invalidate_text(Bitmap * ds,int x1,int y1,int font,color_t text_color,const char * text)575 void draw_and_invalidate_text(Bitmap *ds, int x1, int y1, int font, color_t text_color, const char *text) {
576 	wouttext_outline(ds, x1, y1, font, text_color, text);
577 	invalidate_rect(x1, y1, x1 + wgettextwidth_compensate(text, font), y1 + getfontheight_outlined(font) + get_fixed_pixel_size(1), false);
578 }
579 
580 // Renders black borders for the legacy boxed game mode,
581 // where whole game screen changes size between large and small rooms
render_black_borders()582 void render_black_borders() {
583 	if (_G(gfxDriver)->UsesMemoryBackBuffer())
584 		return;
585 	{
586 		_G(gfxDriver)->BeginSpriteBatch(RectWH(_GP(game).GetGameRes()), SpriteTransform());
587 		const Rect &viewport = _GP(play).GetMainViewport();
588 		if (viewport.Top > 0) {
589 			// letterbox borders
590 			_G(blankImage)->SetStretch(_GP(game).GetGameRes().Width, viewport.Top, false);
591 			_G(gfxDriver)->DrawSprite(0, 0, _G(blankImage));
592 			_G(gfxDriver)->DrawSprite(0, viewport.Bottom + 1, _G(blankImage));
593 		}
594 		if (viewport.Left > 0) {
595 			// sidebar borders for widescreen
596 			_G(blankSidebarImage)->SetStretch(viewport.Left, viewport.GetHeight(), false);
597 			_G(gfxDriver)->DrawSprite(0, 0, _G(blankSidebarImage));
598 			_G(gfxDriver)->DrawSprite(viewport.Right + 1, 0, _G(blankSidebarImage));
599 		}
600 	}
601 }
602 
603 
render_to_screen()604 void render_to_screen() {
605 	// Stage: final plugin callback (still drawn on game screen
606 	if (pl_any_want_hook(AGSE_FINALSCREENDRAW)) {
607 		_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(), SpriteTransform(), Point(0, _GP(play).shake_screen_yoff), (GlobalFlipType)_GP(play).screen_flipped);
608 		_G(gfxDriver)->DrawSprite(AGSE_FINALSCREENDRAW, 0, nullptr);
609 	}
610 	// Stage: engine overlay
611 	construct_engine_overlay();
612 
613 	// only vsync in full screen mode, it makes things worse in a window
614 	_G(gfxDriver)->EnableVsyncBeforeRender((_GP(scsystem).vsync > 0) && (!_GP(scsystem).windowed));
615 
616 	bool succeeded = false;
617 	while (!succeeded) {
618 		//     try
619 		//     {
620 		// For software renderer, need to blacken upper part of the game frame when shaking screen moves image down
621 		const Rect &viewport = _GP(play).GetMainViewport();
622 		if (_GP(play).shake_screen_yoff > 0 && !_G(gfxDriver)->RequiresFullRedrawEachFrame())
623 			_G(gfxDriver)->ClearRectangle(viewport.Left, viewport.Top, viewport.GetWidth() - 1, _GP(play).shake_screen_yoff, nullptr);
624 		_G(gfxDriver)->Render(0, _GP(play).shake_screen_yoff, (GlobalFlipType)_GP(play).screen_flipped);
625 
626 #if AGS_PLATFORM_OS_ANDROID
627 		if (_GP(game).color_depth == 1)
628 			android_render();
629 #elif AGS_PLATFORM_OS_IOS
630 		if (_GP(game).color_depth == 1)
631 			ios_render();
632 #endif
633 
634 		succeeded = true;
635 		/*}
636 		catch (Ali3DFullscreenLostException)
637 		{
638 		    platform->Delay(500);
639 		}*/
640 	}
641 }
642 
643 // Blanks out borders around main viewport in case it became smaller (e.g. after loading another room)
clear_letterbox_borders()644 void clear_letterbox_borders() {
645 	const Rect &viewport = _GP(play).GetMainViewport();
646 	_G(gfxDriver)->ClearRectangle(0, 0, _GP(game).GetGameRes().Width - 1, viewport.Top - 1, nullptr);
647 	_G(gfxDriver)->ClearRectangle(0, viewport.Bottom + 1, _GP(game).GetGameRes().Width - 1, _GP(game).GetGameRes().Height - 1, nullptr);
648 }
649 
draw_game_screen_callback()650 void draw_game_screen_callback() {
651 	construct_game_scene(true);
652 	construct_game_screen_overlay(false);
653 }
654 
655 
656 
putpixel_compensate(Bitmap * ds,int xx,int yy,int col)657 void putpixel_compensate(Bitmap *ds, int xx, int yy, int col) {
658 	if ((ds->GetColorDepth() == 32) && (col != 0)) {
659 		// ensure the alpha channel is preserved if it has one
660 		int alphaval = geta32(ds->GetPixel(xx, yy));
661 		col = makeacol32(getr32(col), getg32(col), getb32(col), alphaval);
662 	}
663 	ds->FillRect(Rect(xx, yy, xx + get_fixed_pixel_size(1) - 1, yy + get_fixed_pixel_size(1) - 1), col);
664 }
665 
666 
667 
668 
draw_sprite_support_alpha(Bitmap * ds,bool ds_has_alpha,int xpos,int ypos,Bitmap * image,bool src_has_alpha,BlendMode blend_mode,int alpha)669 void draw_sprite_support_alpha(Bitmap *ds, bool ds_has_alpha, int xpos, int ypos, Bitmap *image, bool src_has_alpha,
670                                BlendMode blend_mode, int alpha) {
671 	if (alpha <= 0)
672 		return;
673 
674 	if (_GP(game).options[OPT_SPRITEALPHA] == kSpriteAlphaRender_Proper) {
675 		GfxUtil::DrawSpriteBlend(ds, Point(xpos, ypos), image, blend_mode, ds_has_alpha, src_has_alpha, alpha);
676 	}
677 	// Backwards-compatible drawing
678 	else if (src_has_alpha && alpha == 0xFF) {
679 		set_alpha_blender();
680 		ds->TransBlendBlt(image, xpos, ypos);
681 	} else {
682 		GfxUtil::DrawSpriteWithTransparency(ds, image, xpos, ypos, alpha);
683 	}
684 }
685 
draw_sprite_slot_support_alpha(Bitmap * ds,bool ds_has_alpha,int xpos,int ypos,int src_slot,BlendMode blend_mode,int alpha)686 void draw_sprite_slot_support_alpha(Bitmap *ds, bool ds_has_alpha, int xpos, int ypos, int src_slot,
687                                     BlendMode blend_mode, int alpha) {
688 	draw_sprite_support_alpha(ds, ds_has_alpha, xpos, ypos, _GP(spriteset)[src_slot], (_GP(game).SpriteInfos[src_slot].Flags & SPF_ALPHACHANNEL) != 0,
689 	                          blend_mode, alpha);
690 }
691 
692 
recycle_ddb_bitmap(IDriverDependantBitmap * bimp,Bitmap * source,bool hasAlpha,bool opaque)693 IDriverDependantBitmap *recycle_ddb_bitmap(IDriverDependantBitmap *bimp, Bitmap *source, bool hasAlpha, bool opaque) {
694 	if (bimp != nullptr) {
695 		// same colour depth, width and height -> reuse
696 		if (((bimp->GetColorDepth() + 1) / 8 == source->GetBPP()) &&
697 		        (bimp->GetWidth() == source->GetWidth()) && (bimp->GetHeight() == source->GetHeight())) {
698 			_G(gfxDriver)->UpdateDDBFromBitmap(bimp, source, hasAlpha);
699 			return bimp;
700 		}
701 
702 		_G(gfxDriver)->DestroyDDB(bimp);
703 	}
704 	bimp = _G(gfxDriver)->CreateDDBFromBitmap(source, hasAlpha, opaque);
705 	return bimp;
706 }
707 
invalidate_cached_walkbehinds()708 void invalidate_cached_walkbehinds() {
709 	memset(&_G(actspswbcache)[0], 0, sizeof(CachedActSpsData) * _G(actSpsCount));
710 }
711 
712 // sort_out_walk_behinds: modifies the supplied sprite by overwriting parts
713 // of it with transparent pixels where there are walk-behind areas
714 // Returns whether any pixels were updated
sort_out_walk_behinds(Bitmap * sprit,int xx,int yy,int basel,Bitmap * copyPixelsFrom=nullptr,Bitmap * checkPixelsFrom=nullptr,int zoom=100)715 int sort_out_walk_behinds(Bitmap *sprit, int xx, int yy, int basel, Bitmap *copyPixelsFrom = nullptr, Bitmap *checkPixelsFrom = nullptr, int zoom = 100) {
716 	if (_G(noWalkBehindsAtAll))
717 		return 0;
718 
719 	if ((!_GP(thisroom).WalkBehindMask->IsMemoryBitmap()) ||
720 	        (!sprit->IsMemoryBitmap()))
721 		quit("!sort_out_walk_behinds: wb bitmap not linear");
722 
723 	int rr, tmm, toheight; //,tcol;
724 	// precalculate this to try and shave some time off
725 	int maskcol = sprit->GetMaskColor();
726 	int spcoldep = sprit->GetColorDepth();
727 	int screenhit = _GP(thisroom).WalkBehindMask->GetHeight();
728 	short *shptr, *shptr2;
729 	int *loptr, *loptr2;
730 	int pixelsChanged = 0;
731 	int ee = 0;
732 	if (xx < 0)
733 		ee = 0 - xx;
734 
735 	if ((checkPixelsFrom != nullptr) && (checkPixelsFrom->GetColorDepth() != spcoldep))
736 		quit("sprite colour depth does not match background colour depth");
737 
738 	for (; ee < sprit->GetWidth(); ee++) {
739 		if (ee + xx >= _GP(thisroom).WalkBehindMask->GetWidth())
740 			break;
741 
742 		if ((!_G(walkBehindExists)[ee + xx]) ||
743 		        (_G(walkBehindEndY)[ee + xx] <= yy) ||
744 		        (_G(walkBehindStartY)[ee + xx] > yy + sprit->GetHeight()))
745 			continue;
746 
747 		toheight = sprit->GetHeight();
748 
749 		if (_G(walkBehindStartY)[ee + xx] < yy)
750 			rr = 0;
751 		else
752 			rr = (_G(walkBehindStartY)[ee + xx] - yy);
753 
754 		// Since we will use _getpixel, ensure we only check within the screen
755 		if (rr + yy < 0)
756 			rr = 0 - yy;
757 		if (toheight + yy > screenhit)
758 			toheight = screenhit - yy;
759 		if (toheight + yy > _G(walkBehindEndY)[ee + xx])
760 			toheight = _G(walkBehindEndY)[ee + xx] - yy;
761 		if (rr < 0)
762 			rr = 0;
763 
764 		for (; rr < toheight; rr++) {
765 
766 			// we're ok with _getpixel because we've checked the screen edges
767 			//tmm = _getpixel(_GP(thisroom).WalkBehindMask,ee+xx,rr+yy);
768 			// actually, _getpixel is well inefficient, do it ourselves
769 			// since we know it's 8-bit bitmap
770 			tmm = _GP(thisroom).WalkBehindMask->GetScanLine(rr + yy)[ee + xx];
771 			if (tmm < 1) continue;
772 			if (_G(croom)->walkbehind_base[tmm] <= basel) continue;
773 
774 			if (copyPixelsFrom != nullptr) {
775 				if (spcoldep <= 8) {
776 					if (checkPixelsFrom->GetScanLine((rr * 100) / zoom)[(ee * 100) / zoom] != maskcol) {
777 						sprit->GetScanLineForWriting(rr)[ee] = copyPixelsFrom->GetScanLine(rr + yy)[ee + xx];
778 						pixelsChanged = 1;
779 					}
780 				} else if (spcoldep <= 16) {
781 					shptr = (short *)&sprit->GetScanLine(rr)[0];
782 					shptr2 = (short *)&checkPixelsFrom->GetScanLine((rr * 100) / zoom)[0];
783 					if (shptr2[(ee * 100) / zoom] != maskcol) {
784 						shptr[ee] = ((short *)(&copyPixelsFrom->GetScanLine(rr + yy)[0]))[ee + xx];
785 						pixelsChanged = 1;
786 					}
787 				} else if (spcoldep == 24) {
788 					char *chptr = (char *)&sprit->GetScanLine(rr)[0];
789 					char *chptr2 = (char *)&checkPixelsFrom->GetScanLine((rr * 100) / zoom)[0];
790 					if (memcmp(&chptr2[((ee * 100) / zoom) * 3], &maskcol, 3) != 0) {
791 						memcpy(&chptr[ee * 3], &copyPixelsFrom->GetScanLine(rr + yy)[(ee + xx) * 3], 3);
792 						pixelsChanged = 1;
793 					}
794 				} else if (spcoldep <= 32) {
795 					loptr = (int *)&sprit->GetScanLine(rr)[0];
796 					loptr2 = (int *)&checkPixelsFrom->GetScanLine((rr * 100) / zoom)[0];
797 					if (loptr2[(ee * 100) / zoom] != maskcol) {
798 						loptr[ee] = ((int *)(&copyPixelsFrom->GetScanLine(rr + yy)[0]))[ee + xx];
799 						pixelsChanged = 1;
800 					}
801 				}
802 			} else {
803 				pixelsChanged = 1;
804 				if (spcoldep <= 8)
805 					sprit->GetScanLineForWriting(rr)[ee] = maskcol;
806 				else if (spcoldep <= 16) {
807 					shptr = (short *)&sprit->GetScanLine(rr)[0];
808 					shptr[ee] = maskcol;
809 				} else if (spcoldep == 24) {
810 					char *chptr = (char *)&sprit->GetScanLine(rr)[0];
811 					memcpy(&chptr[ee * 3], &maskcol, 3);
812 				} else if (spcoldep <= 32) {
813 					loptr = (int *)&sprit->GetScanLine(rr)[0];
814 					loptr[ee] = maskcol;
815 				} else
816 					quit("!Sprite colour depth >32 ??");
817 			}
818 		}
819 	}
820 	return pixelsChanged;
821 }
822 
sort_out_char_sprite_walk_behind(int actspsIndex,int xx,int yy,int basel,int zoom,int width,int height)823 void sort_out_char_sprite_walk_behind(int actspsIndex, int xx, int yy, int basel, int zoom, int width, int height) {
824 	if (_G(noWalkBehindsAtAll))
825 		return;
826 
827 	if ((!_G(actspswbcache)[actspsIndex].valid) ||
828 	        (_G(actspswbcache)[actspsIndex].xWas != xx) ||
829 	        (_G(actspswbcache)[actspsIndex].yWas != yy) ||
830 	        (_G(actspswbcache)[actspsIndex].baselineWas != basel)) {
831 		_G(actspswb)[actspsIndex] = recycle_bitmap(_G(actspswb)[actspsIndex], _GP(thisroom).BgFrames[_GP(play).bg_frame].Graphic->GetColorDepth(), width, height, true);
832 		Bitmap *wbSprite = _G(actspswb)[actspsIndex];
833 
834 		_G(actspswbcache)[actspsIndex].isWalkBehindHere = sort_out_walk_behinds(wbSprite, xx, yy, basel, _GP(thisroom).BgFrames[_GP(play).bg_frame].Graphic.get(), _G(actsps)[actspsIndex], zoom);
835 		_G(actspswbcache)[actspsIndex].xWas = xx;
836 		_G(actspswbcache)[actspsIndex].yWas = yy;
837 		_G(actspswbcache)[actspsIndex].baselineWas = basel;
838 		_G(actspswbcache)[actspsIndex].valid = 1;
839 
840 		if (_G(actspswbcache)[actspsIndex].isWalkBehindHere) {
841 			_G(actspswbbmp)[actspsIndex] = recycle_ddb_bitmap(_G(actspswbbmp)[actspsIndex], _G(actspswb)[actspsIndex], false);
842 		}
843 	}
844 
845 	if (_G(actspswbcache)[actspsIndex].isWalkBehindHere) {
846 		add_to_sprite_list(_G(actspswbbmp)[actspsIndex], xx, yy, basel, 0, -1, true);
847 	}
848 }
849 
clear_draw_list()850 void clear_draw_list() {
851 	_GP(thingsToDrawList).clear();
852 }
add_thing_to_draw(IDriverDependantBitmap * bmp,int x,int y,int trans,bool alphaChannel)853 void add_thing_to_draw(IDriverDependantBitmap *bmp, int x, int y, int trans, bool alphaChannel) {
854 	SpriteListEntry sprite;
855 	sprite.pic = nullptr;
856 	sprite.bmp = bmp;
857 	sprite.x = x;
858 	sprite.y = y;
859 	sprite.transparent = trans;
860 	sprite.hasAlphaChannel = alphaChannel;
861 	_GP(thingsToDrawList).push_back(sprite);
862 }
863 
864 // the sprite list is an intermediate list used to order
865 // objects and characters by their baselines before everything
866 // is added to the Thing To Draw List
clear_sprite_list()867 void clear_sprite_list() {
868 	_GP(sprlist).clear();
869 }
add_to_sprite_list(IDriverDependantBitmap * spp,int xx,int yy,int baseline,int trans,int sprNum,bool isWalkBehind)870 void add_to_sprite_list(IDriverDependantBitmap *spp, int xx, int yy, int baseline, int trans, int sprNum, bool isWalkBehind) {
871 
872 	if (spp == nullptr)
873 		quit("add_to_sprite_list: attempted to draw NULL sprite");
874 	// completely invisible, so don't draw it at all
875 	if (trans == 255)
876 		return;
877 
878 	SpriteListEntry sprite;
879 	if ((sprNum >= 0) && ((_GP(game).SpriteInfos[sprNum].Flags & SPF_ALPHACHANNEL) != 0))
880 		sprite.hasAlphaChannel = true;
881 	else
882 		sprite.hasAlphaChannel = false;
883 
884 	sprite.bmp = spp;
885 	sprite.baseline = baseline;
886 	sprite.x = xx;
887 	sprite.y = yy;
888 	sprite.transparent = trans;
889 
890 	if (_G(walkBehindMethod) == DrawAsSeparateSprite)
891 		sprite.takesPriorityIfEqual = !isWalkBehind;
892 	else
893 		sprite.takesPriorityIfEqual = isWalkBehind;
894 
895 	_GP(sprlist).push_back(sprite);
896 }
897 
repair_alpha_channel(Bitmap * dest,Bitmap * bgpic)898 void repair_alpha_channel(Bitmap *dest, Bitmap *bgpic) {
899 	// Repair the alpha channel, because sprites may have been drawn
900 	// over it by the buttons, etc
901 	int theWid = (dest->GetWidth() < bgpic->GetWidth()) ? dest->GetWidth() : bgpic->GetWidth();
902 	int theHit = (dest->GetHeight() < bgpic->GetHeight()) ? dest->GetHeight() : bgpic->GetHeight();
903 	for (int y = 0; y < theHit; y++) {
904 		unsigned int *destination = ((unsigned int *)dest->GetScanLineForWriting(y));
905 		unsigned int *source = ((unsigned int *)bgpic->GetScanLineForWriting(y));
906 		for (int x = 0; x < theWid; x++) {
907 			destination[x] |= (source[x] & 0xff000000);
908 		}
909 	}
910 }
911 
912 
913 // used by GUI renderer to draw images
draw_gui_sprite(Bitmap * ds,int pic,int x,int y,bool use_alpha,BlendMode blend_mode)914 void draw_gui_sprite(Bitmap *ds, int pic, int x, int y, bool use_alpha, BlendMode blend_mode) {
915 	Bitmap *sprite = _GP(spriteset)[pic];
916 	const bool ds_has_alpha  = ds->GetColorDepth() == 32;
917 	const bool src_has_alpha = (_GP(game).SpriteInfos[pic].Flags & SPF_ALPHACHANNEL) != 0;
918 
919 	if (use_alpha && _GP(game).options[OPT_NEWGUIALPHA] == kGuiAlphaRender_Proper) {
920 		GfxUtil::DrawSpriteBlend(ds, Point(x, y), sprite, blend_mode, ds_has_alpha, src_has_alpha);
921 	}
922 	// Backwards-compatible drawing
923 	else if (use_alpha && ds_has_alpha && _GP(game).options[OPT_NEWGUIALPHA] == kGuiAlphaRender_AdditiveAlpha) {
924 		if (src_has_alpha)
925 			set_additive_alpha_blender();
926 		else
927 			set_opaque_alpha_blender();
928 		ds->TransBlendBlt(sprite, x, y);
929 	} else {
930 		GfxUtil::DrawSpriteWithTransparency(ds, sprite, x, y);
931 	}
932 }
933 
draw_gui_sprite_v330(Bitmap * ds,int pic,int x,int y,bool use_alpha,BlendMode blend_mode)934 void draw_gui_sprite_v330(Bitmap *ds, int pic, int x, int y, bool use_alpha, BlendMode blend_mode) {
935 	draw_gui_sprite(ds, pic, x, y, use_alpha && (_G(loaded_game_file_version) >= kGameVersion_330), blend_mode);
936 }
937 
938 // function to sort the sprites into baseline order
spritelistentry_less(const SpriteListEntry & e1,const SpriteListEntry & e2)939 bool spritelistentry_less(const SpriteListEntry &e1, const SpriteListEntry &e2) {
940 	if (e1.baseline == e2.baseline) {
941 		if (e1.takesPriorityIfEqual)
942 			return false;
943 		if (e2.takesPriorityIfEqual)
944 			return true;
945 	}
946 	return e1.baseline < e2.baseline;
947 }
948 
949 
950 
951 
draw_sprite_list()952 void draw_sprite_list() {
953 
954 	if (_G(walkBehindMethod) == DrawAsSeparateSprite) {
955 		for (int ee = 1; ee < MAX_WALK_BEHINDS; ee++) {
956 			if (_G(walkBehindBitmap)[ee] != nullptr) {
957 				add_to_sprite_list(_G(walkBehindBitmap)[ee], _G(walkBehindLeft)[ee], _G(walkBehindTop)[ee],
958 				                   _G(croom)->walkbehind_base[ee], 0, -1, true);
959 			}
960 		}
961 	}
962 
963 	std::sort(_GP(sprlist).begin(), _GP(sprlist).end(), spritelistentry_less);
964 
965 	if (pl_any_want_hook(AGSE_PRESCREENDRAW))
966 		add_thing_to_draw(nullptr, AGSE_PRESCREENDRAW, 0, TRANS_RUN_PLUGIN, false);
967 
968 	// copy the sorted sprites into the Things To Draw list
969 	_GP(thingsToDrawList).insert(_GP(thingsToDrawList).end(), _GP(sprlist).begin(), _GP(sprlist).end());
970 }
971 
972 // Avoid freeing and reallocating the memory if possible
recycle_bitmap(Bitmap * bimp,int coldep,int wid,int hit,bool make_transparent)973 Bitmap *recycle_bitmap(Bitmap *bimp, int coldep, int wid, int hit, bool make_transparent) {
974 	if (bimp != nullptr) {
975 		// same colour depth, width and height -> reuse
976 		if ((bimp->GetColorDepth() == coldep) && (bimp->GetWidth() == wid)
977 		        && (bimp->GetHeight() == hit)) {
978 			if (make_transparent) {
979 				bimp->ClearTransparent();
980 			}
981 			return bimp;
982 		}
983 
984 		delete bimp;
985 	}
986 	bimp = make_transparent ? BitmapHelper::CreateTransparentBitmap(wid, hit, coldep) :
987 	       BitmapHelper::CreateBitmap(wid, hit, coldep);
988 	return bimp;
989 }
990 
991 
992 // Get the local tint at the specified X & Y co-ordinates, based on
993 // room regions and SetAmbientTint
994 // tint_amnt will be set to 0 if there is no tint enabled
995 // if this is the case, then light_lev holds the light level (0=none)
get_local_tint(int xpp,int ypp,int nolight,int * tint_amnt,int * tint_r,int * tint_g,int * tint_b,int * tint_lit,int * light_lev)996 void get_local_tint(int xpp, int ypp, int nolight,
997                     int *tint_amnt, int *tint_r, int *tint_g,
998                     int *tint_b, int *tint_lit,
999                     int *light_lev) {
1000 
1001 	int tint_level = 0, light_level = 0;
1002 	int tint_amount = 0;
1003 	int tint_red = 0;
1004 	int tint_green = 0;
1005 	int tint_blue = 0;
1006 	int tint_light = 255;
1007 
1008 	if (nolight == 0) {
1009 
1010 		int onRegion = 0;
1011 
1012 		if ((_GP(play).ground_level_areas_disabled & GLED_EFFECTS) == 0) {
1013 			// check if the player is on a region, to find its
1014 			// light/tint level
1015 			onRegion = GetRegionIDAtRoom(xpp, ypp);
1016 			if (onRegion == 0) {
1017 				// when walking, he might just be off a walkable area
1018 				onRegion = GetRegionIDAtRoom(xpp - 3, ypp);
1019 				if (onRegion == 0)
1020 					onRegion = GetRegionIDAtRoom(xpp + 3, ypp);
1021 				if (onRegion == 0)
1022 					onRegion = GetRegionIDAtRoom(xpp, ypp - 3);
1023 				if (onRegion == 0)
1024 					onRegion = GetRegionIDAtRoom(xpp, ypp + 3);
1025 			}
1026 		}
1027 
1028 		if ((onRegion > 0) && (onRegion < MAX_ROOM_REGIONS)) {
1029 			light_level = _GP(thisroom).Regions[onRegion].Light;
1030 			tint_level = _GP(thisroom).Regions[onRegion].Tint;
1031 		} else if (onRegion <= 0) {
1032 			light_level = _GP(thisroom).Regions[0].Light;
1033 			tint_level = _GP(thisroom).Regions[0].Tint;
1034 		}
1035 
1036 		int tint_sat = (tint_level >> 24) & 0xFF;
1037 		if ((_GP(game).color_depth == 1) || ((tint_level & 0x00ffffff) == 0) ||
1038 		        (tint_sat == 0))
1039 			tint_level = 0;
1040 
1041 		if (tint_level) {
1042 			tint_red = (unsigned char)(tint_level & 0x000ff);
1043 			tint_green = (unsigned char)((tint_level >> 8) & 0x000ff);
1044 			tint_blue = (unsigned char)((tint_level >> 16) & 0x000ff);
1045 			tint_amount = tint_sat;
1046 			tint_light = light_level;
1047 		}
1048 
1049 		if (_GP(play).rtint_enabled) {
1050 			if (_GP(play).rtint_level > 0) {
1051 				// override with room tint
1052 				tint_red = _GP(play).rtint_red;
1053 				tint_green = _GP(play).rtint_green;
1054 				tint_blue = _GP(play).rtint_blue;
1055 				tint_amount = _GP(play).rtint_level;
1056 				tint_light = _GP(play).rtint_light;
1057 			} else {
1058 				// override with room light level
1059 				tint_amount = 0;
1060 				light_level = _GP(play).rtint_light;
1061 			}
1062 		}
1063 	}
1064 
1065 	// copy to output parameters
1066 	*tint_amnt = tint_amount;
1067 	*tint_r = tint_red;
1068 	*tint_g = tint_green;
1069 	*tint_b = tint_blue;
1070 	*tint_lit = tint_light;
1071 	if (light_lev)
1072 		*light_lev = light_level;
1073 }
1074 
1075 
1076 
1077 
1078 // Applies the specified RGB Tint or Light Level to the actsps
1079 // sprite indexed with actspsindex
apply_tint_or_light(int actspsindex,int light_level,int tint_amount,int tint_red,int tint_green,int tint_blue,int tint_light,int coldept,Bitmap * blitFrom)1080 void apply_tint_or_light(int actspsindex, int light_level,
1081                          int tint_amount, int tint_red, int tint_green,
1082                          int tint_blue, int tint_light, int coldept,
1083                          Bitmap *blitFrom) {
1084 
1085 // In a 256-colour game, we cannot do tinting or lightening
1086 // (but we can do darkening, if light_level < 0)
1087 	if (_GP(game).color_depth == 1) {
1088 		if ((light_level > 0) || (tint_amount != 0))
1089 			return;
1090 	}
1091 
1092 // we can only do tint/light if the colour depths match
1093 	if (_GP(game).GetColorDepth() == _G(actsps)[actspsindex]->GetColorDepth()) {
1094 		Bitmap *oldwas;
1095 		// if the caller supplied a source bitmap, ->Blit from it
1096 		// (used as a speed optimisation where possible)
1097 		if (blitFrom)
1098 			oldwas = blitFrom;
1099 		// otherwise, make a new target bmp
1100 		else {
1101 			oldwas = _G(actsps)[actspsindex];
1102 			_G(actsps)[actspsindex] = BitmapHelper::CreateBitmap(oldwas->GetWidth(), oldwas->GetHeight(), coldept);
1103 		}
1104 		Bitmap *active_spr = _G(actsps)[actspsindex];
1105 
1106 		if (tint_amount) {
1107 			// It is an RGB tint
1108 			tint_image(active_spr, oldwas, tint_red, tint_green, tint_blue, tint_amount, tint_light);
1109 		} else {
1110 			// the RGB values passed to set_trans_blender decide whether it will darken
1111 			// or lighten sprites ( <128=darken, >128=lighten). The parameter passed
1112 			// to LitBlendBlt defines how much it will be darkened/lightened by.
1113 
1114 			int lit_amnt;
1115 			active_spr->FillTransparent();
1116 			// It's a light level, not a tint
1117 			if (_GP(game).color_depth == 1) {
1118 				// 256-col
1119 				lit_amnt = (250 - ((-light_level) * 5) / 2);
1120 			} else {
1121 				// hi-color
1122 				if (light_level < 0)
1123 					set_my_trans_blender(8, 8, 8, 0);
1124 				else
1125 					set_my_trans_blender(248, 248, 248, 0);
1126 				lit_amnt = abs(light_level) * 2;
1127 			}
1128 
1129 			active_spr->LitBlendBlt(oldwas, 0, 0, lit_amnt);
1130 		}
1131 
1132 		if (oldwas != blitFrom)
1133 			delete oldwas;
1134 
1135 	} else if (blitFrom) {
1136 		// sprite colour depth != game colour depth, so don't try and tint
1137 		// but we do need to do something, so copy the source
1138 		Bitmap *active_spr = _G(actsps)[actspsindex];
1139 		active_spr->Blit(blitFrom, 0, 0, 0, 0, active_spr->GetWidth(), active_spr->GetHeight());
1140 	}
1141 
1142 }
1143 
1144 // Draws the specified 'sppic' sprite onto actsps[useindx] at the
1145 // specified width and height, and flips the sprite if necessary.
1146 // Returns 1 if something was drawn to actsps; returns 0 if no
1147 // scaling or stretching was required, in which case nothing was done
scale_and_flip_sprite(int useindx,int coldept,int zoom_level,int sppic,int newwidth,int newheight,int isMirrored)1148 int scale_and_flip_sprite(int useindx, int coldept, int zoom_level,
1149                           int sppic, int newwidth, int newheight,
1150                           int isMirrored) {
1151 
1152 	int actsps_used = 1;
1153 
1154 	// create and blank out the new sprite
1155 	_G(actsps)[useindx] = recycle_bitmap(_G(actsps)[useindx], coldept, newwidth, newheight, true);
1156 	Bitmap *active_spr = _G(actsps)[useindx];
1157 
1158 	if (zoom_level != 100) {
1159 		// Scaled character
1160 
1161 		_G(our_eip) = 334;
1162 
1163 		// Ensure that anti-aliasing routines have a palette to
1164 		// use for mapping while faded out
1165 		if (_G(in_new_room))
1166 			select_palette(_G(palette));
1167 
1168 
1169 		if (isMirrored) {
1170 			Bitmap *tempspr = BitmapHelper::CreateBitmap(newwidth, newheight, coldept);
1171 			tempspr->Fill(_G(actsps)[useindx]->GetMaskColor());
1172 			if ((IS_ANTIALIAS_SPRITES) && ((_GP(game).SpriteInfos[sppic].Flags & SPF_ALPHACHANNEL) == 0))
1173 				tempspr->AAStretchBlt(_GP(spriteset)[sppic], RectWH(0, 0, newwidth, newheight), Shared::kBitmap_Transparency);
1174 			else
1175 				tempspr->StretchBlt(_GP(spriteset)[sppic], RectWH(0, 0, newwidth, newheight), Shared::kBitmap_Transparency);
1176 			active_spr->FlipBlt(tempspr, 0, 0, Shared::kBitmap_HFlip);
1177 			delete tempspr;
1178 		} else if ((IS_ANTIALIAS_SPRITES) && ((_GP(game).SpriteInfos[sppic].Flags & SPF_ALPHACHANNEL) == 0))
1179 			active_spr->AAStretchBlt(_GP(spriteset)[sppic], RectWH(0, 0, newwidth, newheight), Shared::kBitmap_Transparency);
1180 		else
1181 			active_spr->StretchBlt(_GP(spriteset)[sppic], RectWH(0, 0, newwidth, newheight), Shared::kBitmap_Transparency);
1182 
1183 		/*  AASTR2 version of code (doesn't work properly, gives black borders)
1184 		if (IS_ANTIALIAS_SPRITES) {
1185 		int aa_mode = AA_MASKED;
1186 		if (_GP(game).spriteflags[sppic] & SPF_ALPHACHANNEL)
1187 		aa_mode |= AA_ALPHA | AA_RAW_ALPHA;
1188 		if (isMirrored)
1189 		aa_mode |= AA_HFLIP;
1190 
1191 		aa_set_mode(aa_mode);
1192 		->AAStretchBlt(_G(actsps)[useindx],_GP(spriteset)[sppic],0,0,newwidth,newheight);
1193 		}
1194 		else if (isMirrored) {
1195 		Bitmap *tempspr = BitmapHelper::CreateBitmap_ (coldept, newwidth, newheight);
1196 		->Clear (tempspr, ->GetMaskColor(_G(actsps)[useindx]));
1197 		->StretchBlt (tempspr, _GP(spriteset)[sppic], 0, 0, newwidth, newheight);
1198 		->FlipBlt(Shared::kBitmap_HFlip, (_G(actsps)[useindx], tempspr, 0, 0);
1199 		wfreeblock (tempspr);
1200 		}
1201 		else
1202 		->StretchBlt(_G(actsps)[useindx],_GP(spriteset)[sppic],0,0,newwidth,newheight);
1203 		*/
1204 		if (_G(in_new_room))
1205 			unselect_palette();
1206 
1207 	} else {
1208 		// Not a scaled character, draw at normal size
1209 
1210 		_G(our_eip) = 339;
1211 
1212 		if (isMirrored)
1213 			active_spr->FlipBlt(_GP(spriteset)[sppic], 0, 0, Shared::kBitmap_HFlip);
1214 		else
1215 			actsps_used = 0;
1216 		//->Blit (_GP(spriteset)[sppic], _G(actsps)[useindx], 0, 0, 0, 0, _G(actsps)[useindx]->GetWidth(), _G(actsps)[useindx]->GetHeight());
1217 	}
1218 
1219 	return actsps_used;
1220 }
1221 
1222 
1223 
1224 // create the actsps[aa] image with the object drawn correctly
1225 // returns 1 if nothing at all has changed and actsps is still
1226 // intact from last time; 0 otherwise
construct_object_gfx(int aa,int * drawnWidth,int * drawnHeight,bool alwaysUseSoftware)1227 int construct_object_gfx(int aa, int *drawnWidth, int *drawnHeight, bool alwaysUseSoftware) {
1228 	int useindx = aa;
1229 	bool hardwareAccelerated = !alwaysUseSoftware && _G(gfxDriver)->HasAcceleratedTransform();
1230 
1231 	if (_GP(spriteset)[_G(objs)[aa].num] == nullptr)
1232 		quitprintf("There was an error drawing object %d. Its current sprite, %d, is invalid.", aa, _G(objs)[aa].num);
1233 
1234 	int coldept = _GP(spriteset)[_G(objs)[aa].num]->GetColorDepth();
1235 	int sprwidth = _GP(game).SpriteInfos[_G(objs)[aa].num].Width;
1236 	int sprheight = _GP(game).SpriteInfos[_G(objs)[aa].num].Height;
1237 
1238 	int tint_red, tint_green, tint_blue;
1239 	int tint_level, tint_light, light_level;
1240 	int zoom_level = 100;
1241 
1242 	// calculate the zoom level
1243 	if ((_G(objs)[aa].flags & OBJF_USEROOMSCALING) == 0) {
1244 		zoom_level = _G(objs)[aa].zoom;
1245 	} else {
1246 		int onarea = get_walkable_area_at_location(_G(objs)[aa].x, _G(objs)[aa].y);
1247 
1248 		if ((onarea <= 0) && (_GP(thisroom).WalkAreas[0].ScalingFar == 0)) {
1249 			// just off the edge of an area -- use the scaling we had
1250 			// while on the area
1251 			zoom_level = _G(objs)[aa].zoom;
1252 		} else
1253 			zoom_level = get_area_scaling(onarea, _G(objs)[aa].x, _G(objs)[aa].y);
1254 	}
1255 
1256 	if (zoom_level != 100)
1257 		scale_sprite_size(_G(objs)[aa].num, zoom_level, &sprwidth, &sprheight);
1258 	_G(objs)[aa].zoom = zoom_level;
1259 
1260 	// save width/height into parameters if requested
1261 	if (drawnWidth)
1262 		*drawnWidth = sprwidth;
1263 	if (drawnHeight)
1264 		*drawnHeight = sprheight;
1265 
1266 	_G(objs)[aa].last_width = sprwidth;
1267 	_G(objs)[aa].last_height = sprheight;
1268 
1269 	tint_red = tint_green = tint_blue = tint_level = tint_light = light_level = 0;
1270 
1271 	if (_G(objs)[aa].flags & OBJF_HASTINT) {
1272 		// object specific tint, use it
1273 		tint_red = _G(objs)[aa].tint_r;
1274 		tint_green = _G(objs)[aa].tint_g;
1275 		tint_blue = _G(objs)[aa].tint_b;
1276 		tint_level = _G(objs)[aa].tint_level;
1277 		tint_light = _G(objs)[aa].tint_light;
1278 		light_level = 0;
1279 	} else if (_G(objs)[aa].flags & OBJF_HASLIGHT) {
1280 		light_level = _G(objs)[aa].tint_light;
1281 	} else {
1282 		// get the ambient or region tint
1283 		int ignoreRegionTints = 1;
1284 		if (_G(objs)[aa].flags & OBJF_USEREGIONTINTS)
1285 			ignoreRegionTints = 0;
1286 
1287 		get_local_tint(_G(objs)[aa].x, _G(objs)[aa].y, ignoreRegionTints,
1288 		               &tint_level, &tint_red, &tint_green, &tint_blue,
1289 		               &tint_light, &light_level);
1290 	}
1291 
1292 	// check whether the image should be flipped
1293 	int isMirrored = 0;
1294 	if ((_G(objs)[aa].view != (uint16_t)-1) &&
1295 	        (_G(views)[_G(objs)[aa].view].loops[_G(objs)[aa].loop].frames[_G(objs)[aa].frame].pic == _G(objs)[aa].num) &&
1296 	        ((_G(views)[_G(objs)[aa].view].loops[_G(objs)[aa].loop].frames[_G(objs)[aa].frame].flags & VFLG_FLIPSPRITE) != 0)) {
1297 		isMirrored = 1;
1298 	}
1299 
1300 	if ((hardwareAccelerated) &&
1301 	        (_G(walkBehindMethod) != DrawOverCharSprite) &&
1302 	        (_G(objcache)[aa].image != nullptr) &&
1303 	        (_G(objcache)[aa].sppic == _G(objs)[aa].num) &&
1304 	        (_G(actsps)[useindx] != nullptr)) {
1305 		// HW acceleration
1306 		_G(objcache)[aa].tintamntwas = tint_level;
1307 		_G(objcache)[aa].tintredwas = tint_red;
1308 		_G(objcache)[aa].tintgrnwas = tint_green;
1309 		_G(objcache)[aa].tintbluwas = tint_blue;
1310 		_G(objcache)[aa].tintlightwas = tint_light;
1311 		_G(objcache)[aa].lightlevwas = light_level;
1312 		_G(objcache)[aa].zoomWas = zoom_level;
1313 		_G(objcache)[aa].mirroredWas = isMirrored;
1314 
1315 		return 1;
1316 	}
1317 
1318 	if ((!hardwareAccelerated) && (_G(gfxDriver)->HasAcceleratedTransform())) {
1319 		// They want to draw it in software mode with the D3D driver,
1320 		// so force a redraw
1321 		_G(objcache)[aa].sppic = -389538;
1322 	}
1323 
1324 	// If we have the image cached, use it
1325 	if ((_G(objcache)[aa].image != nullptr) &&
1326 	        (_G(objcache)[aa].sppic == _G(objs)[aa].num) &&
1327 	        (_G(objcache)[aa].tintamntwas == tint_level) &&
1328 	        (_G(objcache)[aa].tintlightwas == tint_light) &&
1329 	        (_G(objcache)[aa].tintredwas == tint_red) &&
1330 	        (_G(objcache)[aa].tintgrnwas == tint_green) &&
1331 	        (_G(objcache)[aa].tintbluwas == tint_blue) &&
1332 	        (_G(objcache)[aa].lightlevwas == light_level) &&
1333 	        (_G(objcache)[aa].zoomWas == zoom_level) &&
1334 	        (_G(objcache)[aa].mirroredWas == isMirrored)) {
1335 		// the image is the same, we can use it cached!
1336 		if ((_G(walkBehindMethod) != DrawOverCharSprite) &&
1337 		        (_G(actsps)[useindx] != nullptr))
1338 			return 1;
1339 		// Check if the X & Y co-ords are the same, too -- if so, there
1340 		// is scope for further optimisations
1341 		if ((_G(objcache)[aa].xwas == _G(objs)[aa].x) &&
1342 		        (_G(objcache)[aa].ywas == _G(objs)[aa].y) &&
1343 		        (_G(actsps)[useindx] != nullptr) &&
1344 		        (_G(walk_behind_baselines_changed) == 0))
1345 			return 1;
1346 		_G(actsps)[useindx] = recycle_bitmap(_G(actsps)[useindx], coldept, sprwidth, sprheight);
1347 		_G(actsps)[useindx]->Blit(_G(objcache)[aa].image, 0, 0, 0, 0, _G(objcache)[aa].image->GetWidth(), _G(objcache)[aa].image->GetHeight());
1348 		return 0;
1349 	}
1350 
1351 	// Not cached, so draw the image
1352 
1353 	int actspsUsed = 0;
1354 	if (!hardwareAccelerated) {
1355 		// draw the base sprite, scaled and flipped as appropriate
1356 		actspsUsed = scale_and_flip_sprite(useindx, coldept, zoom_level,
1357 		                                   _G(objs)[aa].num, sprwidth, sprheight, isMirrored);
1358 	} else {
1359 		// ensure actsps exists
1360 		_G(actsps)[useindx] = recycle_bitmap(_G(actsps)[useindx], coldept, _GP(game).SpriteInfos[_G(objs)[aa].num].Width, _GP(game).SpriteInfos[_G(objs)[aa].num].Height);
1361 	}
1362 
1363 	// direct read from source bitmap, where possible
1364 	Bitmap *comeFrom = nullptr;
1365 	if (!actspsUsed)
1366 		comeFrom = _GP(spriteset)[_G(objs)[aa].num];
1367 
1368 	// apply tints or lightenings where appropriate, else just copy
1369 	// the source bitmap
1370 	if (!hardwareAccelerated && ((tint_level > 0) || (light_level != 0))) {
1371 		apply_tint_or_light(useindx, light_level, tint_level, tint_red,
1372 		                    tint_green, tint_blue, tint_light, coldept,
1373 		                    comeFrom);
1374 	} else if (!actspsUsed) {
1375 		_G(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);
1376 	}
1377 
1378 	// Re-use the bitmap if it's the same size
1379 	_G(objcache)[aa].image = recycle_bitmap(_G(objcache)[aa].image, coldept, sprwidth, sprheight);
1380 	// Create the cached image and store it
1381 	_G(objcache)[aa].image->Blit(_G(actsps)[useindx], 0, 0, 0, 0, sprwidth, sprheight);
1382 	_G(objcache)[aa].sppic = _G(objs)[aa].num;
1383 	_G(objcache)[aa].tintamntwas = tint_level;
1384 	_G(objcache)[aa].tintredwas = tint_red;
1385 	_G(objcache)[aa].tintgrnwas = tint_green;
1386 	_G(objcache)[aa].tintbluwas = tint_blue;
1387 	_G(objcache)[aa].tintlightwas = tint_light;
1388 	_G(objcache)[aa].lightlevwas = light_level;
1389 	_G(objcache)[aa].zoomWas = zoom_level;
1390 	_G(objcache)[aa].mirroredWas = isMirrored;
1391 	return 0;
1392 }
1393 
1394 
1395 
1396 
1397 // This is only called from draw_screen_background, but it's seperated
1398 // to help with profiling the program
prepare_objects_for_drawing()1399 void prepare_objects_for_drawing() {
1400 	_G(our_eip) = 32;
1401 
1402 	for (int aa = 0; aa < _G(croom)->numobj; aa++) {
1403 		if (_G(objs)[aa].on != 1) continue;
1404 		// offscreen, don't draw
1405 		if ((_G(objs)[aa].x >= _GP(thisroom).Width) || (_G(objs)[aa].y < 1))
1406 			continue;
1407 
1408 		const int useindx = aa;
1409 		int tehHeight;
1410 		int actspsIntact = construct_object_gfx(aa, nullptr, &tehHeight, false);
1411 
1412 		// update the cache for next time
1413 		_G(objcache)[aa].xwas = _G(objs)[aa].x;
1414 		_G(objcache)[aa].ywas = _G(objs)[aa].y;
1415 		int atxp = data_to_game_coord(_G(objs)[aa].x);
1416 		int atyp = data_to_game_coord(_G(objs)[aa].y) - tehHeight;
1417 
1418 		int usebasel = _G(objs)[aa].get_baseline();
1419 
1420 		if (_G(objs)[aa].flags & OBJF_NOWALKBEHINDS) {
1421 			// ignore walk-behinds, do nothing
1422 			if (_G(walkBehindMethod) == DrawAsSeparateSprite) {
1423 				usebasel += _GP(thisroom).Height;
1424 			}
1425 		} else if (_G(walkBehindMethod) == DrawAsSeparateCharSprite) {
1426 			sort_out_char_sprite_walk_behind(useindx, atxp, atyp, usebasel, _G(objs)[aa].zoom, _G(objs)[aa].last_width, _G(objs)[aa].last_height);
1427 		} else if ((!actspsIntact) && (_G(walkBehindMethod) == DrawOverCharSprite)) {
1428 			sort_out_walk_behinds(_G(actsps)[useindx], atxp, atyp, usebasel);
1429 		}
1430 
1431 		if ((!actspsIntact) || (_G(actspsbmp)[useindx] == nullptr)) {
1432 			bool hasAlpha = (_GP(game).SpriteInfos[_G(objs)[aa].num].Flags & SPF_ALPHACHANNEL) != 0;
1433 
1434 			if (_G(actspsbmp)[useindx] != nullptr)
1435 				_G(gfxDriver)->DestroyDDB(_G(actspsbmp)[useindx]);
1436 			_G(actspsbmp)[useindx] = _G(gfxDriver)->CreateDDBFromBitmap(_G(actsps)[useindx], hasAlpha);
1437 		}
1438 
1439 		if (_G(gfxDriver)->HasAcceleratedTransform()) {
1440 			_G(actspsbmp)[useindx]->SetFlippedLeftRight(_G(objcache)[aa].mirroredWas != 0);
1441 			_G(actspsbmp)[useindx]->SetStretch(_G(objs)[aa].last_width, _G(objs)[aa].last_height);
1442 			_G(actspsbmp)[useindx]->SetTint(_G(objcache)[aa].tintredwas, _G(objcache)[aa].tintgrnwas, _G(objcache)[aa].tintbluwas, (_G(objcache)[aa].tintamntwas * 256) / 100);
1443 
1444 			if (_G(objcache)[aa].tintamntwas > 0) {
1445 				if (_G(objcache)[aa].tintlightwas == 0)  // luminance of 0 -- pass 1 to enable
1446 					_G(actspsbmp)[useindx]->SetLightLevel(1);
1447 				else if (_G(objcache)[aa].tintlightwas < 250)
1448 					_G(actspsbmp)[useindx]->SetLightLevel(_G(objcache)[aa].tintlightwas);
1449 				else
1450 					_G(actspsbmp)[useindx]->SetLightLevel(0);
1451 			} else if (_G(objcache)[aa].lightlevwas != 0)
1452 				_G(actspsbmp)[useindx]->SetLightLevel((_G(objcache)[aa].lightlevwas * 25) / 10 + 256);
1453 			else
1454 				_G(actspsbmp)[useindx]->SetLightLevel(0);
1455 		}
1456 
1457 		add_to_sprite_list(_G(actspsbmp)[useindx], atxp, atyp, usebasel, _G(objs)[aa].transparent, _G(objs)[aa].num);
1458 	}
1459 }
1460 
1461 
1462 
1463 // Draws srcimg onto destimg, tinting to the specified level
1464 // Totally overwrites the contents of the destination image
tint_image(Bitmap * ds,Bitmap * srcimg,int red,int grn,int blu,int light_level,int luminance)1465 void tint_image(Bitmap *ds, Bitmap *srcimg, int red, int grn, int blu, int light_level, int luminance) {
1466 
1467 	if ((srcimg->GetColorDepth() != ds->GetColorDepth()) ||
1468 	        (srcimg->GetColorDepth() <= 8)) {
1469 		debug_script_warn("Image tint failed - images must both be hi-color");
1470 		// the caller expects something to have been copied
1471 		ds->Blit(srcimg, 0, 0, 0, 0, srcimg->GetWidth(), srcimg->GetHeight());
1472 		return;
1473 	}
1474 
1475 	// For performance reasons, we have a seperate blender for
1476 	// when light is being adjusted and when it is not.
1477 	// If luminance >= 250, then normal brightness, otherwise darken
1478 	if (luminance >= 250)
1479 		set_blender_mode(kTintBlenderMode, red, grn, blu, 0);
1480 	else
1481 		set_blender_mode(kTintLightBlenderMode, red, grn, blu, 0);
1482 
1483 	if (light_level >= 100) {
1484 		// fully colourised
1485 		ds->FillTransparent();
1486 		ds->LitBlendBlt(srcimg, 0, 0, luminance);
1487 	} else {
1488 		// light_level is between -100 and 100 normally; 0-100 in
1489 		// this case when it's a RGB tint
1490 		light_level = (light_level * 25) / 10;
1491 
1492 		// Copy the image to the new bitmap
1493 		ds->Blit(srcimg, 0, 0, 0, 0, srcimg->GetWidth(), srcimg->GetHeight());
1494 		// Render the colourised image to a temporary bitmap,
1495 		// then transparently draw it over the original image
1496 		Bitmap *finaltarget = BitmapHelper::CreateTransparentBitmap(srcimg->GetWidth(), srcimg->GetHeight(), srcimg->GetColorDepth());
1497 		finaltarget->LitBlendBlt(srcimg, 0, 0, luminance);
1498 
1499 		// customized trans blender to preserve alpha channel
1500 		set_my_trans_blender(0, 0, 0, light_level);
1501 		ds->TransBlendBlt(finaltarget, 0, 0);
1502 		delete finaltarget;
1503 	}
1504 }
1505 
1506 
1507 
1508 
prepare_characters_for_drawing()1509 void prepare_characters_for_drawing() {
1510 	int zoom_level, newwidth, newheight, onarea, sppic;
1511 	int light_level, coldept;
1512 	int tint_red, tint_green, tint_blue, tint_amount, tint_light = 255;
1513 
1514 	_G(our_eip) = 33;
1515 
1516 	// draw characters
1517 	for (int aa = 0; aa < _GP(game).numcharacters; aa++) {
1518 		if (_GP(game).chars[aa].on == 0) continue;
1519 		if (_GP(game).chars[aa].room != _G(displayed_room)) continue;
1520 		_G(eip_guinum) = aa;
1521 		const int useindx = aa + MAX_ROOM_OBJECTS;
1522 
1523 		CharacterInfo *chin = &_GP(game).chars[aa];
1524 		_G(our_eip) = 330;
1525 		// if it's on but set to view -1, they're being silly
1526 		if (chin->view < 0) {
1527 			quitprintf("!The character '%s' was turned on in the current room (room %d) but has not been assigned a view number.",
1528 			           chin->name, _G(displayed_room));
1529 		}
1530 
1531 		if (chin->frame >= _G(views)[chin->view].loops[chin->loop].numFrames)
1532 			chin->frame = 0;
1533 
1534 		if ((chin->loop >= _G(views)[chin->view].numLoops) ||
1535 		        (_G(views)[chin->view].loops[chin->loop].numFrames < 1)) {
1536 			warning("The character '%s' could not be displayed because there were no frames in loop %d of view %d.",
1537 			           chin->name, chin->loop, chin->view + 1);
1538 			continue;
1539 		}
1540 
1541 		sppic = _G(views)[chin->view].loops[chin->loop].frames[chin->frame].pic;
1542 		if (sppic < 0)
1543 			sppic = 0;  // in case it's screwed up somehow
1544 		_G(our_eip) = 331;
1545 		// sort out the stretching if required
1546 		onarea = get_walkable_area_at_character(aa);
1547 		_G(our_eip) = 332;
1548 
1549 		// calculates the zoom level
1550 		if (chin->flags & CHF_MANUALSCALING)  // character ignores scaling
1551 			zoom_level = _G(charextra)[aa].zoom;
1552 		else if ((onarea <= 0) && (_GP(thisroom).WalkAreas[0].ScalingFar == 0)) {
1553 			zoom_level = _G(charextra)[aa].zoom;
1554 			// NOTE: room objects don't have this fix
1555 			if (zoom_level == 0)
1556 				zoom_level = 100;
1557 		} else
1558 			zoom_level = get_area_scaling(onarea, chin->x, chin->y);
1559 
1560 		_G(charextra)[aa].zoom = zoom_level;
1561 
1562 		tint_red = tint_green = tint_blue = tint_amount = tint_light = light_level = 0;
1563 
1564 		if (chin->flags & CHF_HASTINT) {
1565 			// object specific tint, use it
1566 			tint_red = _G(charextra)[aa].tint_r;
1567 			tint_green = _G(charextra)[aa].tint_g;
1568 			tint_blue = _G(charextra)[aa].tint_b;
1569 			tint_amount = _G(charextra)[aa].tint_level;
1570 			tint_light = _G(charextra)[aa].tint_light;
1571 			light_level = 0;
1572 		} else if (chin->flags & CHF_HASLIGHT) {
1573 			light_level = _G(charextra)[aa].tint_light;
1574 		} else {
1575 			get_local_tint(chin->x, chin->y, chin->flags & CHF_NOLIGHTING,
1576 			               &tint_amount, &tint_red, &tint_green, &tint_blue,
1577 			               &tint_light, &light_level);
1578 		}
1579 
1580 		_G(our_eip) = 3330;
1581 		int isMirrored = 0, specialpic = sppic;
1582 		bool usingCachedImage = false;
1583 
1584 		coldept = _GP(spriteset)[sppic]->GetColorDepth();
1585 
1586 		// adjust the sppic if mirrored, so it doesn't accidentally
1587 		// cache the mirrored frame as the real one
1588 		if (_G(views)[chin->view].loops[chin->loop].frames[chin->frame].flags & VFLG_FLIPSPRITE) {
1589 			isMirrored = 1;
1590 			specialpic = -sppic;
1591 		}
1592 
1593 		_G(our_eip) = 3331;
1594 
1595 		// if the character was the same sprite and scaling last time,
1596 		// just use the cached image
1597 		if ((_G(charcache)[aa].inUse) &&
1598 		        (_G(charcache)[aa].sppic == specialpic) &&
1599 		        (_G(charcache)[aa].scaling == zoom_level) &&
1600 		        (_G(charcache)[aa].tintredwas == tint_red) &&
1601 		        (_G(charcache)[aa].tintgrnwas == tint_green) &&
1602 		        (_G(charcache)[aa].tintbluwas == tint_blue) &&
1603 		        (_G(charcache)[aa].tintamntwas == tint_amount) &&
1604 		        (_G(charcache)[aa].tintlightwas == tint_light) &&
1605 		        (_G(charcache)[aa].lightlevwas == light_level)) {
1606 			if (_G(walkBehindMethod) == DrawOverCharSprite) {
1607 				_G(actsps)[useindx] = recycle_bitmap(_G(actsps)[useindx], _G(charcache)[aa].image->GetColorDepth(), _G(charcache)[aa].image->GetWidth(), _G(charcache)[aa].image->GetHeight());
1608 				_G(actsps)[useindx]->Blit(_G(charcache)[aa].image, 0, 0, 0, 0, _G(actsps)[useindx]->GetWidth(), _G(actsps)[useindx]->GetHeight());
1609 			} else {
1610 				usingCachedImage = true;
1611 			}
1612 		} else if ((_G(charcache)[aa].inUse) &&
1613 		           (_G(charcache)[aa].sppic == specialpic) &&
1614 		           (_G(gfxDriver)->HasAcceleratedTransform())) {
1615 			usingCachedImage = true;
1616 		} else if (_G(charcache)[aa].inUse) {
1617 			//destroy_bitmap (charcache[aa].image);
1618 			_G(charcache)[aa].inUse = 0;
1619 		}
1620 
1621 		_G(our_eip) = 3332;
1622 
1623 		if (zoom_level != 100) {
1624 			// it needs to be stretched, so calculate the new dimensions
1625 
1626 			scale_sprite_size(sppic, zoom_level, &newwidth, &newheight);
1627 			_G(charextra)[aa].width = newwidth;
1628 			_G(charextra)[aa].height = newheight;
1629 		} else {
1630 			// draw at original size, so just use the sprite width and height
1631 			// TODO: store width and height always, that's much simplier to use for reference!
1632 			_G(charextra)[aa].width = 0;
1633 			_G(charextra)[aa].height = 0;
1634 			newwidth = _GP(game).SpriteInfos[sppic].Width;
1635 			newheight = _GP(game).SpriteInfos[sppic].Height;
1636 		}
1637 
1638 		_G(our_eip) = 3336;
1639 
1640 		// Calculate the X & Y co-ordinates of where the sprite will be
1641 		const int atxp = (data_to_game_coord(chin->x)) - newwidth / 2;
1642 		const int atyp = (data_to_game_coord(chin->y) - newheight)
1643 		                 // adjust the Y positioning for the character's Z co-ord
1644 		                 - data_to_game_coord(chin->z);
1645 
1646 		_G(charcache)[aa].scaling = zoom_level;
1647 		_G(charcache)[aa].sppic = specialpic;
1648 		_G(charcache)[aa].tintredwas = tint_red;
1649 		_G(charcache)[aa].tintgrnwas = tint_green;
1650 		_G(charcache)[aa].tintbluwas = tint_blue;
1651 		_G(charcache)[aa].tintamntwas = tint_amount;
1652 		_G(charcache)[aa].tintlightwas = tint_light;
1653 		_G(charcache)[aa].lightlevwas = light_level;
1654 
1655 		// If cache needs to be re-drawn
1656 		if (!_G(charcache)[aa].inUse) {
1657 
1658 			// create the base sprite in actsps[useindx], which will
1659 			// be scaled and/or flipped, as appropriate
1660 			int actspsUsed = 0;
1661 			if (!_G(gfxDriver)->HasAcceleratedTransform()) {
1662 				actspsUsed = scale_and_flip_sprite(
1663 				                 useindx, coldept, zoom_level, sppic,
1664 				                 newwidth, newheight, isMirrored);
1665 			} else {
1666 				// ensure actsps exists
1667 				_G(actsps)[useindx] = recycle_bitmap(_G(actsps)[useindx], coldept, _GP(game).SpriteInfos[sppic].Width, _GP(game).SpriteInfos[sppic].Height);
1668 			}
1669 
1670 			_G(our_eip) = 335;
1671 
1672 			if (((light_level != 0) || (tint_amount != 0)) &&
1673 			        (!_G(gfxDriver)->HasAcceleratedTransform())) {
1674 				// apply the lightening or tinting
1675 				Bitmap *comeFrom = nullptr;
1676 				// if possible, direct read from the source image
1677 				if (!actspsUsed)
1678 					comeFrom = _GP(spriteset)[sppic];
1679 
1680 				apply_tint_or_light(useindx, light_level, tint_amount, tint_red,
1681 				                    tint_green, tint_blue, tint_light, coldept,
1682 				                    comeFrom);
1683 			} else if (!actspsUsed) {
1684 				// no scaling, flipping or tinting was done, so just blit it normally
1685 				_G(actsps)[useindx]->Blit(_GP(spriteset)[sppic], 0, 0, 0, 0, _G(actsps)[useindx]->GetWidth(), _G(actsps)[useindx]->GetHeight());
1686 			}
1687 
1688 			// update the character cache with the new image
1689 			_G(charcache)[aa].inUse = 1;
1690 			//_G(charcache)[aa].image = BitmapHelper::CreateBitmap_ (coldept, _G(actsps)[useindx]->GetWidth(), _G(actsps)[useindx]->GetHeight());
1691 			_G(charcache)[aa].image = recycle_bitmap(_G(charcache)[aa].image, coldept, _G(actsps)[useindx]->GetWidth(), _G(actsps)[useindx]->GetHeight());
1692 			_G(charcache)[aa].image->Blit(_G(actsps)[useindx], 0, 0, 0, 0, _G(actsps)[useindx]->GetWidth(), _G(actsps)[useindx]->GetHeight());
1693 
1694 		} // end if !cache.inUse
1695 
1696 		int usebasel = chin->get_baseline();
1697 
1698 		_G(our_eip) = 336;
1699 
1700 		const int bgX = atxp + chin->pic_xoffs;
1701 		const int bgY = atyp + chin->pic_yoffs;
1702 
1703 		if (chin->flags & CHF_NOWALKBEHINDS) {
1704 			// ignore walk-behinds, do nothing
1705 			if (_G(walkBehindMethod) == DrawAsSeparateSprite) {
1706 				usebasel += _GP(thisroom).Height;
1707 			}
1708 		} else if (_G(walkBehindMethod) == DrawAsSeparateCharSprite) {
1709 			sort_out_char_sprite_walk_behind(useindx, bgX, bgY, usebasel, _G(charextra)[aa].zoom, newwidth, newheight);
1710 		} else if (_G(walkBehindMethod) == DrawOverCharSprite) {
1711 			sort_out_walk_behinds(_G(actsps)[useindx], bgX, bgY, usebasel);
1712 		}
1713 
1714 		if ((!usingCachedImage) || (_G(actspsbmp)[useindx] == nullptr)) {
1715 			bool hasAlpha = (_GP(game).SpriteInfos[sppic].Flags & SPF_ALPHACHANNEL) != 0;
1716 
1717 			_G(actspsbmp)[useindx] = recycle_ddb_bitmap(_G(actspsbmp)[useindx], _G(actsps)[useindx], hasAlpha);
1718 		}
1719 
1720 		if (_G(gfxDriver)->HasAcceleratedTransform()) {
1721 			_G(actspsbmp)[useindx]->SetStretch(newwidth, newheight);
1722 			_G(actspsbmp)[useindx]->SetFlippedLeftRight(isMirrored != 0);
1723 			_G(actspsbmp)[useindx]->SetTint(tint_red, tint_green, tint_blue, (tint_amount * 256) / 100);
1724 
1725 			if (tint_amount != 0) {
1726 				if (tint_light == 0) // tint with 0 luminance, pass as 1 instead
1727 					_G(actspsbmp)[useindx]->SetLightLevel(1);
1728 				else if (tint_light < 250)
1729 					_G(actspsbmp)[useindx]->SetLightLevel(tint_light);
1730 				else
1731 					_G(actspsbmp)[useindx]->SetLightLevel(0);
1732 			} else if (light_level != 0)
1733 				_G(actspsbmp)[useindx]->SetLightLevel((light_level * 25) / 10 + 256);
1734 			else
1735 				_G(actspsbmp)[useindx]->SetLightLevel(0);
1736 
1737 		}
1738 
1739 		_G(our_eip) = 337;
1740 
1741 		chin->actx = atxp;
1742 		chin->acty = atyp;
1743 
1744 		add_to_sprite_list(_G(actspsbmp)[useindx], bgX, bgY, usebasel, chin->transparency, sppic);
1745 	}
1746 }
1747 
1748 
1749 // Compiles a list of room sprites (characters, objects, background)
prepare_room_sprites()1750 void prepare_room_sprites() {
1751 	// Background sprite is required for the non-software renderers always,
1752 	// and for software renderer in case there are overlapping viewports.
1753 	// Note that software DDB is just a tiny wrapper around bitmap, so overhead is negligible.
1754 	if (_G(roomBackgroundBmp) == nullptr) {
1755 		update_polled_stuff_if_runtime();
1756 		_G(roomBackgroundBmp) = _G(gfxDriver)->CreateDDBFromBitmap(_GP(thisroom).BgFrames[_GP(play).bg_frame].Graphic.get(), false, true);
1757 	} else if (_G(current_background_is_dirty)) {
1758 		update_polled_stuff_if_runtime();
1759 		_G(gfxDriver)->UpdateDDBFromBitmap(_G(roomBackgroundBmp), _GP(thisroom).BgFrames[_GP(play).bg_frame].Graphic.get(), false);
1760 	}
1761 	if (_G(gfxDriver)->RequiresFullRedrawEachFrame()) {
1762 		if (_G(current_background_is_dirty) || _G(walkBehindsCachedForBgNum) != _GP(play).bg_frame) {
1763 			if (_G(walkBehindMethod) == DrawAsSeparateSprite) {
1764 				update_walk_behind_images();
1765 			}
1766 		}
1767 		add_thing_to_draw(_G(roomBackgroundBmp), 0, 0, 0, false);
1768 	}
1769 	_G(current_background_is_dirty) = false; // Note this is only place where this flag is checked
1770 
1771 	clear_sprite_list();
1772 
1773 	if ((_G(debug_flags) & DBG_NOOBJECTS) == 0) {
1774 		prepare_objects_for_drawing();
1775 		prepare_characters_for_drawing();
1776 
1777 		if ((_G(debug_flags) & DBG_NODRAWSPRITES) == 0) {
1778 			_G(our_eip) = 34;
1779 			draw_sprite_list();
1780 		}
1781 	}
1782 	_G(our_eip) = 36;
1783 }
1784 
1785 // Draws the black surface behind (or rather between) the room viewports
draw_preroom_background()1786 void draw_preroom_background() {
1787 	if (_G(gfxDriver)->RequiresFullRedrawEachFrame())
1788 		return;
1789 	update_black_invreg_and_reset(_G(gfxDriver)->GetMemoryBackBuffer());
1790 }
1791 
1792 // Draws the room background on the given surface.
1793 //
1794 // NOTE that this is **strictly** for software rendering.
1795 // ds is a full game screen surface, and roomcam_surface is a surface for drawing room camera content to.
1796 // ds and roomcam_surface may be the same bitmap.
1797 // no_transform flag tells to copy dirty regions on roomcam_surface without any coordinate conversion
1798 // whatsoever.
draw_room_background(Viewport * view,const SpriteTransform & room_trans)1799 PBitmap draw_room_background(Viewport *view, const SpriteTransform &room_trans) {
1800 	_G(our_eip) = 31;
1801 
1802 	// For the sake of software renderer, if there is any kind of camera transform required
1803 	// except screen offset, we tell it to draw on separate bitmap first with zero transformation.
1804 	// There are few reasons for this, primary is that Allegro does not support StretchBlt
1805 	// between different colour depths (i.e. it won't correctly stretch blit 16-bit rooms to
1806 	// 32-bit virtual screen).
1807 	// Also see comment to ALSoftwareGraphicsDriver::RenderToBackBuffer().
1808 	const int view_index = view->GetID();
1809 	Bitmap *ds = _G(gfxDriver)->GetMemoryBackBuffer();
1810 	// If separate bitmap was prepared for this view/camera pair then use it, draw untransformed
1811 	// and blit transformed whole surface later.
1812 	const bool draw_to_camsurf = _GP(CameraDrawData)[view_index].Frame != nullptr;
1813 	Bitmap *roomcam_surface = draw_to_camsurf ? _GP(CameraDrawData)[view_index].Frame.get() : ds;
1814 	{
1815 		// For software renderer: copy dirty rects onto the virtual screen.
1816 		// TODO: that would be SUPER NICE to reorganize the code and move this operation into SoftwareGraphicDriver somehow.
1817 		// Because basically we duplicate sprite batch transform here.
1818 
1819 		auto camera = view->GetCamera();
1820 		set_invalidrects_cameraoffs(view_index, camera->GetRect().Left, camera->GetRect().Top);
1821 
1822 		// TODO: (by CJ)
1823 		// the following line takes up to 50% of the game CPU time at
1824 		// high resolutions and colour depths - if we can optimise it
1825 		// somehow, significant performance gains to be had
1826 		update_room_invreg_and_reset(view_index, roomcam_surface, _GP(thisroom).BgFrames[_GP(play).bg_frame].Graphic.get(), draw_to_camsurf);
1827 	}
1828 
1829 	return _GP(CameraDrawData)[view_index].Frame;
1830 }
1831 
1832 
draw_fps(const Rect & viewport)1833 void draw_fps(const Rect &viewport) {
1834 	// TODO: make allocated "fps struct" instead of using static vars!!
1835 	static IDriverDependantBitmap *ddb = nullptr;
1836 	static Bitmap *fpsDisplay = nullptr;
1837 	const int font = FONT_NORMAL;
1838 	if (fpsDisplay == nullptr) {
1839 		fpsDisplay = BitmapHelper::CreateBitmap(viewport.GetWidth(), (getfontheight_outlined(font) + get_fixed_pixel_size(5)), _GP(game).GetColorDepth());
1840 		fpsDisplay = ReplaceBitmapWithSupportedFormat(fpsDisplay);
1841 	}
1842 	fpsDisplay->ClearTransparent();
1843 
1844 	color_t text_color = fpsDisplay->GetCompatibleColor(14);
1845 
1846 	char base_buffer[20];
1847 	if (!isTimerFpsMaxed()) {
1848 		sprintf(base_buffer, "%d", _G(frames_per_second));
1849 	} else {
1850 		sprintf(base_buffer, "unlimited");
1851 	}
1852 
1853 	char fps_buffer[60];
1854 	// Don't display fps if we don't have enough information (because loop count was just reset)
1855 	if (!std::isUndefined(_G(fps))) {
1856 		snprintf(fps_buffer, sizeof(fps_buffer), "FPS: %2.1f / %s", _G(fps), base_buffer);
1857 	} else {
1858 		snprintf(fps_buffer, sizeof(fps_buffer), "FPS: --.- / %s", base_buffer);
1859 	}
1860 	wouttext_outline(fpsDisplay, 1, 1, font, text_color, fps_buffer);
1861 
1862 	char loop_buffer[60];
1863 	sprintf(loop_buffer, "Loop %u", _G(loopcounter));
1864 	wouttext_outline(fpsDisplay, viewport.GetWidth() / 2, 1, font, text_color, loop_buffer);
1865 
1866 	if (ddb)
1867 		_G(gfxDriver)->UpdateDDBFromBitmap(ddb, fpsDisplay, false);
1868 	else
1869 		ddb = _G(gfxDriver)->CreateDDBFromBitmap(fpsDisplay, false);
1870 	int yp = viewport.GetHeight() - fpsDisplay->GetHeight();
1871 	_G(gfxDriver)->DrawSprite(1, yp, ddb);
1872 	invalidate_sprite_glob(1, yp, ddb);
1873 }
1874 
1875 // Draw GUI and overlays of all kinds, anything outside the room space
draw_gui_and_overlays()1876 void draw_gui_and_overlays() {
1877 	int gg;
1878 
1879 	if (pl_any_want_hook(AGSE_PREGUIDRAW))
1880 		add_thing_to_draw(nullptr, AGSE_PREGUIDRAW, 0, TRANS_RUN_PLUGIN, false);
1881 
1882 	// draw overlays, except text boxes and portraits
1883 	for (gg = 0; gg < _G(numscreenover); gg++) {
1884 		// complete overlay draw in non-transparent mode
1885 		if (_G(screenover)[gg].type == OVER_COMPLETE)
1886 			add_thing_to_draw(_G(screenover)[gg].bmp, _G(screenover)[gg].x, _G(screenover)[gg].y, TRANS_OPAQUE, false);
1887 		else if (_G(screenover)[gg].type != OVER_TEXTMSG && _G(screenover)[gg].type != OVER_PICTURE) {
1888 			int tdxp, tdyp;
1889 			get_overlay_position(_G(screenover)[gg], &tdxp, &tdyp);
1890 			add_thing_to_draw(_G(screenover)[gg].bmp, tdxp, tdyp, 0, _G(screenover)[gg].hasAlphaChannel);
1891 		}
1892 	}
1893 
1894 	// Draw GUIs - they should always be on top of overlays like
1895 	// speech background text
1896 	_G(our_eip) = 35;
1897 	if (((_G(debug_flags) & DBG_NOIFACE) == 0) && (_G(displayed_room) >= 0)) {
1898 		int aa;
1899 
1900 		if (_G(playerchar)->activeinv >= MAX_INV) {
1901 			quit("!The player.activeinv variable has been corrupted, probably as a result\n"
1902 			     "of an incorrect assignment in the game script.");
1903 		}
1904 		if (_G(playerchar)->activeinv < 1)
1905 			_G(gui_inv_pic) = -1;
1906 		else
1907 			_G(gui_inv_pic) = _GP(game).invinfo[_G(playerchar)->activeinv].pic;
1908 		_G(our_eip) = 37;
1909 		{
1910 			for (aa = 0; aa < _GP(game).numgui; aa++) {
1911 				if (!_GP(guis)[aa].IsDisplayed()) continue;
1912 				if (!_GP(guis)[aa].HasChanged()) continue;
1913 				if (_GP(guis)[aa].Transparency == 255) continue;
1914 
1915 				_GP(guis)[aa].ClearChanged();
1916 				if (_G(guibg)[aa] == nullptr)
1917 					recreate_guibg_image(&_GP(guis)[aa]);
1918 
1919 				_G(eip_guinum) = aa;
1920 				_G(our_eip) = 370;
1921 				_G(guibg)[aa]->ClearTransparent();
1922 				_G(our_eip) = 372;
1923 				_GP(guis)[aa].DrawAt(_G(guibg)[aa], 0, 0);
1924 				_G(our_eip) = 373;
1925 
1926 				bool isAlpha = false;
1927 				if (_GP(guis)[aa].HasAlphaChannel()) {
1928 					isAlpha = true;
1929 
1930 					if ((_GP(game).options[OPT_NEWGUIALPHA] == kGuiAlphaRender_Legacy) && (_GP(guis)[aa].BgImage > 0)) {
1931 						// old-style (pre-3.0.2) GUI alpha rendering
1932 						repair_alpha_channel(_G(guibg)[aa], _GP(spriteset)[_GP(guis)[aa].BgImage]);
1933 					}
1934 				}
1935 
1936 				if (_G(guibgbmp)[aa] != nullptr) {
1937 					_G(gfxDriver)->UpdateDDBFromBitmap(_G(guibgbmp)[aa], _G(guibg)[aa], isAlpha);
1938 				} else {
1939 					_G(guibgbmp)[aa] = _G(gfxDriver)->CreateDDBFromBitmap(_G(guibg)[aa], isAlpha);
1940 				}
1941 				_G(our_eip) = 374;
1942 			}
1943 		}
1944 		_G(our_eip) = 38;
1945 		// Draw the GUIs
1946 		for (gg = 0; gg < _GP(game).numgui; gg++) {
1947 			aa = _GP(play).gui_draw_order[gg];
1948 			if (!_GP(guis)[aa].IsDisplayed()) continue;
1949 			if (_GP(guis)[aa].Transparency == 255) continue;
1950 
1951 			// Don't draw GUI if "GUIs Turn Off When Disabled"
1952 			if ((_GP(game).options[OPT_DISABLEOFF] == 3) &&
1953 			        (_G(all_buttons_disabled) > 0) &&
1954 			        (_GP(guis)[aa].PopupStyle != kGUIPopupNoAutoRemove))
1955 				continue;
1956 
1957 			add_thing_to_draw(_G(guibgbmp)[aa], _GP(guis)[aa].X, _GP(guis)[aa].Y, _GP(guis)[aa].Transparency, _GP(guis)[aa].HasAlphaChannel());
1958 
1959 			// only poll if the interface is enabled (mouseovers should not
1960 			// work while in Wait state)
1961 			if (IsInterfaceEnabled())
1962 				_GP(guis)[aa].Poll();
1963 		}
1964 	}
1965 
1966 	// draw speech and portraits (so that they appear over GUIs)
1967 	for (gg = 0; gg < _G(numscreenover); gg++) {
1968 		if (_G(screenover)[gg].type == OVER_TEXTMSG || _G(screenover)[gg].type == OVER_PICTURE) {
1969 			int tdxp, tdyp;
1970 			get_overlay_position(_G(screenover)[gg], &tdxp, &tdyp);
1971 			add_thing_to_draw(_G(screenover)[gg].bmp, tdxp, tdyp, 0, false);
1972 		}
1973 	}
1974 
1975 	_G(our_eip) = 1099;
1976 }
1977 
1978 // Push the gathered list of sprites into the active graphic renderer
put_sprite_list_on_screen(bool in_room)1979 void put_sprite_list_on_screen(bool in_room) {
1980 	// *** Draw the Things To Draw List ***
1981 
1982 	SpriteListEntry *thisThing;
1983 
1984 	for (size_t i = 0; i < _GP(thingsToDrawList).size(); ++i) {
1985 		thisThing = &_GP(thingsToDrawList)[i];
1986 
1987 		if (thisThing->bmp != nullptr) {
1988 			// mark the image's region as dirty
1989 			invalidate_sprite(thisThing->x, thisThing->y, thisThing->bmp, in_room);
1990 		} else if ((thisThing->transparent != TRANS_RUN_PLUGIN) &&
1991 		           (thisThing->bmp == nullptr)) {
1992 			quit("Null pointer added to draw list");
1993 		}
1994 
1995 		if (thisThing->bmp != nullptr) {
1996 			if (thisThing->transparent <= 255) {
1997 				thisThing->bmp->SetTransparency(thisThing->transparent);
1998 			}
1999 
2000 			_G(gfxDriver)->DrawSprite(thisThing->x, thisThing->y, thisThing->bmp);
2001 		} else if (thisThing->transparent == TRANS_RUN_PLUGIN) {
2002 			// meta entry to run the plugin hook
2003 			_G(gfxDriver)->DrawSprite(thisThing->x, thisThing->y, nullptr);
2004 		} else
2005 			quit("Unknown entry in draw list");
2006 	}
2007 
2008 	_G(our_eip) = 1100;
2009 }
2010 
GfxDriverNullSpriteCallback(int x,int y)2011 bool GfxDriverNullSpriteCallback(int x, int y) {
2012 	if (_G(displayed_room) < 0) {
2013 		// if no room loaded, various stuff won't be initialized yet
2014 		return 1;
2015 	}
2016 	return (pl_run_plugin_hooks(x, y) != 0);
2017 }
2018 
GfxDriverOnInitCallback(void * data)2019 void GfxDriverOnInitCallback(void *data) {
2020 	pl_run_plugin_init_gfx_hooks(_G(gfxDriver)->GetDriverID(), data);
2021 }
2022 
2023 // Schedule room rendering: background, objects, characters
construct_room_view()2024 static void construct_room_view() {
2025 	draw_preroom_background();
2026 	prepare_room_sprites();
2027 	// reset the Baselines Changed flag now that we've drawn stuff
2028 	_G(walk_behind_baselines_changed) = 0;
2029 
2030 	for (const auto &viewport : _GP(play).GetRoomViewportsZOrdered()) {
2031 		if (!viewport->IsVisible())
2032 			continue;
2033 		auto camera = viewport->GetCamera();
2034 		if (!camera)
2035 			continue;
2036 		const Rect &view_rc = _GP(play).GetRoomViewportAbs(viewport->GetID());
2037 		const Rect &cam_rc = camera->GetRect();
2038 		SpriteTransform room_trans(-cam_rc.Left, -cam_rc.Top,
2039 		                           (float)view_rc.GetWidth() / (float)cam_rc.GetWidth(),
2040 		                           (float)view_rc.GetHeight() / (float)cam_rc.GetHeight(),
2041 		                           0.f);
2042 		if (_G(gfxDriver)->RequiresFullRedrawEachFrame()) {
2043 			// we draw everything as a sprite stack
2044 			_G(gfxDriver)->BeginSpriteBatch(view_rc, room_trans, Point(0, _GP(play).shake_screen_yoff), (GlobalFlipType)_GP(play).screen_flipped);
2045 		} else {
2046 			if (_GP(CameraDrawData)[viewport->GetID()].Frame == nullptr && _GP(CameraDrawData)[viewport->GetID()].IsOverlap) {
2047 				// room background is prepended to the sprite stack
2048 				// TODO: here's why we have blit whole piece of background now:
2049 				// if we draw directly to the virtual screen overlapping another
2050 				// viewport, then we'd have to also mark and repaint every our
2051 				// region located directly over their dirty regions. That would
2052 				// require to update regions up the stack, converting their
2053 				// coordinates (cam1 -> screen -> cam2).
2054 				// It's not clear whether this is worth the effort, but if it is,
2055 				// then we'd need to optimise view/cam data first.
2056 				_G(gfxDriver)->BeginSpriteBatch(view_rc, room_trans);
2057 				_G(gfxDriver)->DrawSprite(0, 0, _G(roomBackgroundBmp));
2058 			} else {
2059 				// room background is drawn by dirty rects system
2060 				PBitmap bg_surface = draw_room_background(viewport.get(), room_trans);
2061 				_G(gfxDriver)->BeginSpriteBatch(view_rc, room_trans, Point(), kFlip_None, bg_surface);
2062 			}
2063 		}
2064 		put_sprite_list_on_screen(true);
2065 	}
2066 
2067 	clear_draw_list();
2068 }
2069 
2070 // Schedule ui rendering
construct_ui_view()2071 static void construct_ui_view() {
2072 	_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetUIViewportAbs(), SpriteTransform(), Point(0, _GP(play).shake_screen_yoff), (GlobalFlipType)_GP(play).screen_flipped);
2073 	draw_gui_and_overlays();
2074 	put_sprite_list_on_screen(false);
2075 	clear_draw_list();
2076 }
2077 
construct_game_scene(bool full_redraw)2078 void construct_game_scene(bool full_redraw) {
2079 	_G(gfxDriver)->ClearDrawLists();
2080 
2081 	if (_GP(play).fast_forward)
2082 		return;
2083 
2084 	_G(our_eip) = 3;
2085 
2086 	// React to changes to viewports and cameras (possibly from script) just before the render
2087 	_GP(play).UpdateViewports();
2088 
2089 	_G(gfxDriver)->UseSmoothScaling(IS_ANTIALIAS_SPRITES);
2090 	_G(gfxDriver)->RenderSpritesAtScreenResolution(_GP(usetup).RenderAtScreenRes, _GP(usetup).Supersampling);
2091 
2092 	pl_run_plugin_hooks(AGSE_PRERENDER, 0);
2093 
2094 	// Possible reasons to invalidate whole screen for the software renderer
2095 	if (full_redraw || _GP(play).screen_tint > 0 || _GP(play).shakesc_length > 0)
2096 		invalidate_screen();
2097 
2098 	// TODO: move to game update! don't call update during rendering pass!
2099 	// IMPORTANT: keep the order same because sometimes script may depend on it
2100 	if (_G(displayed_room) >= 0)
2101 		_GP(play).UpdateRoomCameras();
2102 
2103 	// Stage: room viewports
2104 	if (_GP(play).screen_is_faded_out == 0 && _GP(play).complete_overlay_on == 0) {
2105 		if (_G(displayed_room) >= 0) {
2106 			construct_room_view();
2107 		} else if (!_G(gfxDriver)->RequiresFullRedrawEachFrame()) {
2108 			// black it out so we don't get cursor trails
2109 			// TODO: this is possible to do with dirty rects system now too (it can paint black rects outside of room viewport)
2110 			_G(gfxDriver)->GetMemoryBackBuffer()->Fill(0);
2111 		}
2112 	}
2113 
2114 	_G(our_eip) = 4;
2115 
2116 	// Stage: UI overlay
2117 	if (_GP(play).screen_is_faded_out == 0) {
2118 		construct_ui_view();
2119 	}
2120 }
2121 
construct_game_screen_overlay(bool draw_mouse)2122 void construct_game_screen_overlay(bool draw_mouse) {
2123 	_G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(), SpriteTransform(), Point(0, _GP(play).shake_screen_yoff), (GlobalFlipType)_GP(play).screen_flipped);
2124 	if (pl_any_want_hook(AGSE_POSTSCREENDRAW))
2125 		_G(gfxDriver)->DrawSprite(AGSE_POSTSCREENDRAW, 0, nullptr);
2126 
2127 	// TODO: find out if it's okay to move cursor animation and state update
2128 	// to the update loop instead of doing it in the drawing routine
2129 	// update animating mouse cursor
2130 	if (_GP(game).mcurs[_G(cur_cursor)].view >= 0) {
2131 		ags_domouse(DOMOUSE_NOCURSOR);
2132 		// only on mousemove, and it's not moving
2133 		if (((_GP(game).mcurs[_G(cur_cursor)].flags & MCF_ANIMMOVE) != 0) &&
2134 		        (_G(mousex) == _G(lastmx)) && (_G(mousey) == _G(lastmy)));
2135 		// only on hotspot, and it's not on one
2136 		else if (((_GP(game).mcurs[_G(cur_cursor)].flags & MCF_HOTSPOT) != 0) &&
2137 		         (GetLocationType(game_to_data_coord(_G(mousex)), game_to_data_coord(_G(mousey))) == 0))
2138 			set_new_cursor_graphic(_GP(game).mcurs[_G(cur_cursor)].pic);
2139 		else if (_G(mouse_delay) > 0) _G(mouse_delay)--;
2140 		else {
2141 			int viewnum = _GP(game).mcurs[_G(cur_cursor)].view;
2142 			int loopnum = 0;
2143 			if (loopnum >= _G(views)[viewnum].numLoops)
2144 				quitprintf("An animating mouse cursor is using view %d which has no loops", viewnum + 1);
2145 			if (_G(views)[viewnum].loops[loopnum].numFrames < 1)
2146 				quitprintf("An animating mouse cursor is using view %d which has no frames in loop %d", viewnum + 1, loopnum);
2147 
2148 			_G(mouse_frame)++;
2149 			if (_G(mouse_frame) >= _G(views)[viewnum].loops[loopnum].numFrames)
2150 				_G(mouse_frame) = 0;
2151 			set_new_cursor_graphic(_G(views)[viewnum].loops[loopnum].frames[_G(mouse_frame)].pic);
2152 			_G(mouse_delay) = _G(views)[viewnum].loops[loopnum].frames[_G(mouse_frame)].speed + 5;
2153 			CheckViewFrame(viewnum, loopnum, _G(mouse_frame));
2154 		}
2155 		_G(lastmx) = _G(mousex);
2156 		_G(lastmy) = _G(mousey);
2157 	}
2158 
2159 	ags_domouse(DOMOUSE_NOCURSOR);
2160 
2161 	// Stage: mouse cursor
2162 	if (draw_mouse && !_GP(play).mouse_cursor_hidden && _GP(play).screen_is_faded_out == 0) {
2163 		_G(gfxDriver)->DrawSprite(_G(mousex) - _G(hotx), _G(mousey) - _G(hoty),
2164 		                          _G(mouseCursor));
2165 		invalidate_sprite(_G(mousex) - _G(hotx), _G(mousey) - _G(hoty), _G(mouseCursor), false);
2166 	}
2167 
2168 	if (_GP(play).screen_is_faded_out == 0) {
2169 		// Stage: screen fx
2170 		if (_GP(play).screen_tint >= 1)
2171 			_G(gfxDriver)->SetScreenTint(_GP(play).screen_tint & 0xff, (_GP(play).screen_tint >> 8) & 0xff, (_GP(play).screen_tint >> 16) & 0xff);
2172 		// Stage: legacy letterbox mode borders
2173 		render_black_borders();
2174 	}
2175 
2176 	if (_GP(play).screen_is_faded_out != 0 && _G(gfxDriver)->RequiresFullRedrawEachFrame()) {
2177 		const Rect &main_viewport = _GP(play).GetMainViewport();
2178 		_G(gfxDriver)->BeginSpriteBatch(main_viewport, SpriteTransform());
2179 		_G(gfxDriver)->SetScreenFade(_GP(play).fade_to_red, _GP(play).fade_to_green, _GP(play).fade_to_blue);
2180 	}
2181 }
2182 
construct_engine_overlay()2183 void construct_engine_overlay() {
2184 	const Rect &viewport = RectWH(_GP(game).GetGameRes());
2185 	_G(gfxDriver)->BeginSpriteBatch(viewport, SpriteTransform());
2186 
2187 	// draw the debug console, if appropriate
2188 	if ((_GP(play).debug_mode > 0) && (_G(display_console) != 0)) {
2189 		const int font = FONT_NORMAL;
2190 		int ypp = 1;
2191 		int txtspacing = getfontspacing_outlined(font);
2192 		int barheight = getheightoflines(font, DEBUG_CONSOLE_NUMLINES - 1) + 4;
2193 
2194 		if (_G(debugConsoleBuffer) == nullptr) {
2195 			_G(debugConsoleBuffer) = BitmapHelper::CreateBitmap(viewport.GetWidth(), barheight, _GP(game).GetColorDepth());
2196 			_G(debugConsoleBuffer) = ReplaceBitmapWithSupportedFormat(_G(debugConsoleBuffer));
2197 		}
2198 
2199 		color_t draw_color = _G(debugConsoleBuffer)->GetCompatibleColor(15);
2200 		_G(debugConsoleBuffer)->FillRect(Rect(0, 0, viewport.GetWidth() - 1, barheight), draw_color);
2201 		color_t text_color = _G(debugConsoleBuffer)->GetCompatibleColor(16);
2202 		for (int jj = _G(first_debug_line); jj != _G(last_debug_line); jj = (jj + 1) % DEBUG_CONSOLE_NUMLINES) {
2203 			wouttextxy(_G(debugConsoleBuffer), 1, ypp, font, text_color, _G(debug_line)[jj].GetCStr());
2204 			ypp += txtspacing;
2205 		}
2206 
2207 		if (_G(debugConsole) == nullptr)
2208 			_G(debugConsole) = _G(gfxDriver)->CreateDDBFromBitmap(_G(debugConsoleBuffer), false, true);
2209 		else
2210 			_G(gfxDriver)->UpdateDDBFromBitmap(_G(debugConsole), _G(debugConsoleBuffer), false);
2211 
2212 		_G(gfxDriver)->DrawSprite(0, 0, _G(debugConsole));
2213 		invalidate_sprite_glob(0, 0, _G(debugConsole));
2214 	}
2215 
2216 	if (_G(display_fps) != kFPS_Hide)
2217 		draw_fps(viewport);
2218 }
2219 
update_shakescreen()2220 static void update_shakescreen() {
2221 	// TODO: unify blocking and non-blocking shake update
2222 	_GP(play).shake_screen_yoff = 0;
2223 	if (_GP(play).shakesc_length > 0) {
2224 		if ((_G(loopcounter) % _GP(play).shakesc_delay) < (_GP(play).shakesc_delay / 2))
2225 			_GP(play).shake_screen_yoff = _GP(play).shakesc_amount;
2226 	}
2227 }
2228 
2229 // Draw everything
render_graphics(IDriverDependantBitmap * extraBitmap,int extraX,int extraY)2230 void render_graphics(IDriverDependantBitmap *extraBitmap, int extraX, int extraY) {
2231 	// Don't render if skipping cutscene
2232 	if (_GP(play).fast_forward)
2233 		return;
2234 	// Don't render if we've just entered new room and are before fade-in
2235 	// TODO: find out why this is not skipped for 8-bit games
2236 	if ((_G(in_new_room) > 0) & (_GP(game).color_depth > 1))
2237 		return;
2238 
2239 	// TODO: find out if it's okay to move shake to update function
2240 	update_shakescreen();
2241 
2242 	construct_game_scene(false);
2243 	_G(our_eip) = 5;
2244 	// NOTE: extraBitmap will always be drawn with the UI render stage
2245 	if (extraBitmap != nullptr) {
2246 		invalidate_sprite(extraX, extraY, extraBitmap, false);
2247 		_G(gfxDriver)->DrawSprite(extraX, extraY, extraBitmap);
2248 	}
2249 	construct_game_screen_overlay(true);
2250 	render_to_screen();
2251 
2252 	if (!SHOULD_QUIT && !_GP(play).screen_is_faded_out) {
2253 		// always update the palette, regardless of whether the plugin
2254 		// vetos the screen update
2255 		if (_G(bg_just_changed)) {
2256 			setpal();
2257 			_G(bg_just_changed) = 0;
2258 		}
2259 	}
2260 
2261 	_G(screen_is_dirty) = false;
2262 }
2263 
2264 } // namespace AGS3
2265