1 /* GemRB - Infinity Engine Emulator
2  * Copyright (C) 2003 The GemRB Project
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  *
19  */
20 
21 #include "Video.h"
22 
23 #include "Interface.h"
24 #include "Palette.h"
25 #include "Sprite2D.h"
26 
27 #include <cmath>
28 
29 namespace GemRB {
30 
31 const TypeID Video::ID = { "Video" };
32 
33 static Color ApplyFlagsForColor(const Color& inCol, BlitFlags& flags);
34 
Video(void)35 Video::Video(void)
36 {
37 	drawingBuffer = NULL;
38 	EvntManager = NULL;
39 
40 	// Initialize gamma correction tables
41 	for (int i = 0; i < 256; i++) {
42 		Gamma22toGamma10[i] = (unsigned char)(0.5 + (pow (i/255.0, 2.2/1.0) * 255.0));
43 		Gamma10toGamma22[i] = (unsigned char)(0.5 + (pow (i/255.0, 1.0/2.2) * 255.0));
44 	}
45 
46 	// boring inits just to be extra clean
47 	bpp = 0;
48 	fullscreen = false;
49 	lastTime = 0;
50 }
51 
~Video(void)52 Video::~Video(void)
53 {
54 	DestroyBuffers();
55 }
56 
DestroyBuffers()57 void Video::DestroyBuffers()
58 {
59 	for (auto buffer : buffers) {
60 		delete buffer;
61 	}
62 }
63 
CreateDisplay(const Size & s,int bits,bool fs,const char * title)64 int Video::CreateDisplay(const Size& s, int bits, bool fs, const char* title)
65 {
66 	bpp = bits;
67 	screenSize = s;
68 
69 	int ret = CreateDriverDisplay(title);
70 	if (ret == GEM_OK) {
71 		SetScreenClip(NULL);
72 		if (fs) {
73 			ToggleFullscreenMode();
74 		}
75 	}
76 	return ret;
77 }
78 
ClippedDrawingRect(const Region & target,const Region * clip) const79 Region Video::ClippedDrawingRect(const Region& target, const Region* clip) const
80 {
81 	// clip to both screen and the target buffer
82 	Region bufRgn(Point(), drawingBuffer->Size());
83 	Region r = target.Intersect(screenClip).Intersect(bufRgn);
84 	if (clip) {
85 		// Intersect clip with both screen and target rectangle
86 		r = clip->Intersect(r);
87 	}
88 	// the clip must be "safe". no negative values or crashy crashy
89 	if (r.Dimensions().IsEmpty()) { // logically equivalent to no intersection
90 		r.h = 0;
91 		r.w = 0;
92 	}
93 	return r;
94 }
95 
CreateBuffer(const Region & r,BufferFormat fmt)96 VideoBufferPtr Video::CreateBuffer(const Region& r, BufferFormat fmt)
97 {
98 	VideoBuffer* buf = NewVideoBuffer(r, fmt);
99 	if (buf) {
100 		buffers.push_back(buf);
101 		return VideoBufferPtr(buffers.back(), [this](VideoBuffer* buffer) {
102 			DestroyBuffer(buffer);
103 		});
104 	}
105 	return nullptr;
106 	//assert(buf); // FIXME: we should probably deal with this happening
107 }
108 
DestroyBuffer(VideoBuffer * buffer)109 void Video::DestroyBuffer(VideoBuffer* buffer)
110 {
111 	// FIXME: this is poorly implemented
112 	VideoBuffers::iterator it = std::find(drawingBuffers.begin(), drawingBuffers.end(), buffer);
113 	if (it != drawingBuffers.end()) {
114 		drawingBuffers.erase(it);
115 	}
116 
117 	it = std::find(buffers.begin(), buffers.end(), buffer);
118 	if (it != buffers.end()) {
119 		buffers.erase(it);
120 	}
121 	delete buffer;
122 }
123 
PushDrawingBuffer(const VideoBufferPtr & buf)124 void Video::PushDrawingBuffer(const VideoBufferPtr& buf)
125 {
126 	assert(buf);
127 	drawingBuffers.push_back(buf.get());
128 	drawingBuffer = drawingBuffers.back();
129 }
130 
PopDrawingBuffer()131 void Video::PopDrawingBuffer()
132 {
133 	if (drawingBuffers.size() <= 1) {
134 		// can't pop last buffer
135 		return;
136 	}
137 	drawingBuffers.pop_back();
138 	drawingBuffer = drawingBuffers.back();
139 }
140 
SetStencilBuffer(const VideoBufferPtr & stencil)141 void Video::SetStencilBuffer(const VideoBufferPtr& stencil)
142 {
143 	stencilBuffer = stencil;
144 }
145 
SwapBuffers(unsigned int fpscap)146 int Video::SwapBuffers(unsigned int fpscap)
147 {
148 	SwapBuffers(drawingBuffers);
149 	drawingBuffers.clear();
150 	drawingBuffer = NULL;
151 	SetScreenClip(NULL);
152 
153 	if (fpscap) {
154 		unsigned int lim = 1000/fpscap;
155 		unsigned long time = GetTicks();
156 		if (( time - lastTime ) < lim) {
157 			Wait(lim - int(time - lastTime));
158 			time = GetTicks();
159 		}
160 		lastTime = time;
161 	} else {
162 		lastTime = GetTicks();
163 	}
164 
165 	return PollEvents();
166 }
167 
SetScreenClip(const Region * clip)168 void Video::SetScreenClip(const Region* clip)
169 {
170 	screenClip = Region(Point(), screenSize);
171 	if (clip) {
172 		screenClip = screenClip.Intersect(*clip);
173 	}
174 }
175 
ToggleFullscreenMode()176 bool Video::ToggleFullscreenMode()
177 {
178 	return SetFullscreenMode(!fullscreen);
179 }
180 
181 /** Set Event Manager */
SetEventMgr(EventMgr * evnt)182 void Video::SetEventMgr(EventMgr* evnt)
183 {
184 	//if 'evnt' is NULL then no Event Manager will be used
185 	EvntManager = evnt;
186 }
187 
188 // Flips given sprite according to the flags. If MirrorAnchor=true,
189 // flips its anchor (i.e. origin/base point) as well
190 // returns new sprite
MirrorSprite(const Holder<Sprite2D> sprite,BlitFlags flags,bool MirrorAnchor)191 Holder<Sprite2D> Video::MirrorSprite(const Holder<Sprite2D> sprite, BlitFlags flags, bool MirrorAnchor)
192 {
193 	if (!sprite)
194 		return NULL;
195 
196 	Holder<Sprite2D> dest = sprite->copy();
197 
198 	if (flags&BlitFlags::MIRRORX) {
199 		dest->renderFlags ^= BlitFlags::MIRRORX;
200 		if (MirrorAnchor)
201 			dest->Frame.x = sprite->Frame.w - sprite->Frame.x;
202 	}
203 
204 	if (flags&BlitFlags::MIRRORY) {
205 		dest->renderFlags ^= BlitFlags::MIRRORY;
206 		if (MirrorAnchor)
207 			dest->Frame.y = sprite->Frame.h - sprite->Frame.y;
208 	}
209 
210 	return dest;
211 }
212 
213 /** Get the fullscreen mode */
GetFullscreenMode() const214 bool Video::GetFullscreenMode() const
215 {
216 	return fullscreen;
217 }
218 
BlitSprite(const Holder<Sprite2D> spr,Point p,const Region * clip)219 void Video::BlitSprite(const Holder<Sprite2D> spr, Point p, const Region* clip)
220 {
221 	p -= spr->Frame.Origin();
222 	Region dst(p, spr->Frame.Dimensions());
223 	Region fClip = ClippedDrawingRect(dst, clip);
224 
225 	if (fClip.Dimensions().IsEmpty()) {
226 		return; // already know blit fails
227 	}
228 
229 	Region src(0, 0, spr->Frame.w, spr->Frame.h);
230 	// adjust the src region to account for the clipping
231 	src.x += fClip.x - dst.x; // the left edge
232 	src.w -= dst.w - fClip.w; // the right edge
233 	src.y += fClip.y - dst.y; // the top edge
234 	src.h -= dst.h - fClip.h; // the bottom edge
235 
236 	assert(src.w == fClip.w && src.h == fClip.h);
237 
238 	// just pass fclip as dst
239 	// since the next stage is also public, we must readd the Pos becuase it will again be removed
240 	fClip.x += spr->Frame.x;
241 	fClip.y += spr->Frame.y;
242 	BlitSprite(spr, src, fClip, BlitFlags::BLENDED);
243 }
244 
BlitGameSpriteWithPalette(Holder<Sprite2D> spr,PaletteHolder pal,const Point & p,BlitFlags flags,Color tint)245 void Video::BlitGameSpriteWithPalette(Holder<Sprite2D> spr, PaletteHolder pal, const Point& p,
246 									  BlitFlags flags, Color tint)
247 {
248 	if (pal) {
249 		PaletteHolder oldpal = spr->GetPalette();
250 		spr->SetPalette(pal);
251 		BlitGameSprite(spr, p, flags, tint);
252 		spr->SetPalette(oldpal);
253 	} else {
254 		BlitGameSprite(spr, p, flags, tint);
255 	}
256 }
257 
SpriteScaleDown(const Holder<Sprite2D> sprite,unsigned int ratio)258 Holder<Sprite2D> Video::SpriteScaleDown( const Holder<Sprite2D> sprite, unsigned int ratio )
259 {
260 	Region scaledFrame = sprite->Frame;
261 	scaledFrame.w /= ratio;
262 	scaledFrame.h /= ratio;
263 
264 	unsigned int* pixels = (unsigned int *) malloc( scaledFrame.w * scaledFrame.h * 4 );
265 	int i = 0;
266 
267 	for (int y = 0; y < scaledFrame.h; y++) {
268 		for (int x = 0; x < scaledFrame.w; x++) {
269 			Color c = SpriteGetPixelSum( sprite, x, y, ratio );
270 
271 			*(pixels + i++) = c.r + (c.g << 8) + (c.b << 16) + (c.a << 24);
272 		}
273 	}
274 
275 	Holder<Sprite2D> small = CreateSprite(scaledFrame, 32, 0x000000ff, 0x0000ff00, 0x00ff0000,
276 0xff000000, pixels, false, 0 );
277 
278 	small->Frame.x = sprite->Frame.x / ratio;
279 	small->Frame.y = sprite->Frame.y / ratio;
280 
281 	return small;
282 }
283 
284 //TODO light could be elliptical in the original engine
285 //is it difficult?
CreateLight(int radius,int intensity)286 Holder<Sprite2D> Video::CreateLight(int radius, int intensity)
287 {
288 	if(!radius) return NULL;
289 	Point p, q;
290 	int a;
291 	void* pixels = malloc( radius * radius * 4 * 4);
292 	int i = 0;
293 
294 	for (p.y = -radius; p.y < radius; p.y++) {
295 		for (p.x = -radius; p.x < radius; p.x++) {
296 			a = intensity*(radius-(signed) Distance(p,q))/radius;
297 
298 			if(a<0) a=0;
299 			else if(a>255) a = 255;
300 
301 			*((unsigned int*)pixels + i++) = 0xffffff + ((a/2) << 24);
302 		}
303 	}
304 
305 	Holder<Sprite2D> light = CreateSprite(Region(0,0, radius*2, radius*2), 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000, pixels);
306 
307 	light->Frame.x = radius;
308 	light->Frame.y = radius;
309 
310 	return light;
311 }
312 
SpriteGetPixelSum(const Holder<Sprite2D> sprite,unsigned short xbase,unsigned short ybase,unsigned int ratio)313 Color Video::SpriteGetPixelSum(const Holder<Sprite2D> sprite, unsigned short xbase, unsigned short ybase, unsigned int ratio)
314 {
315 	// TODO: turn this into one of our software "shaders"
316 	Color sum;
317 	unsigned int count = ratio*ratio;
318 	unsigned int r=0, g=0, b=0, a=0;
319 
320 	for (unsigned int x = 0; x < ratio; x++) {
321 		for (unsigned int y = 0; y < ratio; y++) {
322 			Color c = sprite->GetPixel( xbase*ratio+x, ybase*ratio+y );
323 			r += Gamma22toGamma10[c.r];
324 			g += Gamma22toGamma10[c.g];
325 			b += Gamma22toGamma10[c.b];
326 			a += Gamma22toGamma10[c.a];
327 		}
328 	}
329 
330 	sum.r = Gamma10toGamma22[r / count];
331 	sum.g = Gamma10toGamma22[g / count];
332 	sum.b = Gamma10toGamma22[b / count];
333 	sum.a = Gamma10toGamma22[a / count];
334 
335 	return sum;
336 }
337 
ApplyFlagsForColor(const Color & inCol,BlitFlags & flags)338 Color ApplyFlagsForColor(const Color& inCol, BlitFlags& flags)
339 {
340 	Color outC = inCol;
341 	if (flags & BlitFlags::HALFTRANS) {
342 		// set exactly to 128 because it is an optimized value
343 		// if we end up needing to do half of something already transparent we can change this
344 		// or do the calculations before calling the video driver and dont pass BlitFlags::HALFTRANS
345 		outC.a = 128;
346 	}
347 
348 	// TODO: do we need to handle BlitFlags::GREY, BlitFlags::SEPIA, or BlitFlags::COLOR_MOD?
349 	// if so we should do that here instead of in the implementations
350 
351 	if (flags & BlitFlags::GREY) {
352 		//static RGBBlendingPipeline<GREYSCALE, true> blender;
353 	} else if (flags & BlitFlags::SEPIA) {
354 		//static RGBBlendingPipeline<SEPIA, true> blender;
355 	}
356 
357 	if (flags & BlitFlags::COLOR_MOD) {
358 		flags |= BlitFlags::MULTIPLY;
359 	}
360 
361 	// clear handled flags
362 	flags &= ~(BlitFlags::HALFTRANS|BlitFlags::GREY|BlitFlags::SEPIA|BlitFlags::COLOR_MOD);
363 	return outC;
364 }
365 
DrawRect(const Region & rgn,const Color & color,bool fill,BlitFlags flags)366 void Video::DrawRect(const Region& rgn, const Color& color, bool fill, BlitFlags flags)
367 {
368 	Color c = ApplyFlagsForColor(color, flags);
369 	DrawRectImp(rgn, c, fill, flags);
370 }
371 
DrawPoint(const Point & p,const Color & color,BlitFlags flags)372 void Video::DrawPoint(const Point& p, const Color& color, BlitFlags flags)
373 {
374 	Color c = ApplyFlagsForColor(color, flags);
375 	DrawPointImp(p, c, flags);
376 }
377 
DrawPoints(const std::vector<Point> & points,const Color & color,BlitFlags flags)378 void Video::DrawPoints(const std::vector<Point>& points, const Color& color, BlitFlags flags)
379 {
380 	Color c = ApplyFlagsForColor(color, flags);
381 	DrawPointsImp(points, c, flags);
382 }
383 
DrawCircle(const Point & origin,unsigned short r,const Color & color,BlitFlags flags)384 void Video::DrawCircle(const Point& origin, unsigned short r, const Color& color, BlitFlags flags)
385 {
386 	Color c = ApplyFlagsForColor(color, flags);
387 	DrawCircleImp(origin, r, c, flags);
388 }
389 
DrawEllipseSegment(const Point & origin,unsigned short xr,unsigned short yr,const Color & color,double anglefrom,double angleto,bool drawlines,BlitFlags flags)390 void Video::DrawEllipseSegment(const Point& origin, unsigned short xr, unsigned short yr, const Color& color,
391 								double anglefrom, double angleto, bool drawlines, BlitFlags flags)
392 {
393 	Color c = ApplyFlagsForColor(color, flags);
394 	DrawEllipseSegmentImp(origin, xr, yr, c, anglefrom, angleto, drawlines, flags);
395 }
396 
DrawEllipse(const Point & origin,unsigned short xr,unsigned short yr,const Color & color,BlitFlags flags)397 void Video::DrawEllipse(const Point& origin, unsigned short xr, unsigned short yr, const Color& color, BlitFlags flags)
398 {
399 	Color c = ApplyFlagsForColor(color, flags);
400 	DrawEllipseImp(origin, xr, yr, c, flags);
401 }
402 
DrawPolygon(const Gem_Polygon * poly,const Point & origin,const Color & color,bool fill,BlitFlags flags)403 void Video::DrawPolygon(const Gem_Polygon* poly, const Point& origin, const Color& color, bool fill, BlitFlags flags)
404 {
405 	Color c = ApplyFlagsForColor(color, flags);
406 	DrawPolygonImp(poly, origin, c, fill, flags);
407 }
408 
DrawLine(const Point & p1,const Point & p2,const Color & color,BlitFlags flags)409 void Video::DrawLine(const Point& p1, const Point& p2, const Color& color, BlitFlags flags)
410 {
411 	Color c = ApplyFlagsForColor(color, flags);
412 	DrawLineImp(p1, p2, c, flags);
413 }
414 
DrawLines(const std::vector<Point> & points,const Color & color,BlitFlags flags)415 void Video::DrawLines(const std::vector<Point>& points, const Color& color, BlitFlags flags)
416 {
417 	Color c = ApplyFlagsForColor(color, flags);
418 	DrawLinesImp(points, c, flags);
419 }
420 
421 }
422