1 //******************************************************************************
2 ///
3 /// @file unix/disp_sdl.cpp
4 ///
5 /// SDL (Simple direct media layer) based render display system.
6 ///
7 /// @author Christoph Hormann <chris_hormann@gmx.de>
8 ///
9 /// @copyright
10 /// @parblock
11 ///
12 /// Persistence of Vision Ray Tracer ('POV-Ray') version 3.8.
13 /// Copyright 1991-2018 Persistence of Vision Raytracer Pty. Ltd.
14 ///
15 /// POV-Ray is free software: you can redistribute it and/or modify
16 /// it under the terms of the GNU Affero General Public License as
17 /// published by the Free Software Foundation, either version 3 of the
18 /// License, or (at your option) any later version.
19 ///
20 /// POV-Ray 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
23 /// GNU Affero General Public License for more details.
24 ///
25 /// You should have received a copy of the GNU Affero General Public License
26 /// along with this program.  If not, see <http://www.gnu.org/licenses/>.
27 ///
28 /// ----------------------------------------------------------------------------
29 ///
30 /// POV-Ray is based on the popular DKB raytracer version 2.12.
31 /// DKBTrace was originally written by David K. Buck.
32 /// DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
33 ///
34 /// @endparblock
35 ///
36 //*******************************************************************************
37 
38 #include "config.h"
39 
40 #ifdef HAVE_LIBSDL
41 
42 #include "disp_sdl.h"
43 
44 #include <algorithm>
45 
46 // this must be the last file included
47 #include "syspovdebug.h"
48 
49 
50 namespace pov_frontend
51 {
52     using namespace vfe;
53     using namespace vfePlatform;
54 
55     extern shared_ptr<Display> gDisplay;
56 
57     const UnixOptionsProcessor::Option_Info UnixSDLDisplay::Options[] =
58     {
59         // command line/povray.conf/environment options of this display mode can be added here
60         // section name, option name, default, has_param, command line parameter, environment variable name, help text
61         UnixOptionsProcessor::Option_Info("display", "scaled", "on", false, "", "POV_DISPLAY_SCALED", "scale render view to fit screen"),
62         UnixOptionsProcessor::Option_Info("", "", "", false, "", "", "") // has to be last
63     };
64 
Register(vfeUnixSession * session)65     bool UnixSDLDisplay::Register(vfeUnixSession *session)
66     {
67         session->GetUnixOptions()->Register(Options);
68         // TODO: correct display detection
69         return true;
70     }
71 
UnixSDLDisplay(unsigned int w,unsigned int h,vfeSession * session,bool visible)72     UnixSDLDisplay::UnixSDLDisplay(unsigned int w, unsigned int h, vfeSession *session, bool visible) :
73         UnixDisplay(w, h, session, visible)
74     {
75         m_valid = false;
76         m_display_scaled = false;
77         m_display_scale = 1.;
78         m_screen = nullptr;
79         m_display = nullptr;
80     }
81 
~UnixSDLDisplay()82     UnixSDLDisplay::~UnixSDLDisplay()
83     {
84         Close();
85     }
86 
Initialise()87     void UnixSDLDisplay::Initialise()
88     {
89         if (m_VisibleOnCreation)
90             Show();
91     }
92 
Hide()93     void UnixSDLDisplay::Hide()
94     {
95     }
96 
TakeOver(UnixDisplay * display)97     bool UnixSDLDisplay::TakeOver(UnixDisplay *display)
98     {
99         UnixSDLDisplay *p = dynamic_cast<UnixSDLDisplay *>(display);
100         if (p == nullptr)
101             return false;
102         if ((GetWidth() != p->GetWidth()) || (GetHeight() != p->GetHeight()))
103             return false;
104 
105         m_valid = p->m_valid;
106         m_display_scaled = p->m_display_scaled;
107         m_display_scale = p->m_display_scale;
108         m_screen = p->m_screen;
109         m_display = p->m_display;
110 
111         if (m_display_scaled)
112         {
113             int width = GetWidth();
114             int height = GetHeight();
115             // allocate a new pixel counters, dropping influence of previous picture
116             m_PxCount.clear(); // not useful, vector was created empty, just to be sure
117             m_PxCount.reserve(width*height); // we need that, and the loop!
118             for(vector<unsigned char>::iterator iter = m_PxCount.begin(); iter != m_PxCount.end(); iter++)
119                 (*iter) = 0;
120         }
121 
122         return true;
123     }
124 
Close()125     void UnixSDLDisplay::Close()
126     {
127         if (!m_valid)
128             return;
129 
130 // FIXME: should handle this correctly for the last frame
131 //      SDL_FreeSurface(m_display);
132 //      SDL_Quit();
133         m_PxCount.clear();
134         m_valid = false;
135     }
136 
SetCaption(bool paused)137     void UnixSDLDisplay::SetCaption(bool paused)
138     {
139         if (!m_valid)
140             return;
141 
142         boost::format f;
143         if (m_display_scaled)
144             f = boost::format(PACKAGE_NAME " " VERSION_BASE " SDL display (scaled at %.0f%%)%s")
145                 % (m_display_scale*100)
146                 % (paused ? " [paused]" : "");
147         else
148             f = boost::format(PACKAGE_NAME " " VERSION_BASE " SDL display%s")
149                 % (paused ? " [paused]" : "");
150         // FIXME: SDL_WM_SetCaption() causes locks on some distros, see http://bugs.povray.org/23
151         // FIXME: SDL_WM_SetCaption(f.str().c_str(), PACKAGE_NAME);
152     }
153 
Show()154     void UnixSDLDisplay::Show()
155     {
156         if (gDisplay.get() != this)
157             gDisplay = m_Session->GetDisplay();
158 
159         if (!m_valid)
160         {
161             // Initialize SDL
162             if ( SDL_Init(SDL_INIT_VIDEO) != 0 )
163             {
164                 fprintf(stderr, "Couldn't initialize SDL: %s.\n", SDL_GetError());
165                 return;
166             }
167 
168             int desired_bpp = 0;
169             Uint32 video_flags = 0;
170             int width = GetWidth();
171             int height = GetHeight();
172 
173             vfeUnixSession *UxSession = dynamic_cast<vfeUnixSession *>(m_Session);
174 
175             if (UxSession->GetUnixOptions()->isOptionSet("display", "scaled"))
176             // determine maximum display area (wrong and ugly)
177             {
178                 SDL_Rect **modes = SDL_ListModes(nullptr, SDL_FULLSCREEN);
179                 // [JG] about testing vs ...(-1), have a look at SDL_ListModes API (the return is very ugly).
180                 if ((modes != nullptr) && (reinterpret_cast<SDL_Rect**>(-1) != modes))
181                 {
182                     width = min(modes[0]->w - 10, width);
183                     height = min(modes[0]->h - 80, height);
184                 }
185             }
186 
187             // calculate display area
188             float AspectRatio = float(width)/float(height);
189             float AspectRatio_Full = float(GetWidth())/float(GetHeight());
190             if (AspectRatio > AspectRatio_Full)
191                 width = int(AspectRatio_Full*float(height));
192             else if (AspectRatio != AspectRatio_Full)
193                 height = int(float(width)/AspectRatio_Full);
194 
195             // Initialize the display
196             m_screen = SDL_SetVideoMode(width, height, desired_bpp, video_flags);
197             if (m_screen == nullptr)
198             {
199                 fprintf(stderr, "Couldn't set %dx%dx%d video mode: %s\n", width, height, desired_bpp, SDL_GetError());
200                 return;
201             }
202 
203             SDL_Surface *temp = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 32, 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF);
204 
205             if (temp == nullptr)
206             {
207                 fprintf(stderr, "Couldn't create render display surface: %s\n", SDL_GetError());
208                 return;
209             }
210 
211             m_display = SDL_DisplayFormat(temp);
212             SDL_FreeSurface(temp);
213 
214             if (m_display == nullptr)
215             {
216                 fprintf(stderr, "Couldn't convert to optimized surface for repeated blitting: %s\n", SDL_GetError());
217                 return;
218             }
219 
220             m_PxCount.clear();
221             m_PxCount.reserve(width*height);
222             for(vector<unsigned char>::iterator iter = m_PxCount.begin(); iter != m_PxCount.end(); iter++)
223                 (*iter) = 0;
224 
225             m_update_rect.x = 0;
226             m_update_rect.y = 0;
227             m_update_rect.w = width;
228             m_update_rect.h = height;
229 
230             m_screen_rect.x = 0;
231             m_screen_rect.y = 0;
232             m_screen_rect.w = width;
233             m_screen_rect.h = height;
234 
235             m_valid = true;
236             m_PxCnt = UpdateInterval;
237 
238             if ((width == GetWidth()) && (height == GetHeight()))
239             {
240                 m_display_scaled = false;
241                 m_display_scale = 1.;
242             }
243             else
244             {
245                 m_display_scaled = true;
246                 /* [JG] the scaling factor between the requested resolution and the actual window is the same in both direction
247                  * yet, the factor (as a float) need the smallest value to avoid an access out of the two buffers for the pixels.
248                  * The difference is nearly invisible until the values of GetWidth and GetHeight are subtil (such as +W2596 +H1003 on a display of 1920 x 1080)
249                  * where in such situation, the computed ratio is not exactly the same as the other.
250                  */
251                 m_display_scale = min(float(width) / GetWidth(), float(height) / GetHeight());
252             }
253 
254             SetCaption(false);
255         }
256     }
257 
SetPixel(unsigned int x,unsigned int y,const RGBA8 & colour)258     inline void UnixSDLDisplay::SetPixel(unsigned int x, unsigned int y, const RGBA8& colour)
259     {
260         Uint8 *p = (Uint8 *) m_display->pixels + y * m_display->pitch + x * m_display->format->BytesPerPixel;
261 
262         Uint32 sdl_col = SDL_MapRGBA(m_display->format, colour.red, colour.green, colour.blue, colour.alpha);
263 
264         switch (m_display->format->BytesPerPixel)
265         {
266             case 1:
267                 *p = sdl_col;
268                 break;
269             case 2:
270                 *(Uint16 *) p = sdl_col;
271                 break;
272             case 3:
273                 if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
274                 {
275                     p[0] = (sdl_col >> 16) & 0xFF;
276                     p[1] = (sdl_col >>  8) & 0xFF;
277                     p[2] =  sdl_col        & 0xFF;
278                 }
279                 else
280                 {
281                     p[0] =  sdl_col        & 0xFF;
282                     p[1] = (sdl_col >>  8) & 0xFF;
283                     p[2] = (sdl_col >> 16) & 0xFF;
284                 }
285                 break;
286             case 4:
287                 *(Uint32 *) p = sdl_col;
288                 break;
289         }
290     }
291 
SetPixelScaled(unsigned int x,unsigned int y,const RGBA8 & colour)292     inline void UnixSDLDisplay::SetPixelScaled(unsigned int x, unsigned int y, const RGBA8& colour)
293     {
294         unsigned int ix = x * m_display_scale;
295         unsigned int iy = y * m_display_scale;
296 
297         Uint8 *p = (Uint8 *) m_display->pixels + iy * m_display->pitch + ix * m_display->format->BytesPerPixel;
298 
299         Uint8 r, g, b, a;
300         Uint32 old = *(Uint32 *) p;
301 
302         SDL_GetRGBA(old, m_display->format, &r, &g, &b, &a);
303 
304         unsigned int ofs = ix + iy * m_display->w;
305         r = (r*m_PxCount[ofs] + colour.red  ) / (m_PxCount[ofs]+1);
306         g = (g*m_PxCount[ofs] + colour.green) / (m_PxCount[ofs]+1);
307         b = (b*m_PxCount[ofs] + colour.blue ) / (m_PxCount[ofs]+1);
308         a = (a*m_PxCount[ofs] + colour.alpha) / (m_PxCount[ofs]+1);
309 
310         Uint32 sdl_col = SDL_MapRGBA(m_display->format, r, g, b, a);
311 
312         switch (m_display->format->BytesPerPixel)
313         {
314             case 1:
315                 *p = sdl_col;
316                 break;
317             case 2:
318                 *(Uint16 *) p = sdl_col;
319                 break;
320             case 3:
321                 if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
322                 {
323                     p[0] = (sdl_col >> 16) & 0xFF;
324                     p[1] = (sdl_col >>  8) & 0xFF;
325                     p[2] =  sdl_col        & 0xFF;
326                 }
327                 else
328                 {
329                     p[0] =  sdl_col        & 0xFF;
330                     p[1] = (sdl_col >>  8) & 0xFF;
331                     p[2] = (sdl_col >> 16) & 0xFF;
332                 }
333                 break;
334             case 4:
335                 *(Uint32 *) p = sdl_col;
336                 break;
337         }
338 
339         ++m_PxCount[ofs];
340     }
341 
UpdateCoord(unsigned int x,unsigned int y)342     void UnixSDLDisplay::UpdateCoord(unsigned int x, unsigned int y)
343     {
344         unsigned int rx2 = m_update_rect.x + m_update_rect.w;
345         unsigned int ry2 = m_update_rect.y + m_update_rect.h;
346         m_update_rect.x = min((unsigned int)m_update_rect.x, x);
347         m_update_rect.y = min((unsigned int)m_update_rect.y, y);
348         rx2 = max(rx2, x);
349         ry2 = max(ry2, y);
350         m_update_rect.w = rx2 - m_update_rect.x;
351         m_update_rect.h = ry2 - m_update_rect.y;
352     }
353 
UpdateCoord(unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2)354     void UnixSDLDisplay::UpdateCoord(unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2)
355     {
356         unsigned int rx2 = m_update_rect.x + m_update_rect.w;
357         unsigned int ry2 = m_update_rect.y + m_update_rect.h;
358         m_update_rect.x = min((unsigned int)m_update_rect.x, x1);
359         m_update_rect.y = min((unsigned int)m_update_rect.y, y1);
360         rx2 = max(rx2, x2);
361         ry2 = max(ry2, y2);
362         m_update_rect.w = rx2 - m_update_rect.x;
363         m_update_rect.h = ry2 - m_update_rect.y;
364     }
365 
UpdateCoordScaled(unsigned int x,unsigned int y)366     void UnixSDLDisplay::UpdateCoordScaled(unsigned int x, unsigned int y)
367     {
368         UpdateCoord(static_cast<unsigned int>(x * m_display_scale), static_cast<unsigned int>(y * m_display_scale));
369     }
370 
UpdateCoordScaled(unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2)371     void UnixSDLDisplay::UpdateCoordScaled(unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2)
372     {
373         UpdateCoord(static_cast<unsigned int>(x1 * m_display_scale), static_cast<unsigned int>(y1 * m_display_scale),
374                     static_cast<unsigned int>(x2 * m_display_scale), static_cast<unsigned int>(y2 * m_display_scale));
375     }
376 
DrawPixel(unsigned int x,unsigned int y,const RGBA8 & colour)377     void UnixSDLDisplay::DrawPixel(unsigned int x, unsigned int y, const RGBA8& colour)
378     {
379         if (!m_valid || x >= GetWidth() || y >= GetHeight())
380             return;
381         if (SDL_MUSTLOCK(m_display) && SDL_LockSurface(m_display) < 0)
382             return;
383 
384         if (m_display_scaled)
385         {
386             SetPixelScaled(x, y, colour);
387             UpdateCoordScaled(x, y);
388         }
389         else
390         {
391             SetPixel(x, y, colour);
392             UpdateCoord(x, y);
393         }
394 
395         m_PxCnt++;
396 
397         if (SDL_MUSTLOCK(m_display))
398             SDL_UnlockSurface(m_display);
399     }
400 
DrawRectangleFrame(unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2,const RGBA8 & colour)401     void UnixSDLDisplay::DrawRectangleFrame(unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2, const RGBA8& colour)
402     {
403         if (!m_valid)
404             return;
405 
406         int ix1 = min(x1, GetWidth()-1);
407         int ix2 = min(x2, GetWidth()-1);
408         int iy1 = min(y1, GetHeight()-1);
409         int iy2 = min(y2, GetHeight()-1);
410 
411         if (SDL_MUSTLOCK(m_display) && SDL_LockSurface(m_display) < 0)
412             return;
413 
414         if (m_display_scaled)
415         {
416             for(unsigned int x = ix1; x <= ix2; x++)
417             {
418                 SetPixelScaled(x, iy1, colour);
419                 SetPixelScaled(x, iy2, colour);
420             }
421 
422             for(unsigned int y = iy1; y <= iy2; y++)
423             {
424                 SetPixelScaled(ix1, y, colour);
425                 SetPixelScaled(ix2, y, colour);
426             }
427             UpdateCoordScaled(ix1, iy1, ix2, iy2);
428         }
429         else
430         {
431             for(unsigned int x = ix1; x <= ix2; x++)
432             {
433                 SetPixel(x, iy1, colour);
434                 SetPixel(x, iy2, colour);
435             }
436 
437             for(unsigned int y = iy1; y <= iy2; y++)
438             {
439                 SetPixel(ix1, y, colour);
440                 SetPixel(ix2, y, colour);
441             }
442             UpdateCoord(ix1, iy1, ix2, iy2);
443         }
444 
445         if (SDL_MUSTLOCK(m_display))
446             SDL_UnlockSurface(m_display);
447 
448         m_PxCnt = UpdateInterval;
449     }
450 
DrawFilledRectangle(unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2,const RGBA8 & colour)451     void UnixSDLDisplay::DrawFilledRectangle(unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2, const RGBA8& colour)
452     {
453         if (!m_valid)
454             return;
455 
456         unsigned int ix1 = min(x1, GetWidth()-1);
457         unsigned int ix2 = min(x2, GetWidth()-1);
458         unsigned int iy1 = min(y1, GetHeight()-1);
459         unsigned int iy2 = min(y2, GetHeight()-1);
460 
461         if (m_display_scaled)
462         {
463             ix1 *= m_display_scale;
464             iy1 *= m_display_scale;
465             ix2 *= m_display_scale;
466             iy2 *= m_display_scale;
467         }
468 
469         UpdateCoord(ix1, iy1, ix2, iy2);
470 
471         Uint32 sdl_col = SDL_MapRGBA(m_display->format, colour.red, colour.green, colour.blue, colour.alpha);
472 
473         SDL_Rect tempRect;
474         tempRect.x = ix1;
475         tempRect.y = iy1;
476         tempRect.w = ix2 - ix1 + 1;
477         tempRect.h = iy2 - iy1 + 1;
478         SDL_FillRect(m_display, &tempRect, sdl_col);
479 
480         m_PxCnt = UpdateInterval;
481     }
482 
DrawPixelBlock(unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2,const RGBA8 * colour)483     void UnixSDLDisplay::DrawPixelBlock(unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2, const RGBA8 *colour)
484     {
485         if (!m_valid)
486             return;
487 
488         unsigned int ix1 = min(x1, GetWidth()-1);
489         unsigned int ix2 = min(x2, GetWidth()-1);
490         unsigned int iy1 = min(y1, GetHeight()-1);
491         unsigned int iy2 = min(y2, GetHeight()-1);
492 
493         if (SDL_MUSTLOCK(m_display) && SDL_LockSurface(m_display) < 0)
494             return;
495 
496         if (m_display_scaled)
497         {
498             for(unsigned int y = iy1, i = 0; y <= iy2; y++)
499                 for(unsigned int x = ix1; x <= ix2; x++, i++)
500                     SetPixelScaled(x, y, colour[i]);
501             UpdateCoordScaled(ix1, iy1, ix2, iy2);
502         }
503         else
504         {
505             for(unsigned int y = y1, i = 0; y <= iy2; y++)
506                 for(unsigned int x = ix1; x <= ix2; x++, i++)
507                     SetPixel(x, y, colour[i]);
508             UpdateCoord(ix1, iy1, ix2, iy2);
509         }
510 
511         if (SDL_MUSTLOCK(m_display))
512             SDL_UnlockSurface(m_display);
513 
514         m_PxCnt = UpdateInterval;
515     }
516 
Clear()517     void UnixSDLDisplay::Clear()
518     {
519         for(vector<unsigned char>::iterator iter = m_PxCount.begin(); iter != m_PxCount.end(); iter++)
520             (*iter) = 0;
521 
522         m_update_rect.x = 0;
523         m_update_rect.y = 0;
524         m_update_rect.w = m_display->w;
525         m_update_rect.h = m_display->h;
526 
527         SDL_FillRect(m_display, &m_update_rect, (Uint32)0);
528 
529         m_PxCnt = UpdateInterval;
530     }
531 
UpdateScreen(bool Force=false)532     void UnixSDLDisplay::UpdateScreen(bool Force = false)
533     {
534         if (!m_valid)
535             return;
536         if (Force || m_PxCnt >= UpdateInterval)
537         {
538             SDL_BlitSurface(m_display, &m_update_rect, m_screen, &m_update_rect);
539             SDL_UpdateRect(m_screen, m_update_rect.x, m_update_rect.y, m_update_rect.w, m_update_rect.h);
540             m_PxCnt = 0;
541         }
542     }
543 
PauseWhenDoneNotifyStart()544     void UnixSDLDisplay::PauseWhenDoneNotifyStart()
545     {
546         if (!m_valid)
547             return;
548         fprintf(stderr, "Press p, q, enter or click the display to continue...");
549         SetCaption(true);
550     }
551 
PauseWhenDoneNotifyEnd()552     void UnixSDLDisplay::PauseWhenDoneNotifyEnd()
553     {
554         if (!m_valid)
555             return;
556         SetCaption(false);
557         fprintf(stderr, "\n\n");
558     }
559 
PauseWhenDoneResumeIsRequested()560     bool UnixSDLDisplay::PauseWhenDoneResumeIsRequested()
561     {
562         if (!m_valid)
563             return true;
564 
565         SDL_Event event;
566         bool do_quit = false;
567 
568         if (SDL_PollEvent(&event))
569         {
570             switch (event.type)
571             {
572                 case SDL_KEYDOWN:
573                     if ( event.key.keysym.sym == SDLK_p || event.key.keysym.sym == SDLK_q ||
574                          event.key.keysym.sym == SDLK_RETURN || event.key.keysym.sym == SDLK_KP_ENTER )
575                         do_quit = true;
576                     break;
577                 case SDL_MOUSEBUTTONDOWN:
578                     do_quit = true;
579                     break;
580             }
581         }
582 
583         return do_quit;
584     }
585 
HandleEvents()586     bool UnixSDLDisplay::HandleEvents()
587     {
588         if (!m_valid)
589             return false;
590 
591         SDL_Event event;
592         bool do_quit = false;
593 
594         while (SDL_PollEvent(&event))
595         {
596             switch (event.type)
597             {
598                 case SDL_KEYDOWN:
599                     if ( event.key.keysym.sym == SDLK_q )
600                         do_quit = true;
601                     else if ( event.key.keysym.sym == SDLK_p )
602                     {
603                         if (!m_Session->IsPausable())
604                             break;
605 
606                         if (m_Session->Paused())
607                         {
608                             if (m_Session->Resume())
609                                 SetCaption(false);
610                         }
611                         else
612                         {
613                             if (m_Session->Pause())
614                                 SetCaption(true);
615                         }
616                     }
617                     break;
618                 case SDL_QUIT:
619                     do_quit = true;
620                     break;
621             }
622             if (do_quit)
623                 break;
624         }
625 
626         return do_quit;
627     }
628 
629 }
630 
631 #endif /* HAVE_LIBSDL */
632