1 /*--License:
2 Kyra Sprite Engine
3 Copyright Lee Thomason (Grinning Lizard Software) 2001-2005
4 www.grinninglizard.com/kyra
5 www.sourceforge.net/projects/kyra
6
7 Kyra is provided under the LGPL.
8
9 I kindly request you display a splash screen (provided in the HTML documentation)
10 to promote Kyra and acknowledge the software and everyone who has contributed to it,
11 but it is not required by the license.
12
13 --- LGPL License --
14
15 This library is free software; you can redistribute it and/or
16 modify it under the terms of the GNU Lesser General Public
17 License as published by the Free Software Foundation; either
18 version 2.1 of the License, or (at your option) any later version.
19
20 This library is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 Lesser General Public License for more details.
24
25 You should have received a copy of the GNU Lesser General Public
26 License along with this library; if not, write to the Free Software
27 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28
29 The full text of the license can be found in lgpl.txt
30 */
31
32 #include "canvas.h"
33 #include "engine.h"
34 #ifdef KYRA_SUPPORT_OPENGL
35 #include "SDL_opengl.h"
36 #include "ogltexture.h"
37 #endif
38 #include "canvasresource.h"
39 #include "../../grinliz/glgeometry.h"
40
41 #ifdef _MSC_VER
42 // Yes, we use 'this' in a member initialization list. Quote from
43 // Microsoft help: "This is a level-1 warning when Microsoft extensions
44 // are enabled (/Ze) and a level-4 warning otherwise." I'm sure that
45 // made sense to someone. -lee
46 #pragma warning ( disable : 4355 )
47 #endif
48
49 using namespace grinliz;
50
51
52 // Check some SDL stuff here:
53 #if !( SDL_VERSION_ATLEAST(1, 2, 0) )
54 #error Kyra requires SDL 1.2.0 or greater.
55 #endif
56
57
58 /*static*/ int KrEngine::maxOglTextureSize = 0;
59
KrEngine(SDL_Surface * _screen)60 KrEngine::KrEngine( SDL_Surface* _screen )
61 : paintInfo( _screen )
62 {
63 Rectangle2I bounds;
64 bounds.Set( 0, 0, _screen->w - 1, _screen->h - 1 );
65
66 Init( _screen, 1, &bounds, 0 );
67 }
68
69
KrEngine(SDL_Surface * _screen,const Rectangle2I & bounds,const KrRGBA * extra)70 KrEngine::KrEngine( SDL_Surface* _screen, const Rectangle2I& bounds, const KrRGBA* extra )
71 : paintInfo( _screen )
72 {
73 Init( _screen, 1, &bounds, extra );
74 }
75
76
KrEngine(SDL_Surface * _screen,int _nWindows,const Rectangle2I * bounds,const KrRGBA * extra)77 KrEngine::KrEngine( SDL_Surface* _screen, int _nWindows, const Rectangle2I* bounds, const KrRGBA* extra )
78 : paintInfo( _screen )
79 {
80 Init( _screen, _nWindows, bounds, extra );
81 }
82
83
~KrEngine()84 KrEngine::~KrEngine()
85 {
86 // Note that the tree must be deleted before the vault.
87 delete tree;
88 delete vault;
89 }
90
Restart(SDL_Surface * _screen,int _nWindows,const Rectangle2I * bounds,const KrRGBA * extra)91 void KrEngine::Restart( SDL_Surface* _screen,
92 int _nWindows,
93 const Rectangle2I* bounds,
94 const KrRGBA* extra )
95 {
96 GLASSERT(bounds);
97
98 KrPaintInfo paintNew(_screen);
99 memcpy(&paintInfo, &paintNew, sizeof(paintInfo));
100
101 screen = _screen;
102 nWindows = _nWindows;
103
104 windowBounds.Set( 0, 0, screen->w-1, screen->h-1 );
105 extraBackground.Set( 0, 0, 0, 255 );
106
107 // If this assert is thrown, increase KR_MAX_WINDOWS to an
108 // appropriate value and re-compile.
109 GLASSERT( nWindows <= KR_MAX_WINDOWS );
110
111 // fullScreenUpdate draws *outside* of the windows.
112 needFullScreenUpdate = ( extra != 0 );
113
114 if ( extra )
115 {
116 extraBackground = *extra;
117
118 if ( !paintInfo.OpenGL() )
119 {
120 U32 color = SDL_MapRGB( screen->format, extra->c.red, extra->c.green, extra->c.blue );
121 SDL_FillRect( screen, 0, color );
122 }
123 }
124
125 int i;
126 for( i=0; i<nWindows; ++i )
127 {
128 // Default to filling the background to black.
129 fillBackground[i] = true;
130 backgroundColor[i].Set( 0, 0, 0, 255 );
131
132 // Set the screenbounds to a window.
133 screenBounds[i] = bounds[i];
134
135 GLASSERT( bounds[i].min.x >= 0 );
136 GLASSERT( bounds[i].min.y >= 0 );
137 GLASSERT( bounds[i].max.x < screen->w );
138 GLASSERT( bounds[i].max.y < screen->h );
139
140 // Really aweful bugs result if the indivual windows
141 // aren't clipped to the screen.
142 screenBounds[i].DoClip( windowBounds );
143
144 // Initialize the DR to repaint everything and clip to the screen
145 // IMPORTANT: Set clip before adding any rectangles.
146 dirtyRectangle[i].SetClipping( screenBounds[i] );
147 dirtyRectangle[i].AddRectangle( screenBounds[i] );
148 }
149
150 // Check that none overlap.
151 #ifdef DEBUG
152 int j;
153 for( i=0; i<nWindows; ++i )
154 {
155 for( j=i+1; j<nWindows; ++j )
156 {
157 GLASSERT( !bounds[i].Intersect( bounds[j] ) );
158 }
159 }
160 #endif
161
162 if ( paintInfo.openGL )
163 InitOpenGL();
164
165 SDL_EnableUNICODE( true );
166 }
167
168
Init(SDL_Surface * _screen,int _nWindows,const Rectangle2I * bounds,const KrRGBA * extra)169 void KrEngine::Init( SDL_Surface* _screen,
170 int _nWindows,
171 const Rectangle2I* bounds,
172 const KrRGBA* extra )
173 {
174 splashStart = 0;
175 splashVault = 0;
176 splash = splashText = 0;
177
178 Restart(_screen, _nWindows, bounds, extra);
179
180 // Initialization of stuff that has "this" usage.
181 vault = new KrResourceVault;
182 tree = new KrImageTree( this );
183
184 //start out with about 64 rects, shouldn't need much more than that for most instances
185 // nNumSDLRects = 16;
186 // sdlRects = new SDL_Rect[16];
187 sdlRects.resize( 16 );
188 // memset( sdlRects.Memory(), 0, sizeof(SDL_Rect) * sdlRects.Count() );
189 }
190
191
InitOpenGL()192 void KrEngine::InitOpenGL()
193 {
194 #ifdef KYRA_SUPPORT_OPENGL
195 GLASSERT( KrTextureManager::TextureIndex() == 0 );
196
197 int w = windowBounds.Width();
198 int h = windowBounds.Height();
199 glViewport( 0, 0, w, h );
200 glClearColor( extraBackground.Redf(),
201 extraBackground.Greenf(),
202 extraBackground.Bluef(),
203 255.0f );
204
205 //glClearDepth(1.0);
206
207 // The depth buffer isn't actually used. This only occured to me at the end:
208 // Kyra sorts everything and draws in order...so you don't need the depth testing.
209 // Oddly...if you turn this on, it creates a bug in 16bit mode. I'de really
210 // like to understand that. -- lee
211 // glDepthFunc(GL_LESS);
212 // glEnable(GL_DEPTH_TEST); // disabled by default.
213
214 glDisable( GL_DEPTH_TEST ); // Don't depth test
215 glDepthMask( GL_FALSE ); // Don't write the depth buffer
216
217 // Done: after reading the above comment...we don't need the openGLZ parameter.
218 // Implemented ignoring the z-buffer.
219
220 glShadeModel(GL_FLAT); // Don't need smooth for 2D.
221
222 // Create and reset the projection matrix
223 glMatrixMode(GL_PROJECTION);
224
225 // This matrix is set up to map pixels to screen the same way the bitbuffer does.
226 // Trick from the OpenGL docs.
227
228 glLoadIdentity();
229 glOrtho( 0, w,
230 h, 0,
231 -1.0f, 1.0f );
232
233 glMatrixMode(GL_MODELVIEW);
234 glLoadIdentity();
235 glTranslatef(0.375, 0.375, 0.0);
236
237 // A more reasonable mode to start in.
238 glMatrixMode(GL_MODELVIEW);
239
240 //glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
241 glClear( GL_COLOR_BUFFER_BIT );
242
243 // Enable the texturing and the blending mode needed.
244 glEnable( GL_TEXTURE_2D );
245 glEnable( GL_BLEND );
246 glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
247
248 GLASSERT( glGetError() == GL_NO_ERROR );
249 #endif
250 }
251
252
FillBackground(const KrRGBA * fillColor)253 void KrEngine::FillBackground( const KrRGBA* fillColor )
254 {
255 for( int i=0; i<nWindows; ++i )
256 {
257 FillBackgroundWindow( i, fillColor );
258 }
259 }
260
261
FillBackgroundWindow(int i,const KrRGBA * fillColor)262 void KrEngine::FillBackgroundWindow( int i, const KrRGBA* fillColor )
263 {
264 if ( fillColor )
265 {
266 fillBackground[i] = true;
267 backgroundColor[i] = *fillColor;
268 //SDL_MapRGB( screen->format, fillColor->c.red, fillColor->c.green, fillColor->c.blue );
269 }
270 else
271 {
272 fillBackground[i] = false;
273 backgroundColor[i].Set( 0, 0, 0 );
274 }
275 dirtyRectangle[i].AddRectangle( screenBounds[i] );
276 }
277
278
279 //void KrEngine::ClearScreen( int red, int green, int blue )
280 //{
281 // U32 color = SDL_MapRGB( screen->format, red, green, blue );
282 // SDL_FillRect( screen, 0, color );
283 //
284 // SDL_UpdateRect( screen, 0, 0, 0, 0 );
285 // Tree()->Root()->Invalidate( KR_ALL_WINDOWS );
286 //}
287
288
289
290 #if defined( DRAWDEBUG_RLE ) || defined( DRAWDEBUG_BLTRECTS )
291 extern int debugFrameCounter;
292 #endif
293
294
UpdateScreen(std::vector<Rectangle2I> * rectArray)295 void KrEngine::UpdateScreen( std::vector< Rectangle2I >* rectArray )
296 {
297 if ( paintInfo.openGL )
298 {
299 SDL_GL_SwapBuffers();
300 }
301 else
302 {
303 if ( rectArray->size() == 0 )
304 return;
305
306 // if( rectArray->Count() > sdlRects.AllocatedSize() )
307 // {
308 // sdlRects.ResizePower2( rectArray->Count() );
309 //
310 // //resize the array, to next multiple of 8
311 // nNumSDLRects = rectArray->Count() + (8 - (rectArray->Count() % 8));
312 // sdlRects = new SDL_Rect[nNumSDLRects];
313 // memset(sdlRects, 0, sizeof(SDL_Rect) * nNumSDLRects);
314 // }
315
316 sdlRects.resize( rectArray->size() );
317 for( unsigned i=0; i<rectArray->size(); i++ )
318 {
319 sdlRects[i].x = rectArray->at( i ).min.x;
320 sdlRects[i].y = rectArray->at( i ).min.y;
321 sdlRects[i].w = rectArray->at( i ).Width();
322 sdlRects[i].h = rectArray->at( i ).Height();
323
324 GLASSERT( sdlRects[i].x >= 0 );
325 GLASSERT( sdlRects[i].y >= 0 );
326 GLASSERT( sdlRects[i].w > 0 && sdlRects[i].w <= screen->w );
327 GLASSERT( sdlRects[i].h > 0 && sdlRects[i].h <= screen->h );
328 GLASSERT( sdlRects[i].x + sdlRects[i].w <= screen->w );
329 GLASSERT( sdlRects[i].y + sdlRects[i].h <= screen->h );
330 }
331
332 // GLOUTPUT( "Updating %d rects\n", rectArray->Count() );
333 SDL_UpdateRects(screen, rectArray->size(), &sdlRects[0] );
334 }
335 }
336
337
Draw(bool updateRect,std::vector<Rectangle2I> * _rectangles)338 void KrEngine::Draw( bool updateRect, std::vector< Rectangle2I >* _rectangles )
339 {
340 std::vector< Rectangle2I > rectArrayOnStack;
341
342 #if defined( DRAWDEBUG_RLE ) || defined( DRAWDEBUG_BLTRECTS )
343 debugFrameCounter++;
344 #endif
345
346 // GLOUTPUT( "Engine::Draw Walk\n" );
347 tree->Walk();
348
349 // We either use the passed in rectangles,
350 // or the one here on the stack. Set the pointer
351 // rectArray to the right thing.
352 std::vector< Rectangle2I >* rectArray = ( _rectangles ) ? _rectangles : &rectArrayOnStack;
353 rectArray->resize(0);
354
355 if ( !paintInfo.openGL )
356 {
357 // Queue up the rectangles that will be used to blit to screen:
358
359 if ( needFullScreenUpdate )
360 {
361 needFullScreenUpdate = false;
362 Rectangle2I rect;
363 rect.Set( 0, 0, screen->w-1, screen->h-1 );
364 rectArray->push_back( rect );
365 }
366 else
367 {
368 for ( int i=0; i<nWindows; ++i )
369 {
370 for ( int j=0; j<dirtyRectangle[i].NumRect(); ++j )
371 {
372 rectArray->push_back( dirtyRectangle[i].Rect(j) );
373 }
374 }
375 }
376 }
377
378 if ( paintInfo.openGL )
379 {
380 #ifdef KYRA_SUPPORT_OPENGL
381
382 // OpenGL drawing
383 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
384
385 for( int j=0; j<nWindows; ++j )
386 {
387 if ( fillBackground[j] )
388 {
389 glBindTexture( GL_TEXTURE_2D, 0 );
390 glColor4f( backgroundColor[j].Redf(), backgroundColor[j].Greenf(), backgroundColor[j].Bluef(), 1.0f );
391 glBegin( GL_QUADS );
392 {
393 glVertex3i( screenBounds[j].min.x, screenBounds[j].min.y, 0 );
394 glVertex3i( screenBounds[j].min.x + screenBounds[j].Width(), screenBounds[j].min.y, 0 );
395 glVertex3i( screenBounds[j].min.x + screenBounds[j].Width(), screenBounds[j].min.y + screenBounds[j].Height(), 0 );
396 glVertex3i( screenBounds[j].min.x, screenBounds[j].min.y + screenBounds[j].Height(), 0 );
397 }
398 glEnd();
399 }
400
401 bool clipping = ( screenBounds[j] != windowBounds );
402
403 if ( clipping )
404 {
405 glEnable(GL_CLIP_PLANE0);
406 glEnable(GL_CLIP_PLANE1);
407 glEnable(GL_CLIP_PLANE2);
408 glEnable(GL_CLIP_PLANE3);
409
410 double plane0[4] = { 1.0, 0.0, 0.0, -screenBounds[j].min.x };
411 double plane1[4] = { -1.0, 0.0, 0.0, (screenBounds[j].min.x + screenBounds[j].Width() ) };
412 double plane2[4] = { 0.0, 1.0, 0.0, -screenBounds[j].min.y };
413 double plane3[4] = { 0.0, -1.0, 0.0, (screenBounds[j].min.y + screenBounds[j].Height() ) };
414
415 glClipPlane( GL_CLIP_PLANE0, plane0 );
416 glClipPlane( GL_CLIP_PLANE1, plane1 );
417 glClipPlane( GL_CLIP_PLANE2, plane2 );
418 glClipPlane( GL_CLIP_PLANE3, plane3 );
419 }
420
421 tree->DrawWalk( screenBounds[j], &paintInfo, j );
422
423 if ( clipping )
424 {
425 glDisable(GL_CLIP_PLANE0);
426 glDisable(GL_CLIP_PLANE1);
427 glDisable(GL_CLIP_PLANE2);
428 glDisable(GL_CLIP_PLANE3);
429 }
430 dirtyRectangle[j].Clear();
431 }
432 UpdateScreen( 0 );
433 #else
434 // No openGl support, but openGl surface used
435 GLASSERT( 0 );
436 #endif
437 }
438 else
439 {
440 // Bitmap drawing.
441
442 // Draw the background, if necessary. Then
443 // do a draw walk for every DR.
444 for( int win=0; win<nWindows; ++win )
445 {
446 for( int i=0; i<dirtyRectangle[win].NumRect(); ++i )
447 {
448 const Rectangle2I& rect = dirtyRectangle[win].Rect( i );
449
450 // Draw the background.
451 //GLASSERT( fillBackground[j] );
452 if ( fillBackground[win] )
453 {
454 SDL_Rect sdlrect = { rect.min.x, rect.min.y, rect.Width(), rect.Height() };
455 U32 sdlColor = SDL_MapRGB( screen->format, backgroundColor[win].c.red,
456 backgroundColor[win].c.green,
457 backgroundColor[win].c.blue );
458 //GLASSERT( sdlColor == 0 );
459 SDL_FillRect( screen, &sdlrect, sdlColor );
460 }
461 tree->DrawWalk( rect, &paintInfo, win );
462
463 /*
464 #ifdef DRAWDEBUG_BLTRECTS
465 KrPainter painter( &paintInfo );
466 painter.DrawBox( rect.xmin, rect.ymin, rect.Width(), rect.Height(), 200, 0, 0 );
467 #endif
468 */
469 }
470
471 #ifdef DRAWDEBUG_BLTRECTS
472 dirtyRectangle[win].DrawRects( screen );
473 #endif
474 dirtyRectangle[win].Clear();
475 }
476
477 // The windows and DRs have been walked. Now transfer to physical screen.
478 if ( updateRect )
479 {
480 // Use the composite list of rectangles.
481 UpdateScreen( rectArray );
482 }
483 }
484 }
485
486
QueryRenderDesc(std::string * desc)487 void KrEngine::QueryRenderDesc( std::string* desc )
488 {
489 QueryRenderDesc( screen, desc );
490 }
491
492
QueryRenderDesc(SDL_Surface * screen,std::string * desc)493 /* static */ void KrEngine::QueryRenderDesc( SDL_Surface* screen, std::string* desc )
494 {
495 char buf[ 256 ];
496 sprintf( buf, "v%d.%d.%d %dbbp Fullscreen=%d %s ",
497 KyraVersionMajor, KyraVersionMinor, KyraVersionBuild,
498 screen->format->BitsPerPixel,
499 ( screen->flags & SDL_FULLSCREEN ) ? 1 : 0,
500 #ifdef WIN32
501 "Win32"
502 #elif defined ( linux )
503 "Linux"
504 #else
505 "UnknownPlatform"
506 #endif
507 );
508
509 char render[256];
510 #ifdef KYRA_SUPPORT_OPENGL
511 if ( screen->flags & SDL_OPENGL )
512 {
513 const unsigned char* vendor = glGetString( GL_VENDOR );
514 const unsigned char* renderer = glGetString( GL_RENDERER );
515 const unsigned char* version = glGetString( GL_VERSION );
516 sprintf( render, "OpenGL render: Vendor: '%s' Renderer: '%s' Version: '%s'",
517 vendor, renderer, version );
518 } else
519 #endif
520 {
521 sprintf( render, "Software render" );
522 }
523 *desc = buf;
524 desc->append( render );
525 }
526
527
GetWindowFromPoint(int x,int y)528 int KrEngine::GetWindowFromPoint( int x, int y )
529 {
530 for( int i=0; i<nWindows; ++i )
531 {
532 if ( screenBounds[i].Intersect( x, y ) )
533 return i;
534 }
535 return -1;
536 }
537
538 /*
539 void KrEngine::StartSplash( U32 msec )
540 {
541 splashStart = msec;
542 GLASSERT( splashVault == 0 );
543
544 splashVault = new KrResourceVault();
545 splashVault->LoadDatFileFromMemory( splash_DAT, splash_SIZE );
546
547 KrSpriteResource* splashRes = splashVault->GetSpriteResource( "splash" );
548 KrSpriteResource* splashTextRes = splashVault->GetSpriteResource( "splashText" );
549
550 splash = new KrSprite( splashRes );
551 splashText = new KrSprite( splashTextRes );
552
553 Rectangle2I bounds, boundsText;
554 splash->QueryBoundingBox( &bounds, 0 );
555 splashText->QueryBoundingBox( &boundsText, 0 );
556
557 tree->AddNode( 0, splash );
558 tree->AddNode( 0, splashText );
559
560 splash->SetPos( screenBounds[0].Width() / 2 - bounds.Width() / 2,
561 screenBounds[0].Height() / 4 - bounds.Height() / 2 );
562 splash->SetZDepth( 5000 );
563
564 splashText->SetPos( screenBounds[0].Width() / 2 - boundsText.Width() / 2,
565 splash->Y() + bounds.Height() + 20 );
566
567 splashText->SetZDepth( 5000 );
568 }
569
570
571 bool KrEngine::UpdateSplash( U32 msec )
572 {
573 U32 delta = msec - splashStart;
574
575 KrColorTransform xcolor;
576
577 if ( delta < 1000 )
578 {
579 xcolor.SetAlpha( 255 * delta / 1000 );
580 }
581 // TODO - I got a crash here becayse the noded had already been deleted at the end of a splash screen cycle. This problem should be fixed properly in the caller, so this is a temporary hack - comment by CRC.
582 if (splash != 0)
583 splash->SetColor( xcolor );
584 if (splashText != 0)
585 splashText->SetColor( xcolor );
586
587 return ( delta >= 2000 );
588 }
589
590
591 void KrEngine::EndSplash()
592 {
593 if (splash != 0)
594 tree->DeleteNode( splash );
595 if (splashText != 0)
596 tree->DeleteNode( splashText );
597 delete splashVault;
598
599 splash = 0;
600 splashText = 0;
601 splashVault = 0;
602 }
603 */
604
605