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