1 /*
2 BStone: A Source port of
3 Blake Stone: Aliens of Gold and Blake Stone: Planet Strike
4 
5 Copyright (c) 1992-2013 Apogee Entertainment, LLC
6 Copyright (c) 2013-2015 Boris I. Bendovsky (bibendovsky@hotmail.com)
7 
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
12 
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 GNU General Public License for more details.
17 
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the
20 Free Software Foundation, Inc.,
21 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 */
23 
24 
25 #include "id_heads.h"
26 
27 
28 static const int palette_color_count = 256;
29 
30 
31 int bufferofs;
32 
33 bool screenfaded;
34 
35 uint8_t palette1[palette_color_count][3];
36 uint8_t palette2[palette_color_count][3];
37 uint8_t* vga_memory = nullptr;
38 
39 int vga_scale = 0;
40 int vga_width = 0;
41 int vga_height = 0;
42 int vga_area = 0;
43 int vga_3d_view_top = 0;
44 int vga_3d_view_bottom = 0;
45 
46 int screen_x = 0;
47 int screen_y = 0;
48 
49 int screen_width = 0;
50 int screen_height = 0;
51 
52 bool vid_has_vsync = false;
53 bool vid_widescreen = default_vid_widescreen;
54 bool vid_is_hud = false;
55 bool vid_is_3d = false;
56 
57 bstone::SpriteCache vid_sprite_cache;
58 
59 
60 // BBi
61 namespace
62 {
63 
64 
65 constexpr int default_window_width = 640;
66 constexpr int default_window_height = 480;
67 
68 
69 class VgaColor
70 {
71 public:
72     uint8_t r;
73     uint8_t g;
74     uint8_t b;
75 }; // VgaColor
76 
77 using VgaPalette = std::array<VgaColor, palette_color_count>;
78 using SdlPalette = std::array<uint32_t, palette_color_count>;
79 
80 
81 int window_width = 0;
82 int window_height = 0;
83 
84 VgaBuffer sdl_ui_buffer;
85 UiMaskBuffer sdl_mask_buffer;
86 
87 
88 std::string sdl_error_message;
89 
90 VgaPalette sdl_vga_palette;
91 VgaBuffer sdl_vga_buffer;
92 
93 bool sdl_use_custom_window_position = false;
94 int sdl_window_x = 0;
95 int sdl_window_y = 0;
96 
97 SDL_DisplayMode display_mode;
98 bool sdl_is_windowed = false;
99 SDL_Window* sdl_window = nullptr;
100 SDL_Renderer* sdl_renderer = nullptr;
101 SDL_PixelFormat* sdl_texture_pixel_format = nullptr;
102 SDL_Texture* sdl_screen_texture = nullptr;
103 SDL_Texture* sdl_ui_texture = nullptr;
104 SdlPalette sdl_palette;
105 
106 
sdl_initialize_vga_buffer()107 void sdl_initialize_vga_buffer()
108 {
109     const auto vga_area = 2 * ::vga_width * ::vga_height;
110 
111     ::sdl_vga_buffer.resize(
112         vga_area);
113 
114     ::vga_memory = ::sdl_vga_buffer.data();
115 }
116 
sdl_initialize_ui_buffer()117 void sdl_initialize_ui_buffer()
118 {
119     const auto area = ::vga_ref_width * ::vga_ref_height;
120 
121     ::sdl_ui_buffer.resize(
122         area);
123 }
124 
sdl_initialize_window()125 bool sdl_initialize_window()
126 {
127     bstone::Log::write("VID: Creating a window...");
128 
129 
130     if (!::sdl_use_custom_window_position)
131     {
132         ::sdl_window_x = SDL_WINDOWPOS_CENTERED;
133         ::sdl_window_y = SDL_WINDOWPOS_CENTERED;
134     }
135 
136     if (::sdl_window_x < 0)
137     {
138         ::sdl_window_x = 0;
139     }
140 
141     if (::sdl_window_y < 0)
142     {
143         ::sdl_window_y = 0;
144     }
145 
146     uint32_t window_flags =
147         SDL_WINDOW_OPENGL |
148         SDL_WINDOW_HIDDEN |
149         0;
150 
151     if (!::sdl_is_windowed)
152     {
153         window_flags |=
154             SDL_WINDOW_BORDERLESS |
155             SDL_WINDOW_FULLSCREEN_DESKTOP;
156     }
157 
158 
159     std::string title = "Blake Stone: ???";
160 
161     if (::is_aog_full())
162     {
163         std::string version_string;
164 
165         if (::is_aog_full_v1_0())
166         {
167             version_string = "v1.0";
168         }
169         else if (::is_aog_full_v2_x())
170         {
171             version_string = "v2.x";
172         }
173         else if (::is_aog_full_v3_0())
174         {
175             version_string = "v3.0";
176         }
177 
178         title = "Blake Stone: Aliens of Gold (full";
179 
180         if (!version_string.empty())
181         {
182             title += ", " + version_string;
183         }
184 
185         title += ')';
186     }
187     else if (::is_aog_sw())
188     {
189         title = "Blake Stone: Aliens of Gold (shareware, v3.0)";
190     }
191     else if (::is_ps())
192     {
193         title = "Blake Stone: Planet Strike (full, v1.x)";
194     }
195 
196     ::sdl_window = ::SDL_CreateWindow(
197         title.c_str(),
198         ::sdl_window_x,
199         ::sdl_window_y,
200         ::window_width,
201         ::window_height,
202         window_flags);
203 
204     if (!::sdl_window)
205     {
206         ::sdl_error_message = "VID: Failed to create a window: ";
207         ::sdl_error_message += ::SDL_GetError();
208 
209         bstone::Log::write_error(::SDL_GetError());
210 
211         return false;
212     }
213 
214     auto hint_result = ::SDL_SetHint(
215         "SDL_HINT_RENDER_DRIVER",
216         "opengl");
217 
218     return true;
219 }
220 
sdl_initialize_renderer()221 bool sdl_initialize_renderer()
222 {
223     bstone::Log::write(
224         "VID: Initializing a renderer...");
225 
226 
227     bool is_succeed = true;
228 
229 
230     if (is_succeed)
231     {
232         bstone::Log::write(
233             "VID: Available renderer drivers:");
234 
235         const auto driver_count = ::SDL_GetNumRenderDrivers();
236 
237         for (int i = 0; i < driver_count; ++i)
238         {
239             SDL_RendererInfo info;
240 
241             const auto sdl_result = ::SDL_GetRenderDriverInfo(
242                 i,
243                 &info);
244 
245             bstone::Log::write(
246                 "{}. {}",
247                 i + 1,
248                 info.name);
249         }
250     }
251 
252 
253     uint32_t renderer_flags = 0;
254     const char* renderer_driver = nullptr;
255 
256     if (is_succeed)
257     {
258         const auto is_vsync_disabled = ::g_args.has_option(
259             "vid_no_vsync");
260 
261         if (is_vsync_disabled)
262         {
263             bstone::Log::write(
264                 "VID: Skipping VSync...");
265         }
266         else
267         {
268             renderer_flags = SDL_RENDERER_PRESENTVSYNC;
269 
270             bstone::Log::write(
271                 "VID: Using VSync...");
272         }
273 
274 
275         auto& ren_string = ::g_args.get_option_value(
276             "vid_renderer");
277 
278         if (!ren_string.empty())
279         {
280             bstone::Log::write(
281                 "VID: Setting preferred renderer...");
282 
283 
284             if (ren_string == "d3d")
285             {
286                 renderer_driver = "direct3d";
287 
288                 bstone::Log::write(
289                     "VID: Forcing Direct3D renderer.");
290             }
291             else if (ren_string == "ogl")
292             {
293                 renderer_driver = "opengl";
294 
295                 bstone::Log::write(
296                     "VID: Forcing OpenGL renderer.");
297             }
298             else if (ren_string == "ogles")
299             {
300                 renderer_driver = "opengles";
301 
302                 bstone::Log::write(
303                     "VID: Forcing OpenGL ES renderer.");
304             }
305             else if (ren_string == "ogles2")
306             {
307                 renderer_driver = "opengles2";
308 
309                 bstone::Log::write(
310                     "VID: Forcing OpenGL ES 2 renderer.");
311             }
312             else if (ren_string == "soft")
313             {
314                 renderer_driver = "software";
315 
316                 bstone::Log::write(
317                     "VID: Forcing software renderer.");
318             }
319             else
320             {
321                 bstone::Log::write_warning(
322                     "VID: Unsupported renderer: {}",
323                     ren_string);
324             }
325 
326 
327             auto hint_result = ::SDL_SetHint(
328                 SDL_HINT_RENDER_DRIVER,
329                 renderer_driver);
330 
331             if (hint_result == SDL_FALSE)
332             {
333                 bstone::Log::write_warning(
334                     ::SDL_GetError());
335             }
336         }
337     }
338 
339     if (is_succeed)
340     {
341         bstone::Log::write(
342             "VID: Creating a renderer...");
343 
344         ::sdl_renderer = ::SDL_CreateRenderer(
345             ::sdl_window,
346             -1,
347             renderer_flags);
348 
349         if (!::sdl_renderer)
350         {
351             is_succeed = false;
352 
353             ::sdl_error_message = "VID: Failed to create a renderer: ";
354             ::sdl_error_message += ::SDL_GetError();
355 
356             bstone::Log::write_error(
357                 ::SDL_GetError());
358         }
359     }
360 
361 
362     SDL_RendererInfo renderer_info;
363 
364     if (is_succeed)
365     {
366         bstone::Log::write(
367             "VID: Quering renderer for info...");
368 
369         auto sdl_result = ::SDL_GetRendererInfo(
370             ::sdl_renderer,
371             &renderer_info);
372 
373         if (sdl_result != 0)
374         {
375             is_succeed = false;
376 
377             ::sdl_error_message = "VID: Failed to query the renderer: ";
378             ::sdl_error_message += ::SDL_GetError();
379 
380             bstone::Log::write_error(
381                 ::SDL_GetError());
382         }
383     }
384 
385 
386     if (is_succeed)
387     {
388         if (renderer_driver)
389         {
390             if (::SDL_strcasecmp(
391                 renderer_driver,
392                 renderer_info.name) != 0)
393             {
394                 bstone::Log::write_warning(
395                     "VID: Unexpected renderer is selected: {0}.",
396                     renderer_info.name);
397             }
398         }
399         else
400         {
401             bstone::Log::write(
402                 "VID: Current renderer: {0}.",
403                 renderer_info.name);
404         }
405     }
406 
407 
408     uint32_t pixel_format = SDL_PIXELFORMAT_UNKNOWN;
409 
410     if (is_succeed)
411     {
412         bstone::Log::write(
413             "VID: Looking up for a texture pixel format...");
414 
415         const auto format_count = renderer_info.num_texture_formats;
416 
417         for (auto i = decltype(format_count){}; i < format_count; ++i)
418         {
419             const auto format = renderer_info.texture_formats[i];
420 
421             if (
422                 SDL_PIXELTYPE(format) == SDL_PIXELTYPE_PACKED32 &&
423                 SDL_PIXELLAYOUT(format) == SDL_PACKEDLAYOUT_8888 &&
424                 SDL_ISPIXELFORMAT_ALPHA(format))
425             {
426                 pixel_format = format;
427                 break;
428             }
429         }
430 
431         if (pixel_format == SDL_PIXELFORMAT_UNKNOWN)
432         {
433             bstone::Log::write_warning(
434                 "Falling back to a predefined pixel format.");
435 
436             pixel_format = SDL_PIXELFORMAT_ARGB8888;
437         }
438 
439 
440         bstone::Log::write(
441             "VID: Allocating a texture pixel format...");
442 
443         ::sdl_texture_pixel_format = ::SDL_AllocFormat(
444             pixel_format);
445 
446         if (!::sdl_texture_pixel_format)
447         {
448             is_succeed = false;
449 
450             ::sdl_error_message = "VID: Failed to allocate a texture pixel format: ";
451             ::sdl_error_message += ::SDL_GetError();
452 
453             bstone::Log::write_error(
454                 ::SDL_GetError());
455         }
456     }
457 
458     return is_succeed;
459 }
460 
sdl_initialize_screen_texture()461 bool sdl_initialize_screen_texture()
462 {
463     bstone::Log::write(
464         "VID: Creating a screen texture...");
465 
466     ::sdl_screen_texture = ::SDL_CreateTexture(
467         ::sdl_renderer,
468         ::sdl_texture_pixel_format->format,
469         SDL_TEXTUREACCESS_STREAMING,
470         ::vga_width,
471         ::vga_height);
472 
473     if (!::sdl_screen_texture)
474     {
475         ::sdl_error_message = "VID: Failed to create a screen texture: ";
476         ::sdl_error_message += ::SDL_GetError();
477 
478         bstone::Log::write(
479             ::SDL_GetError());
480 
481         return false;
482     }
483 
484     return true;
485 }
486 
sdl_initialize_ui_texture()487 bool sdl_initialize_ui_texture()
488 {
489     bstone::Log::write(
490         "VID: Creating an UI texture...");
491 
492     ::sdl_ui_texture = ::SDL_CreateTexture(
493         ::sdl_renderer,
494         ::sdl_texture_pixel_format->format,
495         SDL_TEXTUREACCESS_STREAMING,
496         ::vga_ref_width,
497         ::vga_ref_height);
498 
499     if (!::sdl_ui_texture)
500     {
501         ::sdl_error_message = "VID: Failed to create an UI texture: ";
502         ::sdl_error_message += ::SDL_GetError();
503 
504         bstone::Log::write(
505             ::SDL_GetError());
506 
507         return false;
508     }
509 
510     return true;
511 }
512 
sdl_initialize_textures()513 bool sdl_initialize_textures()
514 {
515     bstone::Log::write(
516         "VID: Initializing textures...");
517 
518 
519     auto is_succeed = true;
520 
521     if (is_succeed)
522     {
523         is_succeed = sdl_initialize_screen_texture();
524     }
525 
526     if (is_succeed)
527     {
528         is_succeed = sdl_initialize_ui_texture();
529     }
530 
531     return is_succeed;
532 }
533 
sdl_update_palette(int first_index,int color_count)534 void sdl_update_palette(
535     int first_index,
536     int color_count)
537 {
538     for (int i = 0; i < color_count; ++i)
539     {
540         const auto color_index = first_index + i;
541         const auto& vga_color = ::sdl_vga_palette[color_index];
542         auto& sdl_color = ::sdl_palette[color_index];
543 
544         sdl_color = ::SDL_MapRGB(
545             ::sdl_texture_pixel_format,
546             (255 * vga_color.r) / 63,
547             (255 * vga_color.g) / 63,
548             (255 * vga_color.b) / 63);
549     }
550 }
551 
sdl_update_viewport()552 void sdl_update_viewport()
553 {
554     auto sdl_result = ::SDL_RenderSetLogicalSize(
555         ::sdl_renderer,
556         ::screen_width,
557         ::screen_height);
558 
559     if (sdl_result != 0)
560     {
561         bstone::Log::write_error(
562             "VID: Failed to update a viewport.");
563     }
564 }
565 
sdl_initialize_palette()566 void sdl_initialize_palette()
567 {
568     ::sdl_vga_palette.fill({});
569 
570     ::sdl_update_palette(
571         0,
572         palette_color_count);
573 }
574 
sdl_calculate_dimensions()575 void sdl_calculate_dimensions()
576 {
577     ::vga_height = (10 * ::window_height) / 12;
578     ::vga_height += 4 - 1;
579     ::vga_height /= 4;
580     ::vga_height *= 4;
581 
582     if (::vid_widescreen)
583     {
584         ::vga_width = ::window_width;
585         ::vga_width += 4 - 1;
586         ::vga_width /= 4;
587         ::vga_width *= 4;
588     }
589     else
590     {
591         ::vga_width = (::vga_ref_width * ::vga_height) / ::vga_ref_height;
592     }
593 
594     ::vga_area = ::vga_width * ::vga_height;
595 
596     ::screen_width = ::vga_width;
597     ::screen_height = (12 * ::vga_height) / 10;
598 }
599 
sdl_initialize_video()600 void sdl_initialize_video()
601 {
602     bstone::Log::write(
603         "VID: Initializing a system...");
604 
605 
606     bool is_custom_scale = false;
607 
608     //
609     // Option "vid_windowed"
610     //
611 
612     ::sdl_is_windowed = ::g_args.has_option(
613         "vid_windowed");
614 
615     ::sdl_use_custom_window_position = false;
616 
617 
618     //
619     // Option "vid_window_x"
620     //
621 
622     const auto& vid_window_x_str = ::g_args.get_option_value(
623         "vid_window_x");
624 
625     if (bstone::StringHelper::lexical_cast(
626         vid_window_x_str,
627         ::sdl_window_x))
628     {
629         ::sdl_use_custom_window_position = true;
630     }
631 
632 
633     //
634     // Option "vid_window_y"
635     //
636 
637     const auto& vid_window_y_str = ::g_args.get_option_value(
638         "vid_window_y");
639 
640     if (bstone::StringHelper::lexical_cast(
641         vid_window_y_str,
642         ::sdl_window_y))
643     {
644         ::sdl_use_custom_window_position = true;
645     }
646 
647 
648     //
649     // Option "vid_mode"
650     //
651 
652     std::string width_str;
653     std::string height_str;
654 
655     ::g_args.get_option_values(
656         "vid_mode",
657         width_str,
658         height_str);
659 
660     static_cast<void>(bstone::StringHelper::lexical_cast(
661         width_str,
662         ::window_width));
663 
664     static_cast<void>(bstone::StringHelper::lexical_cast(
665         height_str,
666         ::window_height));
667 
668     if (::window_width == 0)
669     {
670         ::window_width = ::default_window_width;
671     }
672 
673     if (::window_height == 0)
674     {
675         ::window_height = ::default_window_height;
676     }
677 
678     if (::window_width < ::vga_ref_width)
679     {
680         ::window_width = ::vga_ref_width;
681     }
682 
683     if (::window_height < ::vga_ref_height_4x3)
684     {
685         ::window_height = ::vga_ref_height_4x3;
686     }
687 
688 
689     //
690     // Option "vid_scale"
691     //
692 
693     const auto& vid_scale_str = ::g_args.get_option_value(
694         "vid_scale");
695 
696     if (!vid_scale_str.empty())
697     {
698         int scale_value = 0;
699 
700         if (bstone::StringHelper::lexical_cast(
701             vid_scale_str,
702             scale_value))
703         {
704             is_custom_scale = true;
705 
706             if (scale_value < 1)
707             {
708                 scale_value = 1;
709             }
710 
711             ::vga_scale = scale_value;
712         }
713     }
714 
715 
716     int sdl_result = 0;
717 
718     sdl_result = ::SDL_GetDesktopDisplayMode(
719         0,
720         &::display_mode);
721 
722     if (sdl_result != 0)
723     {
724         ::Quit("VID: Failed to get a display mode.");
725     }
726 
727     if (!::sdl_is_windowed)
728     {
729         ::window_width = ::display_mode.w;
730         ::window_height = ::display_mode.h;
731     }
732 
733 
734     ::sdl_calculate_dimensions();
735 
736     bool is_succeed = true;
737 
738     if (is_succeed)
739     {
740         is_succeed = ::sdl_initialize_window();
741     }
742 
743     if (is_succeed)
744     {
745         is_succeed = ::sdl_initialize_renderer();
746     }
747 
748     if (is_succeed)
749     {
750         is_succeed = ::sdl_initialize_textures();
751     }
752 
753     if (is_succeed)
754     {
755         ::sdl_initialize_palette();
756     }
757 
758     if (is_succeed)
759     {
760         ::sdl_initialize_vga_buffer();
761         ::sdl_initialize_ui_buffer();
762     }
763 
764     if (is_succeed)
765     {
766         ::SDL_ShowWindow(
767             ::sdl_window);
768 
769         ::in_grab_mouse(
770             true);
771     }
772     else
773     {
774         ::Quit(
775             ::sdl_error_message);
776     }
777 }
778 
sdl_uninitialize_screen_texture()779 void sdl_uninitialize_screen_texture()
780 {
781     if (::sdl_screen_texture)
782     {
783         ::SDL_DestroyTexture(
784             ::sdl_screen_texture);
785 
786         ::sdl_screen_texture = nullptr;
787     }
788 }
789 
sdl_uninitialize_ui_texture()790 void sdl_uninitialize_ui_texture()
791 {
792     if (::sdl_ui_texture)
793     {
794         ::SDL_DestroyTexture(
795             ::sdl_ui_texture);
796 
797         ::sdl_ui_texture = nullptr;
798     }
799 }
800 
sdl_uninitialize_vga_buffer()801 void sdl_uninitialize_vga_buffer()
802 {
803     ::sdl_vga_buffer.clear();
804     ::sdl_vga_buffer.shrink_to_fit();
805 
806     ::vga_memory = nullptr;
807 }
808 
sdl_uninitialize_video()809 void sdl_uninitialize_video()
810 {
811     if (::sdl_texture_pixel_format)
812     {
813         ::SDL_FreeFormat(
814             ::sdl_texture_pixel_format);
815 
816         ::sdl_texture_pixel_format = nullptr;
817     }
818 
819     ::sdl_uninitialize_screen_texture();
820     ::sdl_uninitialize_ui_texture();
821 
822     if (::sdl_renderer)
823     {
824         ::SDL_DestroyRenderer(
825             ::sdl_renderer);
826 
827         ::sdl_renderer = nullptr;
828     }
829 
830     if (::sdl_window)
831     {
832         ::SDL_DestroyWindow(
833             ::sdl_window);
834 
835         ::sdl_window = nullptr;
836     }
837 
838     ::sdl_uninitialize_vga_buffer();
839 }
840 
sdl_refresh_screen()841 void sdl_refresh_screen()
842 {
843     int sdl_result = 0;
844 
845     // HUD+3D stuff
846     //
847     if (::vid_is_hud)
848     {
849         const auto src_pixels = ::sdl_vga_buffer.data();
850         const auto src_pitch = ::vga_width;
851 
852         void* dst_raw_pixels = nullptr;
853         int dst_pitch = 0;
854 
855         sdl_result = ::SDL_LockTexture(
856             ::sdl_screen_texture,
857             nullptr,
858             &dst_raw_pixels,
859             &dst_pitch);
860 
861         if (sdl_result != 0)
862         {
863             ::Quit(
864                 "VID: Failed to lock a screen texture: {}",
865                 ::SDL_GetError());
866         }
867 
868         auto dst_pixels = static_cast<uint32_t*>(
869             dst_raw_pixels);
870 
871         for (int y = 0; y < ::vga_height; ++y)
872         {
873             const auto src_line = &src_pixels[y * src_pitch];
874             auto dst_line = &dst_pixels[y * (dst_pitch / 4)];
875 
876             for (int x = 0; x < ::vga_width; ++x)
877             {
878                 dst_line[x] = sdl_palette[src_line[x]];
879             }
880         }
881 
882         ::SDL_UnlockTexture(
883             ::sdl_screen_texture);
884     }
885 
886 
887     // 2D stuff
888     //
889     {
890         void* dst_raw_pixels = nullptr;
891         int dst_pitch = 0;
892 
893         sdl_result = ::SDL_LockTexture(
894             ::sdl_ui_texture,
895             nullptr,
896             &dst_raw_pixels,
897             &dst_pitch);
898 
899         if (sdl_result != 0)
900         {
901             ::Quit(
902                 "VID: Failed to lock an UI texture: {}",
903                 ::SDL_GetError());
904         }
905 
906         const auto alpha_0_mask = ~sdl_texture_pixel_format->Amask;
907 
908         auto dst_pixels = static_cast<uint32_t*>(
909             dst_raw_pixels);
910 
911         for (int y = 0; y < ::vga_ref_height; ++y)
912         {
913             auto dst_line = &dst_pixels[y * (dst_pitch / 4)];
914 
915             for (int x = 0; x < ::vga_ref_width; ++x)
916             {
917                 const auto src_offset = (y * ::vga_ref_width) + x;
918                 auto dst_color = ::sdl_palette[::sdl_ui_buffer[src_offset]];
919 
920                 if (::vid_is_hud)
921                 {
922                     if (!::sdl_mask_buffer[src_offset])
923                     {
924                         dst_color &= alpha_0_mask;
925                     }
926                 }
927 
928                 dst_line[x] = dst_color;
929             }
930         }
931 
932         ::SDL_UnlockTexture(
933             ::sdl_ui_texture);
934     }
935 
936 
937     // Clear all
938     //
939     sdl_result = ::SDL_RenderClear(
940         sdl_renderer);
941 
942     if (sdl_result != 0)
943     {
944         ::Quit(
945             "VID: Failed to clear a render target: {}",
946             ::SDL_GetError());
947     }
948 
949 
950     // Copy HUD+3D stuff
951     //
952     if (::vid_is_hud)
953     {
954         sdl_result = ::SDL_RenderCopy(
955             sdl_renderer,
956             sdl_screen_texture,
957             nullptr,
958             nullptr);
959 
960         if (sdl_result != 0)
961         {
962             ::Quit(
963                 "VID: Failed to copy a screen texture on a render target: {}",
964                 ::SDL_GetError());
965         }
966     }
967 
968 
969     // Copy 2D stuff
970     //
971 
972     if (::vid_is_hud)
973     {
974         sdl_result = ::SDL_SetTextureBlendMode(
975             ::sdl_ui_texture,
976             SDL_BLENDMODE_BLEND);
977 
978         if (sdl_result != 0)
979         {
980             ::Quit(
981                 "VID: Failed to set blend mode for an UI texture: {}",
982                 ::SDL_GetError());
983         }
984     }
985 
986     sdl_result = ::SDL_RenderCopy(
987         ::sdl_renderer,
988         ::sdl_ui_texture,
989         nullptr,
990         nullptr);
991 
992     if (sdl_result != 0)
993     {
994         ::Quit(
995             "VID: Failed to copy an UI texture on a render target: {}",
996             ::SDL_GetError());
997     }
998 
999     if (::vid_is_hud)
1000     {
1001         sdl_result = ::SDL_SetTextureBlendMode(
1002             ::sdl_ui_texture,
1003             SDL_BLENDMODE_NONE);
1004 
1005         if (sdl_result != 0)
1006         {
1007             ::Quit(
1008                 "VID: Failed to set blend mode for an UI texture: {}",
1009                 ::SDL_GetError());
1010         }
1011     }
1012 
1013 
1014     // Present
1015     //
1016     ::SDL_RenderPresent(
1017         sdl_renderer);
1018 
1019     if (sdl_result != 0)
1020     {
1021         ::Quit(
1022             "VID: Failed to present a render target: {}",
1023             ::SDL_GetError());
1024     }
1025 }
1026 
sdl_check_vsync()1027 void sdl_check_vsync()
1028 {
1029     constexpr int draw_count = 10;
1030 
1031     constexpr int duration_tolerance_pct = 25;
1032 
1033     const int expected_duration_ms =
1034         (1000 * draw_count) / ::display_mode.refresh_rate;
1035 
1036     const int min_expected_duration_ms =
1037         ((100 - duration_tolerance_pct) * expected_duration_ms) / 100;
1038 
1039     const auto before_timestamp = Clock::now();
1040 
1041     for (int i = 0; i < draw_count; ++i)
1042     {
1043         ::sdl_refresh_screen();
1044     }
1045 
1046     const auto after_timestamp = Clock::now();
1047 
1048     const auto duration = after_timestamp - before_timestamp;
1049 
1050     const auto duration_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
1051         duration).count();
1052 
1053     ::vid_has_vsync = (duration_ms >= min_expected_duration_ms);
1054 }
1055 
sdl_update_widescreen()1056 void sdl_update_widescreen()
1057 {
1058     ::sdl_uninitialize_screen_texture();
1059     ::sdl_uninitialize_vga_buffer();
1060     ::sdl_calculate_dimensions();
1061     ::sdl_initialize_vga_buffer();
1062     ::sdl_initialize_screen_texture();
1063     ::sdl_update_viewport();
1064 }
1065 
1066 
1067 } // namespace
1068 // BBi
1069 
1070 
1071 // ===========================================================================
1072 
1073 // asm
1074 
VL_WaitVBL(uint32_t vbls)1075 void VL_WaitVBL(
1076     uint32_t vbls)
1077 {
1078     if (vbls == 0)
1079     {
1080         return;
1081     }
1082 
1083     ::sys_sleep_for(1000 * vbls / TickBase);
1084 }
1085 
1086 // ===========================================================================
1087 
1088 
1089 // BBi Moved from jm_free.cpp
VL_Startup()1090 void VL_Startup()
1091 {
1092     ::sdl_initialize_video();
1093     ::sdl_refresh_screen();
1094 
1095     ::in_handle_events();
1096 
1097     ::sdl_check_vsync();
1098 }
1099 // BBi
1100 
VL_Shutdown()1101 void VL_Shutdown()
1102 {
1103     ::sdl_uninitialize_video();
1104 }
1105 
1106 // ===========================================================================
1107 
1108 /*
1109 =============================================================================
1110 
1111                                 PALETTE OPS
1112 
1113                 To avoid snow, do a WaitVBL BEFORE calling these
1114 
1115 =============================================================================
1116 */
1117 
VL_FillPalette(uint8_t red,uint8_t green,uint8_t blue)1118 void VL_FillPalette(
1119     uint8_t red,
1120     uint8_t green,
1121     uint8_t blue)
1122 {
1123     for (auto& vga_color : ::sdl_vga_palette)
1124     {
1125         vga_color.r = red;
1126         vga_color.g = green;
1127         vga_color.b = blue;
1128     }
1129 
1130     ::sdl_update_palette(
1131         0,
1132         palette_color_count);
1133 }
1134 
VL_SetPalette(int first,int count,const uint8_t * palette)1135 void VL_SetPalette(
1136     int first,
1137     int count,
1138     const uint8_t* palette)
1139 {
1140     for (int i = 0; i < count; ++i)
1141     {
1142         auto& vga_color = ::sdl_vga_palette[first + i];
1143 
1144         vga_color.r = palette[(3 * i) + 0];
1145         vga_color.g = palette[(3 * i) + 1];
1146         vga_color.b = palette[(3 * i) + 2];
1147     }
1148 
1149     ::sdl_update_palette(
1150         first,
1151         count);
1152 }
1153 
VL_GetPalette(int first,int count,uint8_t * palette)1154 void VL_GetPalette(
1155     int first,
1156     int count,
1157     uint8_t* palette)
1158 {
1159     for (int i = 0; i < count; ++i)
1160     {
1161         const auto& vga_color = ::sdl_vga_palette[first + i];
1162 
1163         palette[(3 * i) + 0] = vga_color.r;
1164         palette[(3 * i) + 1] = vga_color.g;
1165         palette[(3 * i) + 2] = vga_color.b;
1166     }
1167 }
1168 
1169 /*
1170 =================
1171 =
1172 = VL_FadeOut
1173 =
1174 = Fades the current palette to the given color in the given number of steps
1175 =
1176 =================
1177 */
1178 
VL_FadeOut(int start,int end,int red,int green,int blue,int steps)1179 void VL_FadeOut(
1180     int start,
1181     int end,
1182     int red,
1183     int green,
1184     int blue,
1185     int steps)
1186 {
1187     int orig;
1188     int delta;
1189 
1190     ::VL_GetPalette(
1191         0,
1192         256,
1193         &::palette1[0][0]);
1194 
1195     std::uninitialized_copy_n(
1196         &::palette1[0][0],
1197         768,
1198         &::palette2[0][0]);
1199 
1200     //
1201     // fade through intermediate frames
1202     //
1203     for (int i = 0; i < steps; ++i)
1204     {
1205         auto origptr = &::palette1[start][0];
1206         auto newptr = &::palette2[start][0];
1207 
1208         for (int j = start; j <= end; ++j)
1209         {
1210             orig = *origptr++;
1211             delta = red - orig;
1212             *newptr++ = static_cast<uint8_t>(orig + ((delta * i) / steps));
1213 
1214             orig = *origptr++;
1215             delta = green - orig;
1216             *newptr++ = static_cast<uint8_t>(orig + ((delta * i) / steps));
1217 
1218             orig = *origptr++;
1219             delta = blue - orig;
1220             *newptr++ = static_cast<uint8_t>(orig + ((delta * i) / steps));
1221         }
1222 
1223         ::VL_SetPalette(
1224             0,
1225             256,
1226             &::palette2[0][0]);
1227 
1228         ::VL_RefreshScreen();
1229 
1230         if (!::vid_has_vsync)
1231         {
1232             ::VL_WaitVBL(1);
1233         }
1234     }
1235 
1236     //
1237     // final color
1238     //
1239     ::VL_FillPalette(
1240         static_cast<uint8_t>(red),
1241         static_cast<uint8_t>(green),
1242         static_cast<uint8_t>(blue));
1243 
1244     if (!::vid_has_vsync)
1245     {
1246         ::VL_WaitVBL(1);
1247     }
1248 
1249     ::screenfaded = true;
1250 }
1251 
VL_FadeIn(int start,int end,const uint8_t * palette,int steps)1252 void VL_FadeIn(
1253     int start,
1254     int end,
1255     const uint8_t* palette,
1256     int steps)
1257 {
1258     ::VL_GetPalette(
1259         0,
1260         256,
1261         &::palette1[0][0]);
1262 
1263     std::uninitialized_copy_n(
1264         &::palette1[0][0],
1265         768,
1266         &::palette2[0][0]);
1267 
1268     start *= 3;
1269     end = (end * 3) + 2;
1270 
1271     //
1272     // fade through intermediate frames
1273     //
1274     for (int i = 0; i < steps; ++i)
1275     {
1276         for (int j = start; j <= end; ++j)
1277         {
1278             int delta = palette[j] - ::palette1[0][j];
1279 
1280             ::palette2[0][j] =
1281                 static_cast<uint8_t>(::palette1[0][j] + ((delta * i) / steps));
1282         }
1283 
1284         ::VL_SetPalette(
1285             0,
1286             256,
1287             &::palette2[0][0]);
1288 
1289         ::VL_RefreshScreen();
1290 
1291         if (!::vid_has_vsync)
1292         {
1293             ::VL_WaitVBL(1);
1294         }
1295     }
1296 
1297     //
1298     // final color
1299     //
1300     ::VL_SetPalette(0, 256, palette);
1301 
1302     if (!::vid_has_vsync)
1303     {
1304         ::VL_WaitVBL(1);
1305     }
1306 
1307     ::screenfaded = false;
1308 }
1309 
VL_SetPaletteIntensity(int start,int end,const uint8_t * palette,int intensity)1310 void VL_SetPaletteIntensity(
1311     int start,
1312     int end,
1313     const uint8_t* palette,
1314     int intensity)
1315 {
1316     auto cmap = &::palette1[0][0] + (start * 3);
1317 
1318     intensity = 63 - intensity;
1319 
1320     for (int loop = start; loop <= end; ++loop)
1321     {
1322         int red = (*palette++) - intensity;
1323 
1324         if (red < 0)
1325         {
1326             red = 0;
1327         }
1328 
1329         *cmap++ = static_cast<uint8_t>(red);
1330 
1331         int green = *palette++ - intensity;
1332 
1333         if (green < 0)
1334         {
1335             green = 0;
1336         }
1337 
1338         *cmap++ = static_cast<uint8_t>(green);
1339 
1340         int blue = *palette++ - intensity;
1341 
1342         if (blue < 0)
1343         {
1344             blue = 0;
1345         }
1346 
1347         *cmap++ = static_cast<uint8_t>(blue);
1348     }
1349 
1350     ::VL_SetPalette(
1351         start,
1352         end - start + 1,
1353         &::palette1[0][0]);
1354 }
1355 
1356 /*
1357 =============================================================================
1358 
1359  PIXEL OPS
1360 
1361 =============================================================================
1362 */
1363 
VL_Plot(int x,int y,uint8_t color,const bool is_transparent)1364 void VL_Plot(
1365     int x,
1366     int y,
1367     uint8_t color,
1368     const bool is_transparent)
1369 {
1370     const auto offset = (y * ::vga_ref_width) + x;
1371 
1372     ::sdl_ui_buffer[offset] = color;
1373     ::sdl_mask_buffer[offset] = !is_transparent;
1374 }
1375 
VL_Hlin(int x,int y,int width,uint8_t color)1376 void VL_Hlin(
1377     int x,
1378     int y,
1379     int width,
1380     uint8_t color)
1381 {
1382     ::VL_Bar(x, y, width, 1, color);
1383 }
1384 
VL_Vlin(int x,int y,int height,uint8_t color)1385 void VL_Vlin(
1386     int x,
1387     int y,
1388     int height,
1389     uint8_t color)
1390 {
1391     ::VL_Bar(x, y, 1, height, color);
1392 }
1393 
VL_Bar(int x,int y,int width,int height,uint8_t color,const bool is_transparent)1394 void VL_Bar(
1395     int x,
1396     int y,
1397     int width,
1398     int height,
1399     uint8_t color,
1400     const bool is_transparent)
1401 {
1402     if (x == 0 && width == ::vga_ref_width)
1403     {
1404         const auto offset = y * ::vga_ref_width;
1405         const auto count = height * ::vga_ref_width;
1406 
1407         std::uninitialized_fill(
1408             ::sdl_ui_buffer.begin() + offset,
1409             ::sdl_ui_buffer.begin() + offset + count,
1410             color);
1411 
1412         std::uninitialized_fill(
1413             ::sdl_mask_buffer.begin() + offset,
1414             ::sdl_mask_buffer.begin() + offset + count,
1415             !is_transparent);
1416     }
1417     else
1418     {
1419         for (int i = 0; i < height; ++i)
1420         {
1421             const auto offset = ((y + i) * ::vga_ref_width) + x;
1422 
1423             std::uninitialized_fill(
1424                 ::sdl_ui_buffer.begin() + offset,
1425                 ::sdl_ui_buffer.begin() + offset + width,
1426                 color);
1427 
1428             std::uninitialized_fill(
1429                 ::sdl_mask_buffer.begin() + offset,
1430                 ::sdl_mask_buffer.begin() + offset + width,
1431                 !is_transparent);
1432         }
1433     }
1434 }
1435 
1436 /*
1437 ============================================================================
1438 
1439  MEMORY OPS
1440 
1441 ============================================================================
1442 */
1443 
VL_MemToLatch(const uint8_t * source,int width,int height,int dest)1444 void VL_MemToLatch(
1445     const uint8_t* source,
1446     int width,
1447     int height,
1448     int dest)
1449 {
1450     for (int p = 0; p < 4; ++p)
1451     {
1452         for (int h = 0; h < height; ++h)
1453         {
1454             for (int w = p; w < width; w += 4)
1455             {
1456                 const auto pixel = *source++;
1457                 const auto offset = dest + ((h * width) + w);
1458 
1459                 ::latches_cache[offset] = pixel;
1460             }
1461         }
1462     }
1463 }
1464 
VL_MemToScreen(const uint8_t * source,int width,int height,int x,int y)1465 void VL_MemToScreen(
1466     const uint8_t* source,
1467     int width,
1468     int height,
1469     int x,
1470     int y)
1471 {
1472     for (int p = 0; p < 4; ++p)
1473     {
1474         for (int h = 0; h < height; ++h)
1475         {
1476             for (int w = p; w < width; w += 4)
1477             {
1478                 ::VL_Plot(x + w, y + h, *source++);
1479             }
1480         }
1481     }
1482 }
1483 
VL_MaskMemToScreen(const uint8_t * source,int width,int height,int x,int y,uint8_t mask)1484 void VL_MaskMemToScreen(
1485     const uint8_t* source,
1486     int width,
1487     int height,
1488     int x,
1489     int y,
1490     uint8_t mask)
1491 {
1492     for (int p = 0; p < 4; ++p)
1493     {
1494         for (int h = 0; h < height; ++h)
1495         {
1496             for (int w = p; w < width; w += 4)
1497             {
1498                 const auto color = *source++;
1499 
1500                 if (color != mask)
1501                 {
1502                     ::VL_Plot(x + w, y + h, color);
1503                 }
1504             }
1505         }
1506     }
1507 }
1508 
VL_ScreenToMem(uint8_t * dest,int width,int height,int x,int y)1509 void VL_ScreenToMem(
1510     uint8_t* dest,
1511     int width,
1512     int height,
1513     int x,
1514     int y)
1515 {
1516     for (int p = 0; p < 4; ++p)
1517     {
1518         for (int h = 0; h < height; ++h)
1519         {
1520             for (int w = p; w < width; w += 4)
1521             {
1522                 *dest++ = ::vl_get_pixel(::bufferofs, x + w, y + h);
1523             }
1524         }
1525     }
1526 }
1527 
VL_LatchToScreen(int source,int width,int height,int x,int y)1528 void VL_LatchToScreen(
1529     int source,
1530     int width,
1531     int height,
1532     int x,
1533     int y)
1534 {
1535     for (int h = 0; h < height; ++h)
1536     {
1537         const auto src_offset = source + (h * width);
1538         const auto dst_offset = (::vga_ref_width * (y + h)) + x;
1539 
1540         std::uninitialized_copy(
1541             ::latches_cache.cbegin() + src_offset,
1542             ::latches_cache.cbegin() + src_offset + width,
1543             ::sdl_ui_buffer.begin() + dst_offset);
1544 
1545         std::uninitialized_fill(
1546             ::sdl_mask_buffer.begin() + dst_offset,
1547             ::sdl_mask_buffer.begin() + dst_offset + width,
1548             true);
1549     }
1550 }
1551 
VL_ScreenToScreen(int source,int dest,int width,int height)1552 void VL_ScreenToScreen(
1553     int source,
1554     int dest,
1555     int width,
1556     int height)
1557 {
1558     for (int h = 0; h < height; ++h)
1559     {
1560         const auto src_offset = source + (h * ::vga_ref_width);
1561         const auto dst_offset = dest + (h * ::vga_ref_width);
1562 
1563         std::uninitialized_copy(
1564             ::sdl_ui_buffer.cbegin() + src_offset,
1565             ::sdl_ui_buffer.cbegin() + src_offset + width,
1566             ::sdl_ui_buffer.begin() + dst_offset);
1567 
1568         std::uninitialized_fill(
1569             ::sdl_mask_buffer.begin() + dst_offset,
1570             ::sdl_mask_buffer.begin() + dst_offset + width,
1571             true);
1572     }
1573 }
1574 
1575 
JM_VGALinearFill(int start,int length,uint8_t fill)1576 void JM_VGALinearFill(
1577     int start,
1578     int length,
1579     uint8_t fill)
1580 {
1581     std::uninitialized_fill(
1582         ::sdl_ui_buffer.begin() + start,
1583         ::sdl_ui_buffer.begin() + start + length,
1584         fill);
1585 
1586     std::uninitialized_fill(
1587         ::sdl_mask_buffer.begin() + start,
1588         ::sdl_mask_buffer.begin() + start + length,
1589         true);
1590 }
1591 
VL_RefreshScreen()1592 void VL_RefreshScreen()
1593 {
1594     ::sdl_refresh_screen();
1595 }
1596 
VH_UpdateScreen()1597 void VH_UpdateScreen()
1598 {
1599     ::sdl_refresh_screen();
1600 }
1601 
vl_get_offset(int base_offset,int x,int y)1602 int vl_get_offset(
1603     int base_offset,
1604     int x,
1605     int y)
1606 {
1607     return base_offset + (y * ::vga_width) + x;
1608 }
1609 
vl_get_pixel(int base_offset,int x,int y)1610 uint8_t vl_get_pixel(
1611     int base_offset,
1612     int x,
1613     int y)
1614 {
1615     return ::sdl_ui_buffer[(y * ::vga_ref_width) + x];
1616 }
1617 
vl_minimize_fullscreen_window(bool value)1618 void vl_minimize_fullscreen_window(
1619     bool value)
1620 {
1621     if (value)
1622     {
1623         ::SDL_MinimizeWindow(
1624             ::sdl_window);
1625     }
1626     else
1627     {
1628         ::SDL_RestoreWindow(
1629             ::sdl_window);
1630     }
1631 }
1632 
vl_update_widescreen()1633 void vl_update_widescreen()
1634 {
1635     ::sdl_update_widescreen();
1636 }
1637 
vid_set_ui_mask(bool value)1638 void vid_set_ui_mask(
1639     bool value)
1640 {
1641     ::sdl_mask_buffer.fill(
1642         value);
1643 }
1644 
vid_set_ui_mask(int x,int y,int width,int height,bool value)1645 void vid_set_ui_mask(
1646     int x,
1647     int y,
1648     int width,
1649     int height,
1650     bool value)
1651 {
1652     for (int h = 0; h < height; ++h)
1653     {
1654         const auto offset = ((y + h) * ::vga_ref_width) + x;
1655 
1656         std::uninitialized_fill(
1657             ::sdl_mask_buffer.begin() + offset,
1658             ::sdl_mask_buffer.begin() + offset + width,
1659             value);
1660     }
1661 }
1662 
vid_set_ui_mask_3d(bool value)1663 void vid_set_ui_mask_3d(
1664     bool value)
1665 {
1666     ::vid_set_ui_mask(
1667         0,
1668         ::ref_3d_view_top,
1669         ::vga_ref_width,
1670         ::ref_3d_view_height,
1671         value);
1672 }
1673 
vid_clear_3d()1674 void vid_clear_3d()
1675 {
1676     std::uninitialized_fill(
1677         ::sdl_vga_buffer.begin(),
1678         ::sdl_vga_buffer.end(),
1679         0);
1680 }
1681 
vid_export_ui(VgaBuffer & dst_buffer)1682 void vid_export_ui(
1683     VgaBuffer& dst_buffer)
1684 {
1685     dst_buffer = ::sdl_ui_buffer;
1686 }
1687 
vid_import_ui(const VgaBuffer & src_buffer,bool is_transparent)1688 void vid_import_ui(
1689     const VgaBuffer& src_buffer,
1690     bool is_transparent)
1691 {
1692     ::sdl_ui_buffer = src_buffer;
1693     ::vid_set_ui_mask(!is_transparent);
1694 }
1695 
vid_export_ui_mask(UiMaskBuffer & dst_buffer)1696 void vid_export_ui_mask(
1697     UiMaskBuffer& dst_buffer)
1698 {
1699     dst_buffer = ::sdl_mask_buffer;
1700 }
1701 
vid_import_ui_mask(const UiMaskBuffer & src_buffer)1702 void vid_import_ui_mask(
1703     const UiMaskBuffer& src_buffer)
1704 {
1705     ::sdl_mask_buffer = src_buffer;
1706 }
1707 
vid_draw_ui_sprite(const int sprite_id,const int center_x,const int center_y,const int new_side)1708 void vid_draw_ui_sprite(
1709     const int sprite_id,
1710     const int center_x,
1711     const int center_y,
1712     const int new_side)
1713 {
1714     constexpr auto side = bstone::Sprite::side;
1715 
1716     const auto sprite_ptr = ::vid_sprite_cache.cache(
1717         sprite_id);
1718 
1719     const auto sprite_width = sprite_ptr->get_width();
1720     const auto sprite_height = sprite_ptr->get_height();
1721 
1722     const auto left = sprite_ptr->get_left();
1723     const auto x1 = center_x + ((new_side * (left - (side / 2))) / side);
1724     const auto x2 = x1 + ((sprite_width * new_side) / side);
1725 
1726     const auto top = sprite_ptr->get_top();
1727     const auto y1 = center_y + ((new_side * (top - (side / 2))) / side) - 2;
1728     const auto y2 = y1 + ((sprite_height * new_side) / side);
1729 
1730     for (int x = x1; x < x2; ++x)
1731     {
1732         if (x < 0)
1733         {
1734             continue;
1735         }
1736 
1737         if (x >= ::vga_ref_width)
1738         {
1739             break;
1740         }
1741 
1742         const auto column_index = ((sprite_width - 1) * (x - x1)) / (x2 - x1 - 1);
1743         const auto column = sprite_ptr->get_column(column_index);
1744 
1745         for (int y = y1; y < y2; ++y)
1746         {
1747             if (y < 0)
1748             {
1749                 continue;
1750             }
1751 
1752             if (y >= ::vga_ref_height)
1753             {
1754                 break;
1755             }
1756 
1757             const auto row_index = ((sprite_height - 1) * (y - y1)) / (y2 - y1 - 1);
1758             const auto sprite_color = column[row_index];
1759 
1760             if (sprite_color < 0)
1761             {
1762                 continue;
1763             }
1764 
1765             const auto color_index = static_cast<uint8_t>(sprite_color);
1766 
1767             ::VL_Plot(x, y, color_index);
1768         }
1769     }
1770 }
1771 // BBi
1772