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/shared/gfx/bitmap.h"
24 #include "ags/engine/gfx/gfxfilter.h"
25 #include "ags/engine/gfx/gfx_driver_base.h"
26 #include "ags/engine/gfx/gfx_util.h"
27 
28 namespace AGS3 {
29 
30 using namespace AGS::Shared;
31 
32 namespace AGS {
33 namespace Engine {
34 
GraphicsDriverBase()35 GraphicsDriverBase::GraphicsDriverBase()
36 	: _pollingCallback(nullptr)
37 	, _drawScreenCallback(nullptr)
38 	, _nullSpriteCallback(nullptr)
39 	, _initGfxCallback(nullptr) {
40 	// Initialize default sprite batch, it will be used when no other batch was activated
41 	_actSpriteBatch = 0;
42 	_spriteBatchDesc.push_back(SpriteBatchDesc());
43 }
44 
IsModeSet() const45 bool GraphicsDriverBase::IsModeSet() const {
46 	return _mode.Width != 0 && _mode.Height != 0 && _mode.ColorDepth != 0;
47 }
48 
IsNativeSizeValid() const49 bool GraphicsDriverBase::IsNativeSizeValid() const {
50 	return !_srcRect.IsEmpty();
51 }
52 
IsRenderFrameValid() const53 bool GraphicsDriverBase::IsRenderFrameValid() const {
54 	return !_srcRect.IsEmpty() && !_dstRect.IsEmpty();
55 }
56 
GetDisplayMode() const57 DisplayMode GraphicsDriverBase::GetDisplayMode() const {
58 	return _mode;
59 }
60 
GetNativeSize() const61 Size GraphicsDriverBase::GetNativeSize() const {
62 	return _srcRect.GetSize();
63 }
64 
GetRenderDestination() const65 Rect GraphicsDriverBase::GetRenderDestination() const {
66 	return _dstRect;
67 }
68 
BeginSpriteBatch(const Rect & viewport,const SpriteTransform & transform,const Point offset,GlobalFlipType flip,PBitmap surface)69 void GraphicsDriverBase::BeginSpriteBatch(const Rect &viewport, const SpriteTransform &transform,
70         const Point offset, GlobalFlipType flip, PBitmap surface) {
71 	_actSpriteBatch++;
72 	_spriteBatchDesc.push_back(SpriteBatchDesc(viewport, transform, offset, flip, surface));
73 	InitSpriteBatch(_actSpriteBatch, _spriteBatchDesc[_actSpriteBatch]);
74 }
75 
ClearDrawLists()76 void GraphicsDriverBase::ClearDrawLists() {
77 	ResetAllBatches();
78 	_actSpriteBatch = 0;
79 	_spriteBatchDesc.resize(1);
80 }
81 
OnInit()82 void GraphicsDriverBase::OnInit() {
83 }
84 
OnUnInit()85 void GraphicsDriverBase::OnUnInit() {
86 }
87 
OnModeSet(const DisplayMode & mode)88 void GraphicsDriverBase::OnModeSet(const DisplayMode &mode) {
89 	_mode = mode;
90 }
91 
OnModeReleased()92 void GraphicsDriverBase::OnModeReleased() {
93 	_mode = DisplayMode();
94 	_dstRect = Rect();
95 }
96 
OnScalingChanged()97 void GraphicsDriverBase::OnScalingChanged() {
98 	PGfxFilter filter = GetGraphicsFilter();
99 	if (filter)
100 		_filterRect = filter->SetTranslation(_srcRect.GetSize(), _dstRect);
101 	else
102 		_filterRect = Rect();
103 	_scaling.Init(_srcRect.GetSize(), _dstRect);
104 }
105 
OnSetNativeRes(const GraphicResolution & native_res)106 void GraphicsDriverBase::OnSetNativeRes(const GraphicResolution &native_res) {
107 	_srcRect = RectWH(0, 0, native_res.Width, native_res.Height);
108 	_srcColorDepth = native_res.ColorDepth;
109 	OnScalingChanged();
110 
111 	// Adjust default sprite batch making it comply to native size
112 	_spriteBatchDesc[0].Viewport = RectWH(native_res);
113 	InitSpriteBatch(_actSpriteBatch, _spriteBatchDesc[_actSpriteBatch]);
114 }
115 
OnSetRenderFrame(const Rect & dst_rect)116 void GraphicsDriverBase::OnSetRenderFrame(const Rect &dst_rect) {
117 	_dstRect = dst_rect;
118 	OnScalingChanged();
119 }
120 
OnSetFilter()121 void GraphicsDriverBase::OnSetFilter() {
122 	_filterRect = GetGraphicsFilter()->SetTranslation(Size(_srcRect.GetSize()), _dstRect);
123 }
124 
125 
VideoMemoryGraphicsDriver()126 VideoMemoryGraphicsDriver::VideoMemoryGraphicsDriver()
127 	: _stageVirtualScreenDDB(nullptr)
128 	, _stageScreenDirty(false)
129 	, _fxIndex(0) {
130 	// Only to have something meaningful as default
131 	_vmem_a_shift_32 = 24;
132 	_vmem_r_shift_32 = 16;
133 	_vmem_g_shift_32 = 8;
134 	_vmem_b_shift_32 = 0;
135 }
136 
~VideoMemoryGraphicsDriver()137 VideoMemoryGraphicsDriver::~VideoMemoryGraphicsDriver() {
138 	DestroyAllStageScreens();
139 }
140 
UsesMemoryBackBuffer()141 bool VideoMemoryGraphicsDriver::UsesMemoryBackBuffer() {
142 	// Although we do use ours, we do not let engine draw upon it;
143 	// only plugin handling are allowed to request our mem buffer.
144 	// TODO: find better workaround?
145 	return false;
146 }
147 
GetMemoryBackBuffer()148 Bitmap *VideoMemoryGraphicsDriver::GetMemoryBackBuffer() {
149 	return nullptr;
150 }
151 
SetMemoryBackBuffer(Bitmap * backBuffer)152 void VideoMemoryGraphicsDriver::SetMemoryBackBuffer(Bitmap *backBuffer) { // do nothing, video-memory drivers don't use main back buffer, only stage bitmaps they pass to plugins
153 }
154 
GetStageBackBuffer(bool mark_dirty)155 Bitmap *VideoMemoryGraphicsDriver::GetStageBackBuffer(bool mark_dirty) {
156 	_stageScreenDirty |= mark_dirty;
157 	return _stageVirtualScreen.get();
158 }
159 
GetStageMatrixes(RenderMatrixes & rm)160 bool VideoMemoryGraphicsDriver::GetStageMatrixes(RenderMatrixes &rm) {
161 	rm = _stageMatrixes;
162 	return true;
163 }
164 
CreateDDBFromBitmap(Bitmap * bitmap,bool hasAlpha,bool opaque)165 IDriverDependantBitmap *VideoMemoryGraphicsDriver::CreateDDBFromBitmap(Bitmap *bitmap, bool hasAlpha, bool opaque) {
166 	IDriverDependantBitmap * ddb = CreateDDB(bitmap->GetWidth(), bitmap->GetHeight(), bitmap->GetColorDepth(), opaque);
167 	if (ddb)
168 		UpdateDDBFromBitmap(ddb, bitmap, hasAlpha);
169 	return ddb;
170 }
171 
CreateStageScreen(size_t index,const Size & sz)172 PBitmap VideoMemoryGraphicsDriver::CreateStageScreen(size_t index, const Size &sz) {
173 	if (_stageScreens.size() <= index)
174 		_stageScreens.resize(index + 1);
175 	if (sz.IsNull())
176 		_stageScreens[index].reset();
177 	else if (_stageScreens[index] == nullptr || _stageScreens[index]->GetSize() != sz)
178 		_stageScreens[index].reset(new Bitmap(sz.Width, sz.Height, _mode.ColorDepth));
179 	return _stageScreens[index];
180 }
181 
GetStageScreen(size_t index)182 PBitmap VideoMemoryGraphicsDriver::GetStageScreen(size_t index) {
183 	if (index < _stageScreens.size())
184 		return _stageScreens[index];
185 	return nullptr;
186 }
187 
DestroyAllStageScreens()188 void VideoMemoryGraphicsDriver::DestroyAllStageScreens() {
189 	if (_stageVirtualScreenDDB)
190 		this->DestroyDDB(_stageVirtualScreenDDB);
191 	_stageVirtualScreenDDB = nullptr;
192 
193 	for (size_t i = 0; i < _stageScreens.size(); ++i)
194 		_stageScreens[i].reset();
195 	_stageVirtualScreen.reset();
196 }
197 
DoNullSpriteCallback(int x,int y)198 bool VideoMemoryGraphicsDriver::DoNullSpriteCallback(int x, int y) {
199 	if (!_nullSpriteCallback)
200 		error("Unhandled attempt to draw null sprite");
201 	_stageScreenDirty = false;
202 	_stageVirtualScreen->ClearTransparent();
203 	// NOTE: this is not clear whether return value of callback may be
204 	// relied on. Existing plugins do not seem to return anything but 0,
205 	// even if they handle this event.
206 	_stageScreenDirty |= _nullSpriteCallback(x, y) != 0;
207 	if (_stageScreenDirty) {
208 		if (_stageVirtualScreenDDB)
209 			UpdateDDBFromBitmap(_stageVirtualScreenDDB, _stageVirtualScreen.get(), true);
210 		else
211 			_stageVirtualScreenDDB = CreateDDBFromBitmap(_stageVirtualScreen.get(), true);
212 		return true;
213 	}
214 	return false;
215 }
216 
MakeFx(int r,int g,int b)217 IDriverDependantBitmap *VideoMemoryGraphicsDriver::MakeFx(int r, int g, int b) {
218 	if (_fxIndex == _fxPool.size()) _fxPool.push_back(ScreenFx());
219 	ScreenFx &fx = _fxPool[_fxIndex];
220 	if (fx.DDB == nullptr) {
221 		fx.Raw = BitmapHelper::CreateBitmap(16, 16, _mode.ColorDepth);
222 		fx.DDB = CreateDDBFromBitmap(fx.Raw, false, true);
223 	}
224 	if (r != fx.Red || g != fx.Green || b != fx.Blue) {
225 		fx.Raw->Clear(makecol_depth(fx.Raw->GetColorDepth(), r, g, b));
226 		this->UpdateDDBFromBitmap(fx.DDB, fx.Raw, false);
227 		fx.Red = r;
228 		fx.Green = g;
229 		fx.Blue = b;
230 	}
231 	_fxIndex++;
232 	return fx.DDB;
233 }
234 
ResetFxPool()235 void VideoMemoryGraphicsDriver::ResetFxPool() {
236 	_fxIndex = 0;
237 }
238 
DestroyFxPool()239 void VideoMemoryGraphicsDriver::DestroyFxPool() {
240 	for (auto &fx : _fxPool) {
241 		if (fx.DDB)
242 			DestroyDDB(fx.DDB);
243 		delete fx.Raw;
244 	}
245 	_fxPool.clear();
246 	_fxIndex = 0;
247 }
248 
249 
250 #define algetr32(c) getr32(c)
251 #define algetg32(c) getg32(c)
252 #define algetb32(c) getb32(c)
253 #define algeta32(c) geta32(c)
254 
255 #define algetr16(c) getr16(c)
256 #define algetg16(c) getg16(c)
257 #define algetb16(c) getb16(c)
258 
259 #define algetr8(c)  getr8(c)
260 #define algetg8(c)  getg8(c)
261 #define algetb8(c)  getb8(c)
262 
263 
get_pixel_if_not_transparent8(const unsigned char * pixel,unsigned char * red,unsigned char * green,unsigned char * blue,unsigned char * divisor)264 __inline void get_pixel_if_not_transparent8(const unsigned char *pixel, unsigned char *red, unsigned char *green, unsigned char *blue, unsigned char *divisor) {
265 	if (pixel[0] != MASK_COLOR_8) {
266 		*red += algetr8(pixel[0]);
267 		*green += algetg8(pixel[0]);
268 		*blue += algetb8(pixel[0]);
269 		divisor[0]++;
270 	}
271 }
272 
get_pixel_if_not_transparent16(const unsigned short * pixel,unsigned short * red,unsigned short * green,unsigned short * blue,unsigned short * divisor)273 __inline void get_pixel_if_not_transparent16(const unsigned short *pixel, unsigned short *red, unsigned short *green, unsigned short *blue, unsigned short *divisor) {
274 	if (pixel[0] != MASK_COLOR_16) {
275 		*red += algetr16(pixel[0]);
276 		*green += algetg16(pixel[0]);
277 		*blue += algetb16(pixel[0]);
278 		divisor[0]++;
279 	}
280 }
281 
get_pixel_if_not_transparent32(const unsigned int * pixel,unsigned int * red,unsigned int * green,unsigned int * blue,unsigned int * divisor)282 __inline void get_pixel_if_not_transparent32(const unsigned int *pixel, unsigned int *red, unsigned int *green, unsigned int *blue, unsigned int *divisor) {
283 	if (pixel[0] != MASK_COLOR_32) {
284 		*red += algetr32(pixel[0]);
285 		*green += algetg32(pixel[0]);
286 		*blue += algetb32(pixel[0]);
287 		divisor[0]++;
288 	}
289 }
290 
291 
292 #define VMEMCOLOR_RGBA(r,g,b,a) \
293 	( (((a) & 0xFF) << _vmem_a_shift_32) | (((r) & 0xFF) << _vmem_r_shift_32) | (((g) & 0xFF) << _vmem_g_shift_32) | (((b) & 0xFF) << _vmem_b_shift_32) )
294 
295 
BitmapToVideoMem(const Bitmap * bitmap,const bool has_alpha,const TextureTile * tile,const VideoMemDDB * target,char * dst_ptr,const int dst_pitch,const bool usingLinearFiltering)296 void VideoMemoryGraphicsDriver::BitmapToVideoMem(const Bitmap *bitmap, const bool has_alpha, const TextureTile *tile, const VideoMemDDB *target,
297         char *dst_ptr, const int dst_pitch, const bool usingLinearFiltering) {
298 	const int src_depth = bitmap->GetColorDepth();
299 	bool lastPixelWasTransparent = false;
300 	for (int y = 0; y < tile->height; y++) {
301 		lastPixelWasTransparent = false;
302 		const uint8_t *scanline_before = bitmap->GetScanLine(y + tile->y - 1);
303 		const uint8_t *scanline_at = bitmap->GetScanLine(y + tile->y);
304 		const uint8_t *scanline_after = bitmap->GetScanLine(y + tile->y + 1);
305 		unsigned int *memPtrLong = (unsigned int *)dst_ptr;
306 
307 		for (int x = 0; x < tile->width; x++) {
308 			if (src_depth == 8) {
309 				const unsigned char *srcData = (const unsigned char *)&scanline_at[(x + tile->x) * sizeof(char)];
310 				if (*srcData == MASK_COLOR_8) {
311 					if (!usingLinearFiltering)
312 						memPtrLong[x] = 0;
313 					// set to transparent, but use the colour from the neighbouring
314 					// pixel to stop the linear filter doing black outlines
315 					else {
316 						unsigned char red = 0, green = 0, blue = 0, divisor = 0;
317 						if (x > 0)
318 							get_pixel_if_not_transparent8(&srcData[-1], &red, &green, &blue, &divisor);
319 						if (x < tile->width - 1)
320 							get_pixel_if_not_transparent8(&srcData[1], &red, &green, &blue, &divisor);
321 						if (y > 0)
322 							get_pixel_if_not_transparent8((const unsigned char *)&scanline_before[(x + tile->x) * sizeof(char)], &red, &green, &blue, &divisor);
323 						if (y < tile->height - 1)
324 							get_pixel_if_not_transparent8((const unsigned char *)&scanline_after[(x + tile->x) * sizeof(char)], &red, &green, &blue, &divisor);
325 						if (divisor > 0)
326 							memPtrLong[x] = VMEMCOLOR_RGBA(red / divisor, green / divisor, blue / divisor, 0);
327 						else
328 							memPtrLong[x] = 0;
329 					}
330 					lastPixelWasTransparent = true;
331 				} else {
332 					memPtrLong[x] = VMEMCOLOR_RGBA(algetr8(*srcData), algetg8(*srcData), algetb8(*srcData), 0xFF);
333 					if (lastPixelWasTransparent) {
334 						// update the colour of the previous tranparent pixel, to
335 						// stop black outlines when linear filtering
336 						memPtrLong[x - 1] = memPtrLong[x] & 0x00FFFFFF;
337 						lastPixelWasTransparent = false;
338 					}
339 				}
340 			} else if (src_depth == 16) {
341 				const unsigned short *srcData = (const unsigned short *)&scanline_at[(x + tile->x) * sizeof(short)];
342 				if (*srcData == MASK_COLOR_16) {
343 					if (!usingLinearFiltering)
344 						memPtrLong[x] = 0;
345 					// set to transparent, but use the colour from the neighbouring
346 					// pixel to stop the linear filter doing black outlines
347 					else {
348 						unsigned short red = 0, green = 0, blue = 0, divisor = 0;
349 						if (x > 0)
350 							get_pixel_if_not_transparent16(&srcData[-1], &red, &green, &blue, &divisor);
351 						if (x < tile->width - 1)
352 							get_pixel_if_not_transparent16(&srcData[1], &red, &green, &blue, &divisor);
353 						if (y > 0)
354 							get_pixel_if_not_transparent16((const unsigned short *)&scanline_before[(x + tile->x) * sizeof(short)], &red, &green, &blue, &divisor);
355 						if (y < tile->height - 1)
356 							get_pixel_if_not_transparent16((const unsigned short *)&scanline_after[(x + tile->x) * sizeof(short)], &red, &green, &blue, &divisor);
357 						if (divisor > 0)
358 							memPtrLong[x] = VMEMCOLOR_RGBA(red / divisor, green / divisor, blue / divisor, 0);
359 						else
360 							memPtrLong[x] = 0;
361 					}
362 					lastPixelWasTransparent = true;
363 				} else {
364 					memPtrLong[x] = VMEMCOLOR_RGBA(algetr16(*srcData), algetg16(*srcData), algetb16(*srcData), 0xFF);
365 					if (lastPixelWasTransparent) {
366 						// update the colour of the previous tranparent pixel, to
367 						// stop black outlines when linear filtering
368 						memPtrLong[x - 1] = memPtrLong[x] & 0x00FFFFFF;
369 						lastPixelWasTransparent = false;
370 					}
371 				}
372 			} else if (src_depth == 32) {
373 				const unsigned int *srcData = (const unsigned int *)&scanline_at[(x + tile->x) * sizeof(int)];
374 				if (*srcData == MASK_COLOR_32) {
375 					if (!usingLinearFiltering)
376 						memPtrLong[x] = 0;
377 					// set to transparent, but use the colour from the neighbouring
378 					// pixel to stop the linear filter doing black outlines
379 					else {
380 						unsigned int red = 0, green = 0, blue = 0, divisor = 0;
381 						if (x > 0)
382 							get_pixel_if_not_transparent32(&srcData[-1], &red, &green, &blue, &divisor);
383 						if (x < tile->width - 1)
384 							get_pixel_if_not_transparent32(&srcData[1], &red, &green, &blue, &divisor);
385 						if (y > 0)
386 							get_pixel_if_not_transparent32((const unsigned int *)&scanline_before[(x + tile->x) * sizeof(int)], &red, &green, &blue, &divisor);
387 						if (y < tile->height - 1)
388 							get_pixel_if_not_transparent32((const unsigned int *)&scanline_after[(x + tile->x) * sizeof(int)], &red, &green, &blue, &divisor);
389 						if (divisor > 0)
390 							memPtrLong[x] = VMEMCOLOR_RGBA(red / divisor, green / divisor, blue / divisor, 0);
391 						else
392 							memPtrLong[x] = 0;
393 					}
394 					lastPixelWasTransparent = true;
395 				} else if (has_alpha) {
396 					memPtrLong[x] = VMEMCOLOR_RGBA(algetr32(*srcData), algetg32(*srcData), algetb32(*srcData), algeta32(*srcData));
397 				} else {
398 					memPtrLong[x] = VMEMCOLOR_RGBA(algetr32(*srcData), algetg32(*srcData), algetb32(*srcData), 0xFF);
399 					if (lastPixelWasTransparent) {
400 						// update the colour of the previous tranparent pixel, to
401 						// stop black outlines when linear filtering
402 						memPtrLong[x - 1] = memPtrLong[x] & 0x00FFFFFF;
403 						lastPixelWasTransparent = false;
404 					}
405 				}
406 			}
407 		}
408 
409 		dst_ptr += dst_pitch;
410 	}
411 }
412 
BitmapToVideoMemOpaque(const Bitmap * bitmap,const bool has_alpha,const TextureTile * tile,const VideoMemDDB * target,char * dst_ptr,const int dst_pitch)413 void VideoMemoryGraphicsDriver::BitmapToVideoMemOpaque(const Bitmap *bitmap, const bool has_alpha, const TextureTile *tile, const VideoMemDDB *target,
414         char *dst_ptr, const int dst_pitch) {
415 	const int src_depth = bitmap->GetColorDepth();
416 	for (int y = 0; y < tile->height; y++) {
417 		const uint8_t *scanline_at = bitmap->GetScanLine(y + tile->y);
418 		unsigned int *memPtrLong = (unsigned int *)dst_ptr;
419 
420 		for (int x = 0; x < tile->width; x++) {
421 			if (src_depth == 8) {
422 				const unsigned char *srcData = (const unsigned char *)&scanline_at[(x + tile->x) * sizeof(char)];
423 				memPtrLong[x] = VMEMCOLOR_RGBA(algetr8(*srcData), algetg8(*srcData), algetb8(*srcData), 0xFF);
424 			} else if (src_depth == 16) {
425 				const unsigned short *srcData = (const unsigned short *)&scanline_at[(x + tile->x) * sizeof(short)];
426 				memPtrLong[x] = VMEMCOLOR_RGBA(algetr16(*srcData), algetg16(*srcData), algetb16(*srcData), 0xFF);
427 			} else if (src_depth == 32) {
428 				const unsigned int *srcData = (const unsigned int *)&scanline_at[(x + tile->x) * sizeof(int)];
429 				if (has_alpha)
430 					memPtrLong[x] = VMEMCOLOR_RGBA(algetr32(*srcData), algetg32(*srcData), algetb32(*srcData), algeta32(*srcData));
431 				else
432 					memPtrLong[x] = VMEMCOLOR_RGBA(algetr32(*srcData), algetg32(*srcData), algetb32(*srcData), 0xFF);
433 			}
434 		}
435 
436 		dst_ptr += dst_pitch;
437 	}
438 }
439 
440 } // namespace Engine
441 } // namespace AGS
442 } // namespace AGS3
443