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