1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
4  * All source code herein is the property of Volition, Inc. You may not sell
5  * or otherwise commercially exploit the source or things you created based on the
6  * source.
7  *
8  */
9 
10 #ifdef _WIN32
11 #include <windows.h>
12 #include <windowsx.h>
13 #endif
14 
15 #include "globalincs/alphacolors.h"
16 #include "globalincs/systemvars.h"
17 
18 #include "2d.h"
19 #include "grinternal.h"
20 #include "grstub.h"
21 #include "light.h"
22 #include "material.h"
23 #include "matrix.h"
24 
25 #include "cmdline/cmdline.h"
26 #include "debugconsole/console.h"
27 #include "executor/global_executors.h"
28 #include "graphics/paths/PathRenderer.h"
29 #include "graphics/post_processing.h"
30 #include "graphics/util/GPUMemoryHeap.h"
31 #include "graphics/util/UniformBuffer.h"
32 #include "graphics/util/UniformBufferManager.h"
33 #include "io/mouse.h"
34 #include "libs/jansson.h"
35 #include "options/Option.h"
36 #include "osapi/osapi.h"
37 #include "parse/parselo.h"
38 #include "popup/popup.h"
39 #include "render/3d.h"
40 #include "scripting/hook_api.h"
41 #include "scripting/scripting.h"
42 #include "tracing/tracing.h"
43 #include "utils/boost/hash_combine.h"
44 #include "gamesequence/gamesequence.h"
45 
46 #ifdef WITH_OPENGL
47 #include "graphics/opengl/gropengl.h"
48 #endif
49 #ifdef WITH_VULKAN
50 #include "graphics/vulkan/gr_vulkan.h"
51 #endif
52 
53 #include <SDL_surface.h>
54 
55 #include <algorithm>
56 #include <climits>
57 
58 #if (SDL_VERSION_ATLEAST(1, 2, 7))
59 #include "SDL_cpuinfo.h"
60 #endif
61 
62 const char* Resolution_prefixes[GR_NUM_RESOLUTIONS] = {"", "2_"};
63 
64 screen gr_screen;
65 
66 color_gun Gr_red, Gr_green, Gr_blue, Gr_alpha;
67 color_gun Gr_t_red, Gr_t_green, Gr_t_blue, Gr_t_alpha;
68 color_gun Gr_ta_red, Gr_ta_green, Gr_ta_blue, Gr_ta_alpha;
69 color_gun *Gr_current_red, *Gr_current_green, *Gr_current_blue, *Gr_current_alpha;
70 
71 
72 ubyte Gr_original_palette[768];		// The palette
73 ubyte Gr_current_palette[768];
74 char Gr_current_palette_name[128] = NOX("none");
75 
76 // cursor stuff
77 io::mouse::Cursor* Web_cursor = NULL;
78 
79 int Gr_inited = 0;
80 
81 uint Gr_signature = 0;
82 
83 float Gr_gamma = 1.0f;
84 
gamma_value_enumerator()85 static SCP_vector<float> gamma_value_enumerator()
86 {
87 	SCP_vector<float> vals;
88 	// We want to divide the possible values into increments of 0.05
89 	constexpr auto UPPER_LIMIT = (int)(5.0 / 0.05);
90 
91 	for (int i = 2; i <= UPPER_LIMIT; ++i) {
92 		vals.push_back(0.05f * i);
93 	}
94 
95 	return vals;
96 }
gamma_display(float value)97 static SCP_string gamma_display(float value)
98 {
99 	SCP_string out;
100 	sprintf(out, "%.2f", value);
101 	return out;
102 }
103 
gamma_change_listener(float new_val,bool initial)104 static bool gamma_change_listener(float new_val, bool initial)
105 {
106 	if (!initial) {
107 		// This is not valid for the initial config load since that happens before the graphics system is initialized
108 		gr_set_gamma(new_val);
109 	} else {
110 		Gr_gamma = new_val;
111 	}
112 
113 	return true;
114 }
115 
116 static auto GammaOption =
117     options::OptionBuilder<float>("Graphics.Gamma", "Brightness", "The brighness value used for the game window")
118         .category("Graphics")
119         .default_val(1.0f)
120         .enumerator(gamma_value_enumerator)
121         .display(gamma_display)
122         .change_listener(gamma_change_listener)
123         .finish();
124 
125 
126 const SCP_vector<std::pair<int, SCP_string>> DetailLevelValues = {{ 0, "Minimum" },
127                                                                   { 1, "Low" },
128                                                                   { 2, "Medium" },
129                                                                   { 3, "High" },
130                                                                   { 4, "Ultra" }, };
131 
132 const auto LightingOption = options::OptionBuilder<int>("Graphics.Lighting", "Lighting", "Level of detail of the lighting")
133 		.importance(1)
134 		.category("Graphics")
135 		.values(DetailLevelValues)
136 		.default_val(MAX_DETAIL_LEVEL)
__anona0b94e200102(int val, bool initial) 137 		.change_listener([](int val, bool initial) {
138 			Detail.lighting = val;
139 			if (!initial) {
140 				gr_recompile_all_shaders(nullptr);
141 			}
142 			return true;
143 		})
144 		.finish();
145 
146 os::ViewportState Gr_configured_window_state = os::ViewportState::Windowed;
147 
mode_change_func(os::ViewportState state,bool initial)148 static bool mode_change_func(os::ViewportState state, bool initial)
149 {
150 	Gr_configured_window_state = state;
151 
152 	if (initial) {
153 		return false;
154 	}
155 
156 	auto window = os::getMainViewport();
157 	if (window == nullptr) {
158 		return false;
159 	}
160 
161 	window->setState(state);
162 
163 	return true;
164 }
165 
166 static auto WindowModeOption = options::OptionBuilder<os::ViewportState>("Graphics.WindowMode", "Window Mode",
167 																		 "Controls how the game window is created.")
168 								   .category("Graphics")
169 								   .level(options::ExpertLevel::Beginner)
170 								   .values({{os::ViewportState::Fullscreen, "Fullscreen"},
171 											{os::ViewportState::Borderless, "Borderless"},
172 											{os::ViewportState::Windowed, "Windowed"}})
173 								   .importance(98)
174 								   .default_val(os::ViewportState::Fullscreen)
175 								   .change_listener(mode_change_func)
176 								   .finish();
177 
178 const std::shared_ptr<scripting::OverridableHook> OnFrameHook = scripting::OverridableHook::Factory(
179 	"On Frame", "Called every frame as the last action before showing the frame result to the user.", {}, CHA_ONFRAME);
180 
181 // z-buffer stuff
182 int gr_zbuffering        = 0;
183 int gr_zbuffering_mode   = 0;
184 int gr_global_zbuffering = 0;
185 
186 // stencil buffer stuff
187 int gr_stencil_mode = 0;
188 
189 // Default clipping distances
190 const float Default_min_draw_distance = 1.0f;
191 const float Default_max_draw_distance = 1e10;
192 float Min_draw_distance = Default_min_draw_distance;
193 float Max_draw_distance = Default_max_draw_distance;
194 
195 // Pre-computed screen resize vars
196 static float Gr_full_resize_X = 1.0f, Gr_full_resize_Y = 1.0f;
197 static float Gr_full_center_resize_X = 1.0f, Gr_full_center_resize_Y = 1.0f;
198 static float Gr_resize_X = 1.0f, Gr_resize_Y = 1.0f;
199 static float Gr_menu_offset_X = 0.0f, Gr_menu_offset_Y = 0.0f;
200 static float Gr_menu_zoomed_offset_X = 0.0f, Gr_menu_zoomed_offset_Y = 0.0f;
201 
202 float Gr_save_full_resize_X = 1.0f, Gr_save_full_resize_Y = 1.0f;
203 float Gr_save_full_center_resize_X = 1.0f, Gr_save_full_center_resize_Y = 1.0f;
204 float Gr_save_resize_X = 1.0f, Gr_save_resize_Y = 1.0f;
205 float Gr_save_menu_offset_X = 0.0f, Gr_save_menu_offset_Y = 0.0f;
206 float Gr_save_menu_zoomed_offset_X = 0.0f, Gr_save_menu_zoomed_offset_Y = 0.0f;
207 
208 bool Save_custom_screen_size;
209 
210 bool Deferred_lighting = false;
211 bool High_dynamic_range = false;
212 
213 static ushort* Gr_original_gamma_ramp = nullptr;
214 
videodisplay_deserializer(const json_t * value)215 static int videodisplay_deserializer(const json_t* value)
216 {
217 	int id;
218 
219 	json_error_t err;
220 	if (json_unpack_ex((json_t*)value, &err, 0, "i", &id) != 0) {
221 		throw json_exception(err);
222 	}
223 
224 	return id;
225 }
videodisplay_serializer(int value)226 static json_t* videodisplay_serializer(int value) { return json_pack("i", value); }
videodisplay_enumerator()227 static SCP_vector<int> videodisplay_enumerator()
228 {
229 	SCP_vector<int> vals;
230 	for (int i = 0; i < SDL_GetNumVideoDisplays(); ++i) {
231 		vals.push_back(i);
232 	}
233 	return vals;
234 }
videodisplay_display(int id)235 static SCP_string videodisplay_display(int id)
236 {
237 	SCP_string out;
238 	sprintf(out, "(%d) %s", id + 1, SDL_GetDisplayName(id));
239 	return out;
240 }
videodisplay_change(int display,bool initial)241 static bool videodisplay_change(int display, bool initial)
242 {
243 	if (initial) {
244 		return false;
245 	}
246 
247 	auto window = os::getSDLMainWindow();
248 	if (window == nullptr) {
249 		return false;
250 	}
251 
252 	SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED_DISPLAY(display), SDL_WINDOWPOS_CENTERED_DISPLAY(display));
253 	return true;
254 }
255 static auto VideoDisplayOption =
256     options::OptionBuilder<int>("Graphics.Display", "Primary display", "The display used for rendering.")
257         .category("Graphics")
258         .level(options::ExpertLevel::Beginner)
259         .deserializer(videodisplay_deserializer)
260         .serializer(videodisplay_serializer)
261         .enumerator(videodisplay_enumerator)
262         .display(videodisplay_display)
263         .default_val(0)
264         .change_listener(videodisplay_change)
265         .importance(99)
266         .finish();
267 
268 struct ResolutionInfo {
269 	uint32_t width  = 0;
270 	uint32_t height = 0;
ResolutionInfoResolutionInfo271 	ResolutionInfo(uint32_t _width, uint32_t _height) : width(_width), height(_height) {}
272 	ResolutionInfo() = default;
operator ==(const ResolutionInfo & lhs,const ResolutionInfo & rhs)273 	friend bool operator==(const ResolutionInfo& lhs, const ResolutionInfo& rhs)
274 	{
275 		return lhs.width == rhs.width && lhs.height == rhs.height;
276 	}
operator !=(const ResolutionInfo & lhs,const ResolutionInfo & rhs)277 	friend bool operator!=(const ResolutionInfo& lhs, const ResolutionInfo& rhs) { return !(rhs == lhs); }
278 };
279 
resolution_deserializer(const json_t * el)280 static ResolutionInfo resolution_deserializer(const json_t* el)
281 {
282 	int width;
283 	int height;
284 
285 	json_error_t err;
286 	if (json_unpack_ex((json_t*)el, &err, 0, "{s:i, s:i}", "width", &width, "height", &height) != 0) {
287 		throw json_exception(err);
288 	}
289 
290 	return {(uint32_t)width, (uint32_t)height};
291 }
resolution_serializer(const ResolutionInfo & value)292 static json_t* resolution_serializer(const ResolutionInfo& value)
293 {
294 	return json_pack("{s:i, s:i}", "width", value.width, "height", value.height);
295 }
resolution_enumerator()296 static SCP_vector<ResolutionInfo> resolution_enumerator()
297 {
298 	SCP_vector<ResolutionInfo> out;
299 	auto display = VideoDisplayOption->getValue();
300 	for (auto i = 0; i < SDL_GetNumDisplayModes(display); ++i) {
301 		SDL_DisplayMode mode;
302 		if (SDL_GetDisplayMode(display, i, &mode) != 0) {
303 			continue;
304 		}
305 
306 		auto res = ResolutionInfo(mode.w, mode.h);
307 		if (std::find(out.begin(), out.end(), res) == out.end()) {
308 			out.push_back(res);
309 		}
310 	}
311 
312 	return out;
313 }
resolution_display(const ResolutionInfo & info)314 static SCP_string resolution_display(const ResolutionInfo& info)
315 {
316 	SCP_string str;
317 	sprintf(str, "%dx%d", info.width, info.height);
318 	return str;
319 }
resolution_default()320 static ResolutionInfo resolution_default()
321 {
322 	SDL_DisplayMode mode;
323 	if (SDL_GetDesktopDisplayMode(VideoDisplayOption->getValue(), &mode) != 0) {
324 		return {};
325 	}
326 	return {(uint32_t)mode.w, (uint32_t)mode.h};
327 }
resolution_change(const ResolutionInfo &,bool initial)328 static bool resolution_change(const ResolutionInfo& /*info*/, bool initial)
329 {
330 	if (initial) {
331 		return false;
332 	}
333 	return false;
334 	// The following code should change the size of the window properly but FSO currently can't handle that
335 	/*
336 	auto window = os::getSDLMainWindow();
337 	if (window == nullptr) {
338 	    return;
339 	}
340 
341 	auto display = VideoDisplayOption->getValue();
342 	if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) {
343 	    SDL_DisplayMode target;
344 	    target.w            = info.width;
345 	    target.h            = info.height;
346 	    target.format       = 0; // don't care
347 	    target.refresh_rate = 0; // don't care
348 	    target.driverdata   = 0; // initialize to 0
349 
350 	    SDL_DisplayMode closest;
351 	    if (SDL_GetClosestDisplayMode(display, &target, &closest) == nullptr) {
352 	        return;
353 	    }
354 
355 	    SDL_SetWindowDisplayMode(window, &closest);
356 	} else {
357 	    SDL_SetWindowSize(window, info.width, info.height);
358 	    // Recenter the window
359 	    SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED_DISPLAY(display), SDL_WINDOWPOS_CENTERED_DISPLAY(display));
360 	}
361 	 */
362 }
363 static auto ResolutionOption =
364     options::OptionBuilder<ResolutionInfo>("Graphics.Resolution", "Resolution", "The rendering resolution.")
365         .category("Graphics")
366         .level(options::ExpertLevel::Beginner)
367         .deserializer(resolution_deserializer)
368         .serializer(resolution_serializer)
369         .enumerator(resolution_enumerator)
370         .display(resolution_display)
371         .default_func(resolution_default)
372         .change_listener(resolution_change)
373         .importance(100)
374         .finish();
375 
376 bool Gr_enable_soft_particles = false;
377 
378 static auto SoftParticlesOption = options::OptionBuilder<bool>("Graphics.SoftParticles", "Soft Particles",
379                                                                "Enable or disable soft particle rendering.")
380                                       .category("Graphics")
381                                       .level(options::ExpertLevel::Advanced)
382                                       .default_val(true)
383                                       .bind_to_once(&Gr_enable_soft_particles)
384                                       .importance(68)
385                                       .finish();
386 
387 flagset<FramebufferEffects> Gr_framebuffer_effects;
388 
389 static auto FramebufferEffectsOption =
390     options::OptionBuilder<flagset<FramebufferEffects>>(
391         "Graphics.FramebufferEffects", "Framebuffer effects",
392         "Controls which framebuffer effects will be applied to the scene.")
393         .category("Graphics")
394         .level(options::ExpertLevel::Advanced)
395         .values({{{}, "None"},
396                  {{FramebufferEffects::Shockwaves}, "Shockwaves"},
397                  {{FramebufferEffects::Thrusters}, "Thrusters"},
398                  {{FramebufferEffects::Shockwaves, FramebufferEffects::Thrusters}, "All"}})
399         .default_val({FramebufferEffects::Shockwaves, FramebufferEffects::Thrusters})
400         .bind_to_once(&Gr_framebuffer_effects)
401         .importance(77)
402         .finish();
403 
404 AntiAliasMode Gr_aa_mode = AntiAliasMode::None;
405 AntiAliasMode Gr_aa_mode_last_frame = AntiAliasMode::None;
406 
407 static auto AAOption = options::OptionBuilder<AntiAliasMode>("Graphics.AAMode", "Anti Aliasing",
408                                                              "Controls the anti aliasing mode of the engine.")
409                            .category("Graphics")
410                            .level(options::ExpertLevel::Advanced)
411                            .values({{AntiAliasMode::None, "None"},
412                                     {AntiAliasMode::FXAA_Low, "FXAA Low"},
413                                     {AntiAliasMode::FXAA_Medium, "FXAA Medium"},
414                                     {AntiAliasMode::FXAA_High, "FXAA High"},
415                                     {AntiAliasMode::SMAA_Low, "SMAA Low"},
416                                     {AntiAliasMode::SMAA_Medium, "SMAA Medium"},
417                                     {AntiAliasMode::SMAA_High, "SMAA High"},
418                                     {AntiAliasMode::SMAA_Ultra, "SMAA Ultra"}})
419                            .default_val(AntiAliasMode::None)
420                            .bind_to(&Gr_aa_mode)
421                            .importance(79)
422                            .finish();
423 
gr_is_fxaa_mode(AntiAliasMode mode)424 bool gr_is_fxaa_mode(AntiAliasMode mode)
425 {
426 	return mode == AntiAliasMode::FXAA_Low || mode == AntiAliasMode::FXAA_Medium || mode == AntiAliasMode::FXAA_High;
427 }
gr_is_smaa_mode(AntiAliasMode mode)428 bool gr_is_smaa_mode(AntiAliasMode mode) {
429 	return mode == AntiAliasMode::SMAA_Low || mode == AntiAliasMode::SMAA_Medium || mode == AntiAliasMode::SMAA_High || mode == AntiAliasMode::SMAA_Ultra;
430 }
431 
432 bool Gr_post_processing_enabled = true;
433 
434 static auto PostProcessOption =
435     options::OptionBuilder<bool>("Graphis.PostProcessing", "Post processing",
436                                  "Controls whether post processing is enabled in the engine")
437         .category("Graphics")
438         .level(options::ExpertLevel::Advanced)
439         .default_val(false)
440         .bind_to_once(&Gr_post_processing_enabled)
441         .importance(69)
442         .finish();
443 
444 bool Gr_enable_vsync = true;
445 
446 static auto VSyncOption = options::OptionBuilder<bool>("Graphis.VSync", "Vertical Sync",
447                                                        "Controls how the engine does vertical synchronization")
448                               .category("Graphics")
449                               .level(options::ExpertLevel::Advanced)
450                               .default_val(true)
451                               .bind_to_once(&Gr_enable_vsync)
452                               .importance(70)
453                               .finish();
454 
455 static std::unique_ptr<graphics::util::UniformBufferManager> UniformBufferManager;
456 
457 // Forward definitions
458 static void uniform_buffer_managers_init();
459 static void uniform_buffer_managers_deinit();
460 static void uniform_buffer_managers_retire_buffers();
461 
462 static void gpu_heap_init();
463 static void gpu_heap_deinit();
464 
gr_set_screen_scale(int w,int h,int zoom_w,int zoom_h,int max_w,int max_h,int center_w,int center_h,bool force_stretch)465 void gr_set_screen_scale(int w, int h, int zoom_w, int zoom_h, int max_w, int max_h, int center_w, int center_h,
466                          bool force_stretch)
467 {
468 	bool do_zoom = zoom_w > 0 && zoom_h > 0 && (zoom_w != w || zoom_h != h);
469 
470 	Gr_full_resize_X = (float)max_w / (float)w;
471 	Gr_full_resize_Y = (float)max_h / (float)h;
472 
473 	Gr_full_center_resize_X = (float)center_w / (float)w;
474 	Gr_full_center_resize_Y = (float)center_h / (float)h;
475 
476 	if (do_zoom) {
477 		float aspect_quotient = ((float)center_w / (float)center_h) / ((float)zoom_w / (float)zoom_h);
478 
479 		Gr_resize_X = (float)center_w / (float)zoom_w / ((aspect_quotient > 1.0f) ? aspect_quotient : 1.0f);
480 		Gr_resize_Y = (float)center_h / (float)zoom_h * ((aspect_quotient < 1.0f) ? aspect_quotient : 1.0f);
481 
482 		Gr_menu_offset_X = ((center_w - w * Gr_resize_X) / 2.0f) + gr_screen.center_offset_x;
483 		Gr_menu_offset_Y = ((center_h - h * Gr_resize_Y) / 2.0f) + gr_screen.center_offset_y;
484 
485 		Gr_menu_zoomed_offset_X = (Gr_menu_offset_X >= 0.0f) ? Gr_menu_offset_X : gr_screen.center_offset_x;
486 		Gr_menu_zoomed_offset_Y = (Gr_menu_offset_Y >= 0.0f) ? Gr_menu_offset_Y : gr_screen.center_offset_y;
487 
488 		if (force_stretch || Cmdline_stretch_menu) {
489 			if (Gr_menu_offset_X > (float)gr_screen.center_offset_x) {
490 				Gr_resize_X = Gr_full_center_resize_X;
491 				Gr_menu_offset_X = Gr_menu_zoomed_offset_X = (float)gr_screen.center_offset_x;
492 			}
493 			if (Gr_menu_offset_Y > (float)gr_screen.center_offset_y) {
494 				Gr_resize_Y = Gr_full_center_resize_Y;
495 				Gr_menu_offset_Y = Gr_menu_zoomed_offset_Y = (float)gr_screen.center_offset_y;
496 			}
497 		}
498 	} else {
499 		if (force_stretch || Cmdline_stretch_menu) {
500 			Gr_resize_X = Gr_full_center_resize_X;
501 			Gr_resize_Y = Gr_full_center_resize_Y;
502 
503 			Gr_menu_offset_X = Gr_menu_zoomed_offset_X = (float)gr_screen.center_offset_x;
504 			Gr_menu_offset_Y = Gr_menu_zoomed_offset_Y = (float)gr_screen.center_offset_y;
505 		} else {
506 			float aspect_quotient = ((float)center_w / (float)center_h) / ((float)w / (float)h);
507 
508 			Gr_resize_X = Gr_full_center_resize_X / ((aspect_quotient > 1.0f) ? aspect_quotient : 1.0f);
509 			Gr_resize_Y = Gr_full_center_resize_Y * ((aspect_quotient < 1.0f) ? aspect_quotient : 1.0f);
510 
511 			Gr_menu_offset_X = Gr_menu_zoomed_offset_X = ((aspect_quotient > 1.0f) ? ((center_w - w * Gr_resize_X) / 2.0f) : 0.0f) + gr_screen.center_offset_x;
512 			Gr_menu_offset_Y = Gr_menu_zoomed_offset_Y = ((aspect_quotient < 1.0f) ? ((center_h - h * Gr_resize_Y) / 2.0f) : 0.0f) + gr_screen.center_offset_y;
513 		}
514 	}
515 
516 	gr_screen.custom_size = (w != max_w || w != center_w || h != max_h || h != center_h);
517 
518 	if (gr_screen.rendering_to_texture == -1) {
519 		gr_screen.max_w_unscaled = w;
520 		gr_screen.max_h_unscaled = h;
521 
522 		if (do_zoom) {
523 			gr_screen.max_w_unscaled_zoomed = gr_screen.max_w_unscaled + fl2i(Gr_menu_offset_X * 2.0f / Gr_resize_X);
524 			gr_screen.max_h_unscaled_zoomed = gr_screen.max_h_unscaled + fl2i(Gr_menu_offset_Y * 2.0f / Gr_resize_Y);
525 			if (gr_screen.max_w_unscaled_zoomed > gr_screen.max_w_unscaled) {
526 				gr_screen.max_w_unscaled_zoomed = gr_screen.max_w_unscaled;
527 			}
528 			if (gr_screen.max_h_unscaled_zoomed > gr_screen.max_h_unscaled) {
529 				gr_screen.max_h_unscaled_zoomed = gr_screen.max_h_unscaled;
530 			}
531 		} else {
532 			gr_screen.max_w_unscaled_zoomed = gr_screen.max_w_unscaled;
533 			gr_screen.max_h_unscaled_zoomed = gr_screen.max_h_unscaled;
534 		}
535 	}
536 }
537 
gr_reset_screen_scale()538 void gr_reset_screen_scale()
539 {
540 	Gr_full_resize_X = Gr_save_full_resize_X;
541 	Gr_full_resize_Y = Gr_save_full_resize_Y;
542 
543 	Gr_full_center_resize_X = Gr_save_full_center_resize_X;
544 	Gr_full_center_resize_Y = Gr_save_full_center_resize_Y;
545 
546 	Gr_resize_X = Gr_save_resize_X;
547 	Gr_resize_Y = Gr_save_resize_Y;
548 
549 	Gr_menu_offset_X = Gr_save_menu_offset_X;
550 	Gr_menu_offset_Y = Gr_save_menu_offset_Y;
551 
552 	Gr_menu_zoomed_offset_X = Gr_save_menu_zoomed_offset_X;
553 	Gr_menu_zoomed_offset_Y = Gr_save_menu_zoomed_offset_Y;
554 
555 	gr_screen.custom_size = Save_custom_screen_size;
556 
557 	if (gr_screen.rendering_to_texture == -1) {
558 		gr_screen.max_w_unscaled = gr_screen.max_w_unscaled_zoomed = (gr_screen.res == GR_1024) ? 1024 : 640;
559 		gr_screen.max_h_unscaled = gr_screen.max_h_unscaled_zoomed = (gr_screen.res == GR_1024) ?  768 : 480;
560 	}
561 }
562 
563 /**
564  * This function is to be called if you wish to scale GR_1024 or GR_640 x and y positions or
565  * lengths in order to keep the correctly scaled to nonstandard resolutions
566  *
567  * @param x X value, can be NULL
568  * @param y Y value, can be NULL
569  * @param w width, can be NULL
570  * @param h height, can be NULL
571  * @param resize_mode
572  * @return always true unless error
573  */
gr_resize_screen_pos(int * x,int * y,int * w,int * h,int resize_mode)574 bool gr_resize_screen_pos(int *x, int *y, int *w, int *h, int resize_mode)
575 {
576 	if ( resize_mode == GR_RESIZE_NONE || (!gr_screen.custom_size && (gr_screen.rendering_to_texture == -1)) ) {
577 		return false;
578 	}
579 
580 	float xy_tmp = 0.0f;
581 
582 	if ( x ) {
583 		switch (resize_mode) {
584 		case GR_RESIZE_FULL:
585 			xy_tmp = (*x) * Gr_full_resize_X;
586 			break;
587 
588 		case GR_RESIZE_FULL_CENTER:
589 			xy_tmp = (*x) * Gr_full_center_resize_X + (float)gr_screen.center_offset_x;
590 			break;
591 
592 		case GR_RESIZE_MENU:
593 			xy_tmp = (*x) * Gr_resize_X + Gr_menu_offset_X;
594 			break;
595 
596 		case GR_RESIZE_MENU_ZOOMED:
597 			xy_tmp = (*x) * Gr_resize_X + Gr_menu_zoomed_offset_X;
598 			break;
599 
600 		case GR_RESIZE_MENU_NO_OFFSET:
601 			xy_tmp = (*x) * Gr_resize_X;
602 			break;
603 	}
604 		(*x) = fl2ir(xy_tmp);
605 	}
606 
607 	if ( y ) {
608 		switch (resize_mode) {
609 		case GR_RESIZE_FULL:
610 			xy_tmp = (*y) * Gr_full_resize_Y;
611 			break;
612 
613 		case GR_RESIZE_FULL_CENTER:
614 			xy_tmp = (*y) * Gr_full_center_resize_Y + (float)gr_screen.center_offset_y;
615 			break;
616 
617 		case GR_RESIZE_MENU:
618 			xy_tmp = (*y) * Gr_resize_Y + Gr_menu_offset_Y;
619 			break;
620 
621 		case GR_RESIZE_MENU_ZOOMED:
622 			xy_tmp = (*y) * Gr_resize_Y + Gr_menu_zoomed_offset_Y;
623 			break;
624 
625 		case GR_RESIZE_MENU_NO_OFFSET:
626 			xy_tmp = (*y) * Gr_resize_Y;
627 			break;
628 	}
629 		(*y) = fl2ir(xy_tmp);
630 	}
631 
632 	if ( w ) {
633 		switch (resize_mode) {
634 		case GR_RESIZE_FULL:
635 			xy_tmp = (*w) * Gr_full_resize_X;
636 			break;
637 
638 		case GR_RESIZE_FULL_CENTER:
639 			xy_tmp = (*w) * Gr_full_center_resize_X;
640 			break;
641 
642 		case GR_RESIZE_MENU:
643 		case GR_RESIZE_MENU_ZOOMED:
644 		case GR_RESIZE_MENU_NO_OFFSET:
645 			xy_tmp = (*w) * Gr_resize_X;
646 			break;
647 		}
648 		(*w) = fl2ir(xy_tmp);
649 	}
650 
651 	if ( h ) {
652 		switch (resize_mode) {
653 		case GR_RESIZE_FULL:
654 			xy_tmp = (*h) * Gr_full_resize_Y;
655 			break;
656 
657 		case GR_RESIZE_FULL_CENTER:
658 			xy_tmp = (*h) * Gr_full_center_resize_Y;
659 			break;
660 
661 		case GR_RESIZE_MENU:
662 		case GR_RESIZE_MENU_ZOOMED:
663 		case GR_RESIZE_MENU_NO_OFFSET:
664 			xy_tmp = (*h) * Gr_resize_Y;
665 			break;
666 		}
667 		(*h) = fl2ir(xy_tmp);
668 	}
669 
670 	return true;
671 }
672 
673 /**
674  *
675  * @param x X value, can be NULL
676  * @param y Y value, can be NULL
677  * @param w width, can be NULL
678  * @param h height, can be NULL
679  * @param resize_mode
680  * @return always true unless error
681  */
gr_unsize_screen_pos(int * x,int * y,int * w,int * h,int resize_mode)682 bool gr_unsize_screen_pos(int *x, int *y, int *w, int *h, int resize_mode)
683 {
684 	if ( resize_mode == GR_RESIZE_NONE || (!gr_screen.custom_size && (gr_screen.rendering_to_texture == -1)) ) {
685 		return false;
686 	}
687 
688 	float xy_tmp = 0.0f;
689 
690 	if ( x ) {
691 		switch (resize_mode) {
692 		case GR_RESIZE_FULL:
693 			xy_tmp = (*x) / Gr_full_resize_X;
694 			break;
695 
696 		case GR_RESIZE_FULL_CENTER:
697 			xy_tmp = ((*x) - (float)gr_screen.center_offset_x) / Gr_full_center_resize_X;
698 			break;
699 
700 		case GR_RESIZE_MENU:
701 			xy_tmp = ((*x) - Gr_menu_offset_X) / Gr_resize_X;
702 			break;
703 
704 		case GR_RESIZE_MENU_ZOOMED:
705 			xy_tmp = ((*x) - Gr_menu_zoomed_offset_X) / Gr_resize_X;
706 			break;
707 
708 		case GR_RESIZE_MENU_NO_OFFSET:
709 			xy_tmp = (*x) / Gr_resize_X;
710 			break;
711 	}
712 		(*x) = fl2ir(xy_tmp);
713 	}
714 
715 	if ( y ) {
716 		switch (resize_mode) {
717 		case GR_RESIZE_FULL:
718 			xy_tmp = (*y) / Gr_full_resize_Y;
719 			break;
720 
721 		case GR_RESIZE_FULL_CENTER:
722 			xy_tmp = ((*y) - (float)gr_screen.center_offset_y) / Gr_full_center_resize_Y;
723 			break;
724 
725 		case GR_RESIZE_MENU:
726 			xy_tmp = ((*y) - Gr_menu_offset_Y) / Gr_resize_Y;
727 			break;
728 
729 		case GR_RESIZE_MENU_ZOOMED:
730 			xy_tmp = ((*y) - Gr_menu_zoomed_offset_Y) / Gr_resize_Y;
731 			break;
732 
733 		case GR_RESIZE_MENU_NO_OFFSET:
734 			xy_tmp = (*y) / Gr_resize_Y;
735 			break;
736 	}
737 		(*y) = fl2ir(xy_tmp);
738 	}
739 
740 	if ( w ) {
741 		switch (resize_mode) {
742 		case GR_RESIZE_FULL:
743 			xy_tmp = (*w) / Gr_full_resize_X;
744 			break;
745 
746 		case GR_RESIZE_FULL_CENTER:
747 			xy_tmp = (*w) / Gr_full_center_resize_X;
748 			break;
749 
750 		case GR_RESIZE_MENU:
751 		case GR_RESIZE_MENU_ZOOMED:
752 		case GR_RESIZE_MENU_NO_OFFSET:
753 			xy_tmp = (*w) / Gr_resize_X;
754 			break;
755 		}
756 		(*w) = fl2ir(xy_tmp);
757 	}
758 
759 	if ( h ) {
760 		switch (resize_mode) {
761 		case GR_RESIZE_FULL:
762 			xy_tmp = (*h) / Gr_full_resize_Y;
763 			break;
764 
765 		case GR_RESIZE_FULL_CENTER:
766 			xy_tmp = (*h) / Gr_full_center_resize_Y;
767 			break;
768 
769 		case GR_RESIZE_MENU:
770 		case GR_RESIZE_MENU_ZOOMED:
771 		case GR_RESIZE_MENU_NO_OFFSET:
772 			xy_tmp = (*h) / Gr_resize_Y;
773 			break;
774 		}
775 		(*h) = fl2ir(xy_tmp);
776 	}
777 
778 	return true;
779 }
780 
781 /**
782  * This function is to be called if you wish to scale GR_1024 or GR_640 x and y positions or
783  * lengths in order to keep the correctly scaled to nonstandard resolutions
784  *
785  * @param x X value, can be NULL
786  * @param y Y value, can be NULL
787  * @param w width, can be NULL
788  * @param h height, can be NULL
789  * @param resize_mode
790  * @return always true unless error
791  */
gr_resize_screen_posf(float * x,float * y,float * w,float * h,int resize_mode)792 bool gr_resize_screen_posf(float *x, float *y, float *w, float *h, int resize_mode)
793 {
794 	if ( resize_mode == GR_RESIZE_NONE || (!gr_screen.custom_size && (gr_screen.rendering_to_texture == -1)) ) {
795 		return false;
796 	}
797 
798 	float xy_tmp = 0.0f;
799 
800 	if ( x ) {
801 		switch (resize_mode) {
802 		case GR_RESIZE_FULL:
803 			xy_tmp = (*x) * Gr_full_resize_X;
804 			break;
805 
806 		case GR_RESIZE_FULL_CENTER:
807 			xy_tmp = (*x) * Gr_full_center_resize_X + (float)gr_screen.center_offset_x;
808 			break;
809 
810 		case GR_RESIZE_MENU:
811 			xy_tmp = (*x) * Gr_resize_X + Gr_menu_offset_X;
812 			break;
813 
814 		case GR_RESIZE_MENU_ZOOMED:
815 			xy_tmp = (*x) * Gr_resize_X + Gr_menu_zoomed_offset_X;
816 			break;
817 
818 		case GR_RESIZE_MENU_NO_OFFSET:
819 			xy_tmp = (*x) * Gr_resize_X;
820 			break;
821 		}
822 		(*x) = xy_tmp;
823 	}
824 
825 	if ( y ) {
826 		switch (resize_mode) {
827 		case GR_RESIZE_FULL:
828 			xy_tmp = (*y) * Gr_full_resize_Y;
829 			break;
830 
831 		case GR_RESIZE_FULL_CENTER:
832 			xy_tmp = (*y) * Gr_full_center_resize_Y + (float)gr_screen.center_offset_y;
833 			break;
834 
835 		case GR_RESIZE_MENU:
836 			xy_tmp = (*y) * Gr_resize_Y + Gr_menu_offset_Y;
837 			break;
838 
839 		case GR_RESIZE_MENU_ZOOMED:
840 			xy_tmp = (*y) * Gr_resize_Y + Gr_menu_zoomed_offset_Y;
841 			break;
842 
843 		case GR_RESIZE_MENU_NO_OFFSET:
844 			xy_tmp = (*y) * Gr_resize_Y;
845 			break;
846 		}
847 		(*y) = xy_tmp;
848 	}
849 
850 	if ( w ) {
851 		switch (resize_mode) {
852 		case GR_RESIZE_FULL:
853 			xy_tmp = (*w) * Gr_full_resize_X;
854 			break;
855 
856 		case GR_RESIZE_FULL_CENTER:
857 			xy_tmp = (*w) * Gr_full_center_resize_X;
858 			break;
859 
860 		case GR_RESIZE_MENU:
861 		case GR_RESIZE_MENU_ZOOMED:
862 		case GR_RESIZE_MENU_NO_OFFSET:
863 			xy_tmp = (*w) * Gr_resize_X;
864 			break;
865 		}
866 		(*w) = xy_tmp;
867 	}
868 
869 	if ( h ) {
870 		switch (resize_mode) {
871 		case GR_RESIZE_FULL:
872 			xy_tmp = (*h) * Gr_full_resize_Y;
873 			break;
874 
875 		case GR_RESIZE_FULL_CENTER:
876 			xy_tmp = (*h) * Gr_full_center_resize_Y;
877 			break;
878 
879 		case GR_RESIZE_MENU:
880 		case GR_RESIZE_MENU_ZOOMED:
881 		case GR_RESIZE_MENU_NO_OFFSET:
882 			xy_tmp = (*h) * Gr_resize_Y;
883 			break;
884 		}
885 		(*h) = xy_tmp;
886 	}
887 
888 	return true;
889 }
890 
891 /**
892  *
893  * @param x X value, can be NULL
894  * @param y Y value, can be NULL
895  * @param w width, can be NULL
896  * @param h height, can be NULL
897  * @param resize_mode
898  * @return always true unless error
899  */
gr_unsize_screen_posf(float * x,float * y,float * w,float * h,int resize_mode)900 bool gr_unsize_screen_posf(float *x, float *y, float *w, float *h, int resize_mode)
901 {
902 	if ( resize_mode == GR_RESIZE_NONE || (!gr_screen.custom_size && (gr_screen.rendering_to_texture == -1)) ) {
903 		return false;
904 	}
905 
906 	float xy_tmp = 0.0f;
907 
908 	if ( x ) {
909 		switch (resize_mode) {
910 		case GR_RESIZE_FULL:
911 			xy_tmp = (*x) / Gr_full_resize_X;
912 			break;
913 
914 		case GR_RESIZE_FULL_CENTER:
915 			xy_tmp = ((*x) - (float)gr_screen.center_offset_x) / Gr_full_center_resize_X;
916 			break;
917 
918 		case GR_RESIZE_MENU:
919 			xy_tmp = ((*x) - Gr_menu_offset_X) / Gr_resize_X;
920 			break;
921 
922 		case GR_RESIZE_MENU_ZOOMED:
923 			xy_tmp = ((*x) - Gr_menu_zoomed_offset_X) / Gr_resize_X;
924 			break;
925 
926 		case GR_RESIZE_MENU_NO_OFFSET:
927 			xy_tmp = (*x) / Gr_resize_X;
928 			break;
929 		}
930 		(*x) = xy_tmp;
931 	}
932 
933 	if ( y ) {
934 		switch (resize_mode) {
935 		case GR_RESIZE_FULL:
936 			xy_tmp = (*y) / Gr_full_resize_Y;
937 			break;
938 
939 		case GR_RESIZE_FULL_CENTER:
940 			xy_tmp = ((*y) - (float)gr_screen.center_offset_y) / Gr_full_center_resize_Y;
941 			break;
942 
943 		case GR_RESIZE_MENU:
944 			xy_tmp = ((*y) - Gr_menu_offset_Y) / Gr_resize_Y;
945 			break;
946 
947 		case GR_RESIZE_MENU_ZOOMED:
948 			xy_tmp = ((*y) - Gr_menu_zoomed_offset_Y) / Gr_resize_Y;
949 			break;
950 
951 		case GR_RESIZE_MENU_NO_OFFSET:
952 			xy_tmp = (*y) / Gr_resize_Y;
953 			break;
954 		}
955 		(*y) = xy_tmp;
956 	}
957 
958 	if ( w ) {
959 		switch (resize_mode) {
960 		case GR_RESIZE_FULL:
961 			xy_tmp = (*w) / Gr_full_resize_X;
962 			break;
963 
964 		case GR_RESIZE_FULL_CENTER:
965 			xy_tmp = (*w) / Gr_full_center_resize_X;
966 			break;
967 
968 		case GR_RESIZE_MENU:
969 		case GR_RESIZE_MENU_ZOOMED:
970 		case GR_RESIZE_MENU_NO_OFFSET:
971 			xy_tmp = (*w) / Gr_resize_X;
972 			break;
973 		}
974 		(*w) = xy_tmp;
975 	}
976 
977 	if ( h ) {
978 		switch (resize_mode) {
979 		case GR_RESIZE_FULL:
980 			xy_tmp = (*h) / Gr_full_resize_Y;
981 			break;
982 
983 		case GR_RESIZE_FULL_CENTER:
984 			xy_tmp = (*h) / Gr_full_center_resize_Y;
985 			break;
986 
987 		case GR_RESIZE_MENU:
988 		case GR_RESIZE_MENU_ZOOMED:
989 		case GR_RESIZE_MENU_NO_OFFSET:
990 			xy_tmp = (*h) / Gr_resize_Y;
991 			break;
992 		}
993 		(*h) = xy_tmp;
994 	}
995 
996 	return true;
997 }
998 
gr_close()999 void gr_close()
1000 {
1001 	if ( !Gr_inited ) {
1002 		return;
1003 	}
1004 
1005 	if (Gr_original_gamma_ramp != nullptr && os::getSDLMainWindow() != nullptr) {
1006 		SDL_SetWindowGammaRamp(os::getSDLMainWindow(), Gr_original_gamma_ramp, (Gr_original_gamma_ramp + 256),
1007 		                       (Gr_original_gamma_ramp + 512));
1008 	}
1009 
1010 	// This is valid even if Gr_original_gamma_ramp is nullptr
1011 	vm_free(Gr_original_gamma_ramp);
1012 	Gr_original_gamma_ramp = nullptr;
1013 
1014 	gpu_heap_deinit();
1015 
1016 	// Cleanup uniform buffer managers
1017 	uniform_buffer_managers_deinit();
1018 
1019 	font::close();
1020 
1021 	gr_light_shutdown();
1022 
1023 	graphics::paths::PathRenderer::shutdown();
1024 
1025 	switch (gr_screen.mode) {
1026 		case GR_OPENGL:
1027 #ifdef WITH_OPENGL
1028 			gr_opengl_cleanup(true);
1029 #endif
1030 			break;
1031 
1032 		case GR_VULKAN:
1033 #ifdef WITH_VULKAN
1034 			graphics::vulkan::cleanup();
1035 #endif
1036 			break;
1037 
1038 		case GR_STUB:
1039 			break;
1040 
1041 		default:
1042 			Int3();		// Invalid graphics mode
1043 	}
1044 
1045 	bm_close();
1046 
1047 	Gr_inited = 0;
1048 }
1049 
1050 
1051 /**
1052  * Set screen clear color
1053  */
1054 DCF(clear_color, "set clear color r, g, b")
1055 {
1056 	ubyte r, g, b;
1057 
1058 	dc_stuff_ubyte(&r);
1059 	dc_stuff_ubyte(&g);
1060 	dc_stuff_ubyte(&b);
1061 
1062 	// set the color
1063 	gr_set_clear_color(r, g, b);
1064 }
1065 
gr_set_palette_internal(const char *,ubyte * palette,int)1066 void gr_set_palette_internal( const char * /*name*/, ubyte * palette, int  /*restrict_font_to_128*/ )
1067 {
1068 	if ( palette == NULL ) {
1069 		// Create a default palette
1070 		int r,g,b,i;
1071 		i = 0;
1072 
1073 		for (r=0; r<6; r++ )
1074 			for (g=0; g<6; g++ )
1075 				for (b=0; b<6; b++ ) {
1076 					Gr_current_palette[i*3+0] = (unsigned char)(r*51);
1077 					Gr_current_palette[i*3+1] = (unsigned char)(g*51);
1078 					Gr_current_palette[i*3+2] = (unsigned char)(b*51);
1079 					i++;
1080 				}
1081 		for ( i=216;i<256; i++ ) {
1082 			Gr_current_palette[i*3+0] = (unsigned char)((i-216)*6);
1083 			Gr_current_palette[i*3+1] = (unsigned char)((i-216)*6);
1084 			Gr_current_palette[i*3+2] = (unsigned char)((i-216)*6);
1085 		}
1086 		memmove( Gr_original_palette, Gr_current_palette, 768 );
1087 	} else {
1088 		memmove( Gr_original_palette, palette, 768 );
1089 		memmove( Gr_current_palette, palette, 768 );
1090 	}
1091 
1092 	if ( Gr_inited ) {
1093 		// Since the palette set code might shuffle the palette,
1094 		// reload it into the source palette
1095 		if (palette) {
1096 			memmove(palette, Gr_current_palette, 768);
1097 		}
1098 	}
1099 }
1100 
gr_screen_resize(int width,int height)1101 void gr_screen_resize(int width, int height)
1102 {
1103 	gr_screen.save_center_w = gr_screen.center_w = gr_screen.save_max_w = gr_screen.max_w = gr_screen.max_w_unscaled = gr_screen.max_w_unscaled_zoomed = width;
1104 	gr_screen.save_center_h = gr_screen.center_h = gr_screen.save_max_h = gr_screen.max_h = gr_screen.max_h_unscaled = gr_screen.max_h_unscaled_zoomed = height;
1105 
1106 	gr_screen.offset_x = gr_screen.offset_x_unscaled = 0;
1107 	gr_screen.offset_y = gr_screen.offset_y_unscaled = 0;
1108 
1109 	gr_screen.clip_left = gr_screen.clip_left_unscaled = 0;
1110 	gr_screen.clip_top = gr_screen.clip_top_unscaled = 0;
1111 	gr_screen.clip_right = gr_screen.clip_right_unscaled = gr_screen.max_w - 1;
1112 	gr_screen.clip_bottom = gr_screen.clip_bottom_unscaled = gr_screen.max_h - 1;
1113 	gr_screen.clip_width = gr_screen.clip_width_unscaled = gr_screen.max_w;
1114 	gr_screen.clip_height = gr_screen.clip_height_unscaled = gr_screen.max_h;
1115 	gr_screen.clip_aspect = i2fl(gr_screen.clip_width) / i2fl(gr_screen.clip_height);
1116 
1117 	if (gr_screen.custom_size) {
1118 		gr_unsize_screen_pos( &gr_screen.max_w_unscaled, &gr_screen.max_h_unscaled );
1119 		gr_unsize_screen_pos( &gr_screen.max_w_unscaled_zoomed, &gr_screen.max_h_unscaled_zoomed );
1120 		gr_unsize_screen_pos( &gr_screen.clip_right_unscaled, &gr_screen.clip_bottom_unscaled );
1121 		gr_unsize_screen_pos( &gr_screen.clip_width_unscaled, &gr_screen.clip_height_unscaled );
1122 	}
1123 
1124 	gr_screen.save_max_w_unscaled = gr_screen.max_w_unscaled;
1125 	gr_screen.save_max_h_unscaled = gr_screen.max_h_unscaled;
1126 	gr_screen.save_max_w_unscaled_zoomed = gr_screen.max_w_unscaled_zoomed;
1127 	gr_screen.save_max_h_unscaled_zoomed = gr_screen.max_h_unscaled_zoomed;
1128 
1129 	gr_setup_viewport();
1130 }
1131 
gr_get_resolution_class(int width,int height)1132 int gr_get_resolution_class(int width, int height)
1133 {
1134 	if ((width >= GR_1024_THRESHOLD_WIDTH) && (height >= GR_1024_THRESHOLD_HEIGHT)) {
1135 		return GR_1024;
1136 	} else {
1137 		return GR_640;
1138 	}
1139 }
1140 
init_colors()1141 static void init_colors()
1142 {
1143 	int bpp = gr_screen.bits_per_pixel;
1144 
1145 	Assertion((bpp == 16) || (bpp == 32), "Invalid bits-per-pixel value %d!", bpp);
1146 
1147 	// screen format
1148 	switch (bpp) {
1149 	case 16: {
1150 		Gr_red.bits  = 5;
1151 		Gr_red.shift = 11;
1152 		Gr_red.scale = 8;
1153 		Gr_red.mask  = 0xF800;
1154 
1155 		Gr_green.bits  = 6;
1156 		Gr_green.shift = 5;
1157 		Gr_green.scale = 4;
1158 		Gr_green.mask  = 0x7E0;
1159 
1160 		Gr_blue.bits  = 5;
1161 		Gr_blue.shift = 0;
1162 		Gr_blue.scale = 8;
1163 		Gr_blue.mask  = 0x1F;
1164 
1165 		break;
1166 	}
1167 
1168 	case 32: {
1169 		Gr_red.bits  = 8;
1170 		Gr_red.shift = 16;
1171 		Gr_red.scale = 1;
1172 		Gr_red.mask  = 0xff0000;
1173 
1174 		Gr_green.bits  = 8;
1175 		Gr_green.shift = 8;
1176 		Gr_green.scale = 1;
1177 		Gr_green.mask  = 0x00ff00;
1178 
1179 		Gr_blue.bits  = 8;
1180 		Gr_blue.shift = 0;
1181 		Gr_blue.scale = 1;
1182 		Gr_blue.mask  = 0x0000ff;
1183 
1184 		Gr_alpha.bits  = 8;
1185 		Gr_alpha.shift = 24;
1186 		Gr_alpha.mask  = 0xff000000;
1187 		Gr_alpha.scale = 1;
1188 
1189 		break;
1190 	}
1191 	}
1192 
1193 	// texture format
1194 	Gr_t_red.bits  = 5;
1195 	Gr_t_red.mask  = 0x7c00;
1196 	Gr_t_red.shift = 10;
1197 	Gr_t_red.scale = 8;
1198 
1199 	Gr_t_green.bits  = 5;
1200 	Gr_t_green.mask  = 0x03e0;
1201 	Gr_t_green.shift = 5;
1202 	Gr_t_green.scale = 8;
1203 
1204 	Gr_t_blue.bits  = 5;
1205 	Gr_t_blue.mask  = 0x001f;
1206 	Gr_t_blue.shift = 0;
1207 	Gr_t_blue.scale = 8;
1208 
1209 	Gr_t_alpha.bits  = 1;
1210 	Gr_t_alpha.mask  = 0x8000;
1211 	Gr_t_alpha.scale = 255;
1212 	Gr_t_alpha.shift = 15;
1213 
1214 	// alpha-texture format
1215 	Gr_ta_red.bits  = 4;
1216 	Gr_ta_red.mask  = 0x0f00;
1217 	Gr_ta_red.shift = 8;
1218 	Gr_ta_red.scale = 17;
1219 
1220 	Gr_ta_green.bits  = 4;
1221 	Gr_ta_green.mask  = 0x00f0;
1222 	Gr_ta_green.shift = 4;
1223 	Gr_ta_green.scale = 17;
1224 
1225 	Gr_ta_blue.bits  = 4;
1226 	Gr_ta_blue.mask  = 0x000f;
1227 	Gr_ta_blue.shift = 0;
1228 	Gr_ta_blue.scale = 17;
1229 
1230 	Gr_ta_alpha.bits  = 4;
1231 	Gr_ta_alpha.mask  = 0xf000;
1232 	Gr_ta_alpha.shift = 12;
1233 	Gr_ta_alpha.scale = 17;
1234 }
1235 
gr_init_sub(std::unique_ptr<os::GraphicsOperations> && graphicsOps,int mode,int width,int height,int depth,float center_aspect_ratio)1236 static bool gr_init_sub(std::unique_ptr<os::GraphicsOperations>&& graphicsOps, int mode, int width, int height,
1237 						int depth, float center_aspect_ratio)
1238 {
1239 	int res = GR_1024;
1240 	bool rc = false;
1241 
1242 	gr_screen = {};
1243 
1244 	float aspect_ratio = (float)width / (float)height;
1245 
1246 	if ( (((width == 640) && (height == 480)) || ((width == 1024) && (height == 768))) && (aspect_ratio == center_aspect_ratio) ) {
1247 		gr_screen.custom_size = false;
1248 	} else {
1249 		gr_screen.custom_size = true;
1250 	}
1251 
1252 	gr_screen.save_max_w = gr_screen.max_w = gr_screen.max_w_unscaled = gr_screen.max_w_unscaled_zoomed = width;
1253 	gr_screen.save_max_h = gr_screen.max_h = gr_screen.max_h_unscaled = gr_screen.max_h_unscaled_zoomed = height;
1254 	if (aspect_ratio > center_aspect_ratio) {
1255 		gr_screen.save_center_w = gr_screen.center_w = fl2ir(height * center_aspect_ratio);
1256 		gr_screen.save_center_h = gr_screen.center_h = height;
1257 	} else if (aspect_ratio < center_aspect_ratio) {
1258 		gr_screen.save_center_w = gr_screen.center_w = width;
1259 		gr_screen.save_center_h = gr_screen.center_h = fl2ir(width / center_aspect_ratio);
1260 	} else {
1261 		gr_screen.save_center_w = gr_screen.center_w = width;
1262 		gr_screen.save_center_h = gr_screen.center_h = height;
1263 	}
1264 	gr_screen.save_center_offset_x = gr_screen.center_offset_x = (width - gr_screen.center_w) / 2;
1265 	gr_screen.save_center_offset_y = gr_screen.center_offset_y = (height - gr_screen.center_h) / 2;
1266 
1267 	res = gr_get_resolution_class(gr_screen.center_w, gr_screen.center_h);
1268 
1269 	if (Fred_running) {
1270 		gr_screen.custom_size = false;
1271 		res = GR_640;
1272 		mode = GR_OPENGL;
1273 	}
1274 
1275 	Save_custom_screen_size = gr_screen.custom_size;
1276 
1277 	Gr_save_full_resize_X = Gr_full_resize_X = (float)width / ((res == GR_1024) ? 1024.0f : 640.0f);
1278 	Gr_save_full_resize_Y = Gr_full_resize_Y = (float)height / ((res == GR_1024) ?  768.0f : 480.0f);
1279 
1280 	Gr_save_full_center_resize_X = Gr_full_center_resize_X = (float)gr_screen.center_w / ((res == GR_1024) ? 1024.0f : 640.0f);
1281 	Gr_save_full_center_resize_Y = Gr_full_center_resize_Y = (float)gr_screen.center_h / ((res == GR_1024) ?  768.0f : 480.0f);
1282 
1283 	if (gr_screen.custom_size && !Cmdline_stretch_menu) {
1284 		float aspect_quotient = center_aspect_ratio / (4.0f / 3.0f);
1285 
1286 		Gr_save_resize_X = Gr_resize_X = Gr_full_center_resize_X / ((aspect_quotient > 1.0f) ? aspect_quotient : 1.0f);
1287 		Gr_save_resize_Y = Gr_resize_Y = Gr_full_center_resize_Y * ((aspect_quotient < 1.0f) ? aspect_quotient : 1.0f);
1288 
1289 		Gr_save_menu_offset_X = Gr_menu_offset_X = ((aspect_quotient > 1.0f) ? ((gr_screen.center_w - gr_screen.center_w / aspect_quotient) / 2.0f) : 0.0f) + gr_screen.center_offset_x;
1290 		Gr_save_menu_offset_Y = Gr_menu_offset_Y = ((aspect_quotient < 1.0f) ? ((gr_screen.center_h - gr_screen.center_h * aspect_quotient) / 2.0f) : 0.0f) + gr_screen.center_offset_y;
1291 	} else {
1292 		Gr_save_resize_X = Gr_resize_X = Gr_full_center_resize_X;
1293 		Gr_save_resize_Y = Gr_resize_Y = Gr_full_center_resize_Y;
1294 
1295 		Gr_save_menu_offset_X = Gr_menu_offset_X = (float)gr_screen.center_offset_x;
1296 		Gr_save_menu_offset_Y = Gr_menu_offset_Y = (float)gr_screen.center_offset_y;
1297 	}
1298 
1299 	Gr_save_menu_zoomed_offset_X = Gr_menu_zoomed_offset_X = Gr_menu_offset_X;
1300 	Gr_save_menu_zoomed_offset_Y = Gr_menu_zoomed_offset_Y = Gr_menu_offset_Y;
1301 
1302 
1303 	gr_screen.signature = Gr_signature++;
1304 	gr_screen.bits_per_pixel = depth;
1305 	gr_screen.bytes_per_pixel= depth / 8;
1306 	gr_screen.rendering_to_texture = -1;
1307 	gr_screen.envmap_render_target = -1;
1308 	gr_screen.line_width = 1.0f;
1309 	gr_screen.mode = mode;
1310 	gr_screen.res = res;
1311 	gr_screen.aspect = 1.0f;			// Normal PC screen
1312 
1313 	gr_screen.offset_x = gr_screen.offset_x_unscaled = 0;
1314 	gr_screen.offset_y = gr_screen.offset_y_unscaled = 0;
1315 
1316 	gr_screen.clip_left = gr_screen.clip_left_unscaled = 0;
1317 	gr_screen.clip_top = gr_screen.clip_top_unscaled = 0;
1318 	gr_screen.clip_right = gr_screen.clip_right_unscaled = gr_screen.max_w - 1;
1319 	gr_screen.clip_bottom = gr_screen.clip_bottom_unscaled = gr_screen.max_h - 1;
1320 	gr_screen.clip_width = gr_screen.clip_width_unscaled = gr_screen.max_w;
1321 	gr_screen.clip_height = gr_screen.clip_height_unscaled = gr_screen.max_h;
1322 	gr_screen.clip_aspect = i2fl(gr_screen.clip_width) / i2fl(gr_screen.clip_height);
1323 	gr_screen.clip_center_x = (gr_screen.clip_left + gr_screen.clip_right) * 0.5f;
1324 	gr_screen.clip_center_y = (gr_screen.clip_top + gr_screen.clip_bottom) * 0.5f;
1325 
1326 	if (gr_screen.custom_size) {
1327 		gr_unsize_screen_pos(&gr_screen.max_w_unscaled, &gr_screen.max_h_unscaled);
1328 		gr_unsize_screen_pos(&gr_screen.max_w_unscaled_zoomed, &gr_screen.max_h_unscaled_zoomed);
1329 		gr_unsize_screen_pos(&gr_screen.clip_right_unscaled, &gr_screen.clip_bottom_unscaled);
1330 		gr_unsize_screen_pos(&gr_screen.clip_width_unscaled, &gr_screen.clip_height_unscaled);
1331 	}
1332 
1333 	gr_screen.save_max_w_unscaled        = gr_screen.max_w_unscaled;
1334 	gr_screen.save_max_h_unscaled        = gr_screen.max_h_unscaled;
1335 	gr_screen.save_max_w_unscaled_zoomed = gr_screen.max_w_unscaled_zoomed;
1336 	gr_screen.save_max_h_unscaled_zoomed = gr_screen.max_h_unscaled_zoomed;
1337 
1338 	init_colors();
1339 
1340 	switch (mode) {
1341 	case GR_OPENGL:
1342 #ifdef WITH_OPENGL
1343 		rc = gr_opengl_init(std::move(graphicsOps));
1344 #else
1345 		Error(LOCATION, "OpenGL renderer was requested but that was not compiled into this build.");
1346 		rc = false;
1347 #endif
1348 		break;
1349 	case GR_VULKAN:
1350 #ifdef WITH_VULKAN
1351 		rc = graphics::vulkan::initialize(std::move(graphicsOps));
1352 #else
1353 		Error(LOCATION, "Vulkan renderer was requested but that was not compiled into this build.");
1354 		rc = false;
1355 #endif
1356 		break;
1357 	case GR_STUB:
1358 		SCP_UNUSED(graphicsOps);
1359 		rc = gr_stub_init();
1360 		break;
1361 	default:
1362 		Int3(); // Invalid graphics mode
1363 	}
1364 
1365 	return rc != 0;
1366 }
1367 
init_window_icon()1368 static void init_window_icon() {
1369 	auto view = os::getMainViewport();
1370 
1371 	if (view == nullptr) {
1372 		// Graphics backend has no viewport
1373 		return;
1374 	}
1375 
1376 	auto sdl_wnd = view->toSDLWindow();
1377 
1378 	if (sdl_wnd == nullptr) {
1379 		// No support for changing the icon
1380 		return;
1381 	}
1382 
1383 	auto icon_handle = bm_load(Window_icon_path);
1384 	if (icon_handle < 0) {
1385 		Warning(LOCATION, "Failed to load window icon '%s'!", Window_icon_path.c_str());
1386 		return;
1387 	}
1388 
1389 	auto surface = bm_to_sdl_surface(icon_handle);
1390 	if (surface == nullptr) {
1391 		Warning(LOCATION, "Convert icon '%s' to a SDL surface!", Window_icon_path.c_str());
1392 		bm_release(icon_handle);
1393 		return;
1394 	}
1395 
1396 	SDL_SetWindowIcon(sdl_wnd, surface);
1397 
1398 	SDL_FreeSurface(surface);
1399 	bm_release(icon_handle);
1400 }
1401 
gr_capability_to_string(gr_capability capability)1402 SCP_string gr_capability_to_string(gr_capability capability)
1403 {
1404 	switch (capability) {
1405 	case CAPABILITY_BPTC:
1406 		return "BPTC Texture Compression";
1407 	default:
1408 		return "Invalid Capability";
1409 	}
1410 }
1411 
gr_init(std::unique_ptr<os::GraphicsOperations> && graphicsOps,int d_mode,int d_width,int d_height,int d_depth)1412 bool gr_init(std::unique_ptr<os::GraphicsOperations>&& graphicsOps, int d_mode, int d_width, int d_height, int d_depth)
1413 {
1414 	int width = 1024, height = 768, depth = 32, mode = GR_OPENGL;
1415 	float center_aspect_ratio = -1.0f;
1416 	const char *ptr = NULL;
1417 	// If already inited, shutdown the previous graphics
1418 	if (Gr_inited) {
1419 		switch (gr_screen.mode) {
1420 			case GR_OPENGL:
1421 #ifdef WITH_OPENGL
1422 				gr_opengl_cleanup(false);
1423 #endif
1424 				break;
1425 
1426 			case GR_STUB:
1427 				break;
1428 
1429 			default:
1430 				Int3();		// Invalid graphics mode
1431 		}
1432 	}
1433 
1434 	if (Using_in_game_options) {
1435 		auto res = ResolutionOption->getValue();
1436 		width = res.width;
1437 		height = res.height;
1438 	} else {
1439 		// We cannot continue without this, quit, but try to help the user out first
1440 		ptr = os_config_read_string(nullptr, NOX("VideocardFs2open"), nullptr);
1441 
1442 		// if we don't have a config string then construct one, using OpenGL 1024x768 32-bit as the default
1443 		if (ptr == nullptr) {
1444 			// If we don't have a display mode, use SDL to get default settings
1445 			// We need to initialize SDL to do this
1446 
1447 			if (SDL_InitSubSystem(SDL_INIT_VIDEO) == 0)
1448 			{
1449 				auto display = static_cast<int>(os_config_read_uint("Video", "Display", 0));
1450 				SDL_DisplayMode displayMode;
1451 				if (SDL_GetDesktopDisplayMode(display, &displayMode) == 0)
1452 				{
1453 					width = displayMode.w;
1454 					height = displayMode.h;
1455 					int sdlBits = SDL_BITSPERPIXEL(displayMode.format);
1456 
1457 					if (SDL_ISPIXELFORMAT_ALPHA(displayMode.format))
1458 					{
1459 						depth = sdlBits;
1460 					}
1461 					else
1462 					{
1463 						// Fix a few values
1464 						if (sdlBits == 24)
1465 						{
1466 							depth = 32;
1467 						}
1468 						else if (sdlBits == 15)
1469 						{
1470 							depth = 16;
1471 						}
1472 						else
1473 						{
1474 							depth = sdlBits;
1475 						}
1476 					}
1477 
1478 					SCP_string videomode;
1479 					sprintf(videomode, "OGL -(%dx%d)x%d bit", width, height, depth);
1480 
1481 					os_config_write_string(nullptr, NOX("VideocardFs2open"), videomode.c_str());
1482 				}
1483 			}
1484 		} else {
1485 			Assert(ptr != nullptr);
1486 
1487 			// NOTE: The "ptr+5" is to skip over the initial "????-" in the video string.
1488 			//       If the format of that string changes you'll have to change this too!!!
1489 			if (sscanf(ptr + 5, "(%dx%d)x%d ", &width, &height, &depth) != 3) {
1490 				Error(LOCATION, "Can't understand 'VideocardFs2open' config entry!");
1491 			}
1492 
1493 			// Get the first 4 characters of the video string which is the chosen API
1494 			SCP_string videoApi(ptr, ptr + 4);
1495 
1496 			if (videoApi == "OGL ") {
1497 				d_mode = GR_OPENGL; // GR_OPENGL;
1498 			} else if (videoApi == "VK  ") {
1499 				d_mode = GR_VULKAN;
1500 			} else {
1501 				ReleaseWarning(LOCATION, "Unknown video API '%s'", videoApi.c_str());
1502 			}
1503 		}
1504 
1505 		if (Cmdline_res != nullptr) {
1506 			int tmp_width = 0;
1507 			int tmp_height = 0;
1508 
1509 			if ( sscanf(Cmdline_res, "%dx%d", &tmp_width, &tmp_height) == 2 ) {
1510 				width = tmp_width;
1511 				height = tmp_height;
1512 			}
1513 		}
1514 
1515 		Gr_enable_soft_particles = Cmdline_softparticles != 0;
1516 	}
1517 	if (Cmdline_center_res != NULL) {
1518 		int tmp_center_width = 0;
1519 		int tmp_center_height = 0;
1520 
1521 		if ( (sscanf(Cmdline_center_res, "%dx%d", &tmp_center_width, &tmp_center_height) == 2) && (tmp_center_width > 0) && (tmp_center_height > 0) ) {
1522 			center_aspect_ratio = (float)tmp_center_width / (float)tmp_center_height;
1523 		}
1524 	}
1525 
1526 	if (d_mode == GR_DEFAULT) {
1527 		// OpenGL should be default
1528 		mode = GR_OPENGL;
1529 	} else {
1530 		mode = d_mode;
1531 	}
1532 
1533 	// see if we passed good values, and use those instead of the config settings
1534 	if ( (d_width != GR_DEFAULT) && (d_height != GR_DEFAULT) ) {
1535 		width = d_width;
1536 		height = d_height;
1537 	}
1538 
1539 	if (d_depth != GR_DEFAULT) {
1540 		depth = d_depth;
1541 	}
1542 
1543 	if (gr_get_resolution_class(width, height) != GR_640) {
1544 		// check for hi-res interface files so that we can verify our width/height is correct
1545 		// if we don't have it then fall back to 640x480 mode instead
1546 		if ( !cf_exists_full("2_ChoosePilot-m.pcx", CF_TYPE_ANY)) {
1547 			if ( (width == 1024) && (height == 768) ) {
1548 				width = 640;
1549 				height = 480;
1550 				center_aspect_ratio = -1.0f;
1551 			} else {
1552 				width = 800;
1553 				height = 600;
1554 				center_aspect_ratio = -1.0f;
1555 			}
1556 		}
1557 	}
1558 
1559 	// if we are in standalone mode then just use special defaults
1560 	if (Is_standalone) {
1561 		mode = GR_STUB;
1562 		width = 640;
1563 		height = 480;
1564 		depth = 16;
1565 		center_aspect_ratio = -1.0f;
1566 	}
1567 
1568 // These compiler macros will force windowed mode at the specified resolution if
1569 // built in debug mode.  This helps if you run with the debugger active as the
1570 // game won't be switching from fullscreen to minimized every time you hit a breakpoint or
1571 // warning message.
1572 #ifdef _DEBUG
1573 #ifdef _FORCE_DEBUG_WIDESCREEN
1574 	width = 1280;
1575 	height = 800;
1576 	depth = 32;
1577 	center_aspect_ratio = -1.0f;
1578 	Cmdline_window = 1;
1579 #elif defined(_FORCE_DEBUG_1024)
1580 	width = 1024;
1581 	height = 768;
1582 	depth = 32;
1583 	center_aspect_ratio = -1.0f;
1584 	Cmdline_window = 1;
1585 #elif defined(_FORCE_DEBUG_640)
1586 	width = 640;
1587 	height = 480;
1588 	depth = 32;
1589 	center_aspect_ratio = -1.0f;
1590 	Cmdline_window = 1;
1591 #endif
1592 #endif
1593 
1594 	if (center_aspect_ratio <= 0.0f) {
1595 		float aspect_ratio = (float)width / (float)height;
1596 		if (aspect_ratio > 3.5f) {
1597 			center_aspect_ratio = aspect_ratio * 0.3f;
1598 		} else {
1599 			center_aspect_ratio = aspect_ratio;
1600 		}
1601 	}
1602 
1603 	// now try to actually init everything...
1604 	if ( gr_init_sub(std::move(graphicsOps), mode, width, height, depth, center_aspect_ratio) == false ) {
1605 		return false;
1606 	}
1607 
1608 	gr_light_init();
1609 
1610 	gr_set_palette_internal(Gr_current_palette_name, NULL, 0);
1611 
1612 	bm_init();
1613 
1614 	init_window_icon();
1615 
1616 	io::mouse::CursorManager::init();
1617 
1618 	mprintf(("Initializing path renderer...\n"));
1619 	graphics::paths::PathRenderer::init();
1620 
1621 	// Initialize uniform buffer managers
1622 	uniform_buffer_managers_init();
1623 
1624 	gpu_heap_init();
1625 
1626 	mprintf(("Checking graphics capabilities:\n"));
1627 	mprintf(("  Persistent buffer mapping: %s\n",
1628 	         gr_is_capable(CAPABILITY_PERSISTENT_BUFFER_MAPPING) ? "Enabled" : "Disabled"));
1629 
1630 	mprintf(("Checking mod required rendering features...\n"));
1631 	for (gr_capability ext : Required_render_ext) {
1632 		if (!gr_is_capable(ext)) {
1633 			Error(LOCATION, "Feature %s required by mod not supported by system.\n", gr_capability_to_string(ext).c_str());
1634 		}
1635 	}
1636 	mprintf(("  All required features are supported.\n"));
1637 
1638 	bool missing_installation = false;
1639 	if (!running_unittests && Web_cursor == nullptr) {
1640 		if (Is_standalone) {
1641 			// Cursors don't work in standalone mode, just check if the animation exists.
1642 			auto handle = bm_load_animation("cursorweb");
1643 			if (handle < 0) {
1644 				missing_installation = true;
1645 			} else {
1646 				bm_release(handle);
1647 			}
1648 		} else {
1649 			Web_cursor = io::mouse::CursorManager::get()->loadCursor("cursorweb", true);
1650 			missing_installation = Web_cursor == nullptr;
1651 		}
1652 	}
1653 	if (missing_installation) {
1654 		Error(LOCATION, "\nWeb cursor bitmap not found.  This is most likely due to one of three reasons:\n"
1655 			"    1) You're running FreeSpace Open from somewhere other than your FreeSpace 2 folder;\n"
1656 			"    2) You've somehow corrupted your FreeSpace 2 installation, e.g. by modifying or removing the retail VP files;\n"
1657 			"    3) You haven't installed FreeSpace 2 at all.  (Note that installing FreeSpace Open does NOT remove the need for a FreeSpace 2 installation.)\n"
1658 			"Number 1 can be fixed by simply moving the FreeSpace Open executable file to the FreeSpace 2 folder.  Numbers 2 and 3 can be fixed by installing or reinstalling FreeSpace 2.\n");
1659 	}
1660 
1661 	mprintf(("GRAPHICS: Initializing default colors...\n"));
1662 
1663 	gr_set_color(0,0,0);
1664 	gr_set_clear_color(0, 0, 0);
1665 
1666 	gr_set_shader(NULL);
1667 
1668 	if (!Is_standalone) {
1669 		if (Using_in_game_options) {
1670 			// The value should have been loaded into the variable already so we can use that here
1671 			gr_set_gamma(Gr_gamma);
1672 		} else {
1673 			// D3D's gamma system now works differently. 1.0 is the default value
1674 			ptr = os_config_read_string(nullptr, NOX("GammaD3D"), NOX("1.0"));
1675 			gr_set_gamma((float)atof(ptr));
1676 		}
1677 	}
1678 
1679 	Gr_inited = 1;
1680 
1681 	return true;
1682 }
1683 
gr_force_windowed()1684 void gr_force_windowed()
1685 {
1686 	if ( !Gr_inited ) {
1687 		return;
1688 	}
1689 
1690 	if ( Os_debugger_running ) {
1691 		os_sleep(1000);
1692 	}
1693 }
1694 
1695 int gr_activated = 0;
gr_activate(int active)1696 void gr_activate(int active)
1697 {
1698 
1699 	if (gr_activated == active) {
1700 		return;
1701 	}
1702 	gr_activated = active;
1703 
1704 	if ( !Gr_inited || os::getMainViewport() == nullptr) {
1705 		return;
1706 	}
1707 
1708 	if (active) {
1709 		if (Cmdline_fullscreen_window || Cmdline_window) {
1710 			os::getMainViewport()->restore();
1711 		} else {
1712 			os::getMainViewport()->setState(os::ViewportState::Fullscreen);
1713 		}
1714 	} else {
1715 		os::getMainViewport()->minimize();
1716 	}
1717 
1718 	if (active) {
1719 		if (!Cmdline_fullscreen_window && !Cmdline_window) {
1720 			gr_set_gamma(Gr_gamma);
1721 		}
1722 	}
1723 }
1724 
1725 // color stuff
gr_get_color(int * r,int * g,int * b)1726 void gr_get_color( int *r, int *g, int *b )
1727 {
1728 	if (r) *r = gr_screen.current_color.red;
1729 	if (g) *g = gr_screen.current_color.green;
1730 	if (b) *b = gr_screen.current_color.blue;
1731 }
1732 
gr_init_color(color * c,int r,int g,int b)1733 void gr_init_color(color *c, int r, int g, int b)
1734 {
1735 	CAP(r, 0, 255);
1736 	CAP(g, 0, 255);
1737 	CAP(b, 0, 255);
1738 
1739 	c->screen_sig = gr_screen.signature;
1740 	c->red = (ubyte)r;
1741 	c->green = (ubyte)g;
1742 	c->blue = (ubyte)b;
1743 	c->alpha = 255;
1744 	c->ac_type = AC_TYPE_NONE;
1745 	c->alphacolor = -1;
1746 	c->is_alphacolor = 0;
1747 	c->magic = 0xAC01;
1748 	c->raw8 = 0;
1749 }
1750 
gr_init_alphacolor(color * clr,int r,int g,int b,int alpha,int type)1751 void gr_init_alphacolor( color *clr, int r, int g, int b, int alpha, int type )
1752 {
1753 	CAP(r, 0, 255);
1754 	CAP(g, 0, 255);
1755 	CAP(b, 0, 255);
1756 	CAP(alpha, 0, 255);
1757 
1758 	gr_init_color( clr, r, g, b );
1759 
1760 	clr->alpha = (ubyte)alpha;
1761 	clr->ac_type = (ubyte)type;
1762 	clr->alphacolor = -1;
1763 	clr->is_alphacolor = 1;
1764 }
1765 
gr_set_color(int r,int g,int b)1766 void gr_set_color( int r, int g, int b )
1767 {
1768 	Assert((r >= 0) && (r < 256));
1769 	Assert((g >= 0) && (g < 256));
1770 	Assert((b >= 0) && (b < 256));
1771 
1772 	gr_init_color( &gr_screen.current_color, r, g, b );
1773 }
1774 
gr_set_color_fast(color * dst)1775 void gr_set_color_fast(color *dst)
1776 {
1777 	if ( dst->screen_sig != gr_screen.signature ) {
1778 		if (dst->is_alphacolor) {
1779 			gr_init_alphacolor( dst, dst->red, dst->green, dst->blue, dst->alpha, dst->ac_type );
1780 		} else {
1781 			gr_init_color( dst, dst->red, dst->green, dst->blue );
1782 		}
1783 	}
1784 
1785 	gr_screen.current_color = *dst;
1786 }
1787 
1788 // shader functions
gr_create_shader(shader * shade,ubyte r,ubyte g,ubyte b,ubyte c)1789 void gr_create_shader(shader *shade, ubyte r, ubyte g, ubyte b, ubyte c )
1790 {
1791 	shade->screen_sig = gr_screen.signature;
1792 	shade->r = r;
1793 	shade->g = g;
1794 	shade->b = b;
1795 	shade->c = c;
1796 }
1797 
gr_set_shader(shader * shade)1798 void gr_set_shader(shader *shade)
1799 {
1800 	if (shade) {
1801 		if (shade->screen_sig != gr_screen.signature) {
1802 			gr_create_shader( shade, shade->r, shade->g, shade->b, shade->c );
1803 		}
1804 		gr_screen.current_shader = *shade;
1805 	} else {
1806 		gr_create_shader( &gr_screen.current_shader, 0, 0, 0, 0 );
1807 	}
1808 }
1809 
1810 // new bitmap functions
gr_bitmap(int _x,int _y,int resize_mode)1811 void gr_bitmap(int _x, int _y, int resize_mode)
1812 {
1813 	GR_DEBUG_SCOPE("2D Bitmap");
1814 
1815 	int _w, _h;
1816 	float x, y, w, h;
1817 	vertex verts[4];
1818 
1819 	if (gr_screen.mode == GR_STUB) {
1820 		return;
1821 	}
1822 
1823 	bm_get_info(gr_screen.current_bitmap, &_w, &_h, NULL, NULL, NULL);
1824 
1825 	x = i2fl(_x);
1826 	y = i2fl(_y);
1827 	w = i2fl(_w);
1828 	h = i2fl(_h);
1829 
1830 	auto do_resize = gr_resize_screen_posf(&x, &y, &w, &h, resize_mode);
1831 
1832 	x += ((do_resize) ? gr_screen.offset_x : gr_screen.offset_x_unscaled);
1833 	y += ((do_resize) ? gr_screen.offset_y : gr_screen.offset_y_unscaled);
1834 
1835 	memset(verts, 0, sizeof(verts));
1836 
1837 	verts[0].screen.xyw.x = x;
1838 	verts[0].screen.xyw.y = y;
1839 	verts[0].texture_position.u = 0.0f;
1840 	verts[0].texture_position.v = 0.0f;
1841 
1842 	verts[1].screen.xyw.x = x + w;
1843 	verts[1].screen.xyw.y = y;
1844 	verts[1].texture_position.u = 1.0f;
1845 	verts[1].texture_position.v = 0.0f;
1846 
1847 	verts[2].screen.xyw.x = x + w;
1848 	verts[2].screen.xyw.y = y + h;
1849 	verts[2].texture_position.u = 1.0f;
1850 	verts[2].texture_position.v = 1.0f;
1851 
1852 	verts[3].screen.xyw.x = x;
1853 	verts[3].screen.xyw.y = y + h;
1854 	verts[3].texture_position.u = 0.0f;
1855 	verts[3].texture_position.v = 1.0f;
1856 
1857 	// turn off zbuffering
1858 	//int saved_zbuffer_mode = gr_zbuffer_get();
1859 	//gr_zbuffer_set(GR_ZBUFF_NONE);
1860 
1861 	material mat_params;
1862 	material_set_interface(
1863 		&mat_params,
1864 		gr_screen.current_bitmap,
1865 		gr_screen.current_alphablend_mode == GR_ALPHABLEND_FILTER ? true : false,
1866 		gr_screen.current_alpha
1867 	);
1868 
1869 	g3_render_primitives_textured(&mat_params, verts, 4, PRIM_TYPE_TRIFAN, true);
1870 
1871 	//gr_zbuffer_set(saved_zbuffer_mode);
1872 }
1873 
gr_bitmap_uv(int _x,int _y,int _w,int _h,float _u0,float _v0,float _u1,float _v1,int resize_mode)1874 void gr_bitmap_uv(int _x, int _y, int _w, int _h, float _u0, float _v0, float _u1, float _v1, int resize_mode)
1875 {
1876 	GR_DEBUG_SCOPE("2D Bitmap UV");
1877 
1878 	float x, y, w, h;
1879 	vertex verts[4];
1880 
1881 	x = i2fl(_x);
1882 	y = i2fl(_y);
1883 	w = i2fl(_w);
1884 	h = i2fl(_h);
1885 
1886 	// I will tidy this up later - RT
1887 	if ( resize_mode != GR_RESIZE_NONE && (gr_screen.custom_size || (gr_screen.rendering_to_texture != -1)) ) {
1888 		gr_resize_screen_posf(&x, &y, &w, &h, resize_mode);
1889 	}
1890 
1891 	memset(verts, 0, sizeof(verts));
1892 
1893 	verts[0].screen.xyw.x = x;
1894 	verts[0].screen.xyw.y = y;
1895 	verts[0].texture_position.u = _u0;
1896 	verts[0].texture_position.v = _v0;
1897 
1898 	verts[1].screen.xyw.x = x + w;
1899 	verts[1].screen.xyw.y = y;
1900 	verts[1].texture_position.u = _u1;
1901 	verts[1].texture_position.v = _v0;
1902 
1903 	verts[2].screen.xyw.x = x + w;
1904 	verts[2].screen.xyw.y = y + h;
1905 	verts[2].texture_position.u = _u1;
1906 	verts[2].texture_position.v = _v1;
1907 
1908 	verts[3].screen.xyw.x = x;
1909 	verts[3].screen.xyw.y = y + h;
1910 	verts[3].texture_position.u = _u0;
1911 	verts[3].texture_position.v = _v1;
1912 
1913 	material material_params;
1914 	material_set_interface(
1915 		&material_params,
1916 		gr_screen.current_bitmap,
1917 		gr_screen.current_alphablend_mode == GR_ALPHABLEND_FILTER ? true : false,
1918 		gr_screen.current_alpha
1919 	);
1920 	g3_render_primitives_textured(&material_params, verts, 4, PRIM_TYPE_TRIFAN, true);
1921 }
1922 
1923 /**
1924 * Given endpoints, and thickness, calculate coords of the endpoint
1925 * Adapted from gr_pline_helper()
1926 */
gr_pline_helper(vec3d * out,vec3d * in1,vec3d * in2,int thickness)1927 void gr_pline_helper(vec3d *out, vec3d *in1, vec3d *in2, int thickness)
1928 {
1929 	vec3d slope;
1930 
1931 	// slope of the line
1932 	if ( vm_vec_same(in1, in2) ) {
1933 		slope = vmd_zero_vector;
1934 	} else {
1935 		vm_vec_sub(&slope, in2, in1);
1936 		float temp = -slope.xyz.x;
1937 		slope.xyz.x = slope.xyz.y;
1938 		slope.xyz.y = temp;
1939 		vm_vec_normalize(&slope);
1940 	}
1941 	// get the points
1942 	vm_vec_scale_add(out, in1, &slope, (float)thickness);
1943 }
1944 
1945 
1946 /**
1947 * Special function for drawing polylines.
1948 *
1949 * This function is specifically intended for polylines where each section
1950 * is no more than 90 degrees away from a previous section.
1951 * Moreover, it is _really_ intended for use with 45 degree angles.
1952 * Adapted from gr_pline_special()
1953 */
gr_pline_special(SCP_vector<vec3d> * pts,int thickness,int resize_mode)1954 void gr_pline_special(SCP_vector<vec3d> *pts, int thickness, int resize_mode)
1955 {
1956 	vec3d s1, s2, e1, e2, dir;
1957 	vec3d last_e1, last_e2;
1958 	vertex v[4];
1959 	int started_frame = 0;
1960 
1961 	size_t num_pts = pts->size();
1962 
1963 	// if we have less than 2 pts, bail
1964 	if ( num_pts < 2 ) {
1965 		return;
1966 	}
1967 
1968 	extern int G3_count;
1969 	if ( G3_count == 0 ) {
1970 		g3_start_frame(1);
1971 		started_frame = 1;
1972 	}
1973 
1974 	float sw = 0.1f;
1975 
1976 	color clr = gr_screen.current_color;
1977 
1978 	material material_def;
1979 
1980 	material_def.set_depth_mode(ZBUFFER_TYPE_NONE);
1981 	material_def.set_blend_mode(ALPHA_BLEND_ALPHA_BLEND_ALPHA);
1982 	material_def.set_cull_mode(false);
1983 
1984 	// draw each section
1985 	last_e1 = vmd_zero_vector;
1986 	last_e2 = vmd_zero_vector;
1987 	int j;
1988 	for(size_t idx=0; idx<num_pts-1; idx++) {
1989 		// get the start and endpoints
1990 		s1 = pts->at(idx);													// start 1 (on the line)
1991 		e1 = pts->at(idx + 1);												// end 1 (on the line)
1992 		gr_pline_helper(&s2, &s1, &e1, thickness);	// start 2
1993 		vm_vec_sub(&dir, &e1, &s1);
1994 		vm_vec_add(&e2, &s2, &dir);											// end 2
1995 
1996 																			// stuff coords
1997 		v[0].screen.xyw.x = (float)ceil(s1.xyz.x);
1998 		v[0].screen.xyw.y = (float)ceil(s1.xyz.y);
1999 		v[0].screen.xyw.w = sw;
2000 		v[0].texture_position.u = 0.5f;
2001 		v[0].texture_position.v = 0.5f;
2002 		v[0].flags = PF_PROJECTED;
2003 		v[0].codes = 0;
2004 		v[0].r = clr.red;
2005 		v[0].g = clr.green;
2006 		v[0].b = clr.blue;
2007 		v[0].a = clr.alpha;
2008 
2009 		v[1].screen.xyw.x = (float)ceil(s2.xyz.x);
2010 		v[1].screen.xyw.y = (float)ceil(s2.xyz.y);
2011 		v[1].screen.xyw.w = sw;
2012 		v[1].texture_position.u = 0.5f;
2013 		v[1].texture_position.v = 0.5f;
2014 		v[1].flags = PF_PROJECTED;
2015 		v[1].codes = 0;
2016 		v[1].r = clr.red;
2017 		v[1].g = clr.green;
2018 		v[1].b = clr.blue;
2019 		v[1].a = clr.alpha;
2020 
2021 		v[2].screen.xyw.x = (float)ceil(e2.xyz.x);
2022 		v[2].screen.xyw.y = (float)ceil(e2.xyz.y);
2023 		v[2].screen.xyw.w = sw;
2024 		v[2].texture_position.u = 0.5f;
2025 		v[2].texture_position.v = 0.5f;
2026 		v[2].flags = PF_PROJECTED;
2027 		v[2].codes = 0;
2028 		v[2].r = clr.red;
2029 		v[2].g = clr.green;
2030 		v[2].b = clr.blue;
2031 		v[2].a = clr.alpha;
2032 
2033 		v[3].screen.xyw.x = (float)ceil(e1.xyz.x);
2034 		v[3].screen.xyw.y = (float)ceil(e1.xyz.y);
2035 		v[3].screen.xyw.w = sw;
2036 		v[3].texture_position.u = 0.5f;
2037 		v[3].texture_position.v = 0.5f;
2038 		v[3].flags = PF_PROJECTED;
2039 		v[3].codes = 0;
2040 		v[3].r = clr.red;
2041 		v[3].g = clr.green;
2042 		v[3].b = clr.blue;
2043 		v[3].a = clr.alpha;
2044 
2045 		//We could really do this better...but oh well. _WMC
2046 		if ( resize_mode != GR_RESIZE_NONE ) {
2047 			for ( j = 0; j<4; j++ ) {
2048 				gr_resize_screen_posf(&v[j].screen.xyw.x, &v[j].screen.xyw.y, NULL, NULL, resize_mode);
2049 			}
2050 		}
2051 
2052 		// draw the polys
2053 		//g3_draw_poly_constant_sw(4, verts, TMAP_FLAG_GOURAUD | TMAP_FLAG_RGB, 0.1f);
2054 		g3_render_primitives_colored(&material_def, v, 4, PRIM_TYPE_TRIFAN, true);
2055 
2056 		// if we're past the first section, draw a "patch" triangle to fill any gaps
2057 		if ( idx > 0 ) {
2058 			// stuff coords
2059 			v[0].screen.xyw.x = (float)ceil(s1.xyz.x);
2060 			v[0].screen.xyw.y = (float)ceil(s1.xyz.y);
2061 			v[0].screen.xyw.w = sw;
2062 			v[0].texture_position.u = 0.5f;
2063 			v[0].texture_position.v = 0.5f;
2064 			v[0].flags = PF_PROJECTED;
2065 			v[0].codes = 0;
2066 			v[0].r = clr.red;
2067 			v[0].g = clr.green;
2068 			v[0].b = clr.blue;
2069 			v[0].a = clr.alpha;
2070 
2071 			v[1].screen.xyw.x = (float)ceil(s2.xyz.x);
2072 			v[1].screen.xyw.y = (float)ceil(s2.xyz.y);
2073 			v[1].screen.xyw.w = sw;
2074 			v[1].texture_position.u = 0.5f;
2075 			v[1].texture_position.v = 0.5f;
2076 			v[1].flags = PF_PROJECTED;
2077 			v[1].codes = 0;
2078 			v[1].r = clr.red;
2079 			v[1].g = clr.green;
2080 			v[1].b = clr.blue;
2081 			v[1].a = clr.alpha;
2082 
2083 			v[2].screen.xyw.x = (float)ceil(last_e2.xyz.x);
2084 			v[2].screen.xyw.y = (float)ceil(last_e2.xyz.y);
2085 			v[2].screen.xyw.w = sw;
2086 			v[2].texture_position.u = 0.5f;
2087 			v[2].texture_position.v = 0.5f;
2088 			v[2].flags = PF_PROJECTED;
2089 			v[2].codes = 0;
2090 			v[2].r = clr.red;
2091 			v[2].g = clr.green;
2092 			v[2].b = clr.blue;
2093 			v[2].a = clr.alpha;
2094 
2095 			//Inefficiency or flexibility? you be the judge -WMC
2096 			if ( resize_mode != GR_RESIZE_NONE ) {
2097 				for ( j = 0; j<3; j++ ) {
2098 					gr_resize_screen_posf(&v[j].screen.xyw.x, &v[j].screen.xyw.y, NULL, NULL, resize_mode);
2099 				}
2100 			}
2101 
2102 			g3_render_primitives_colored(&material_def, v, 3, PRIM_TYPE_TRIFAN, true);
2103 		}
2104 
2105 		// store our endpoints
2106 		last_e1 = e1;
2107 		last_e2 = e2;
2108 	}
2109 
2110 	if ( started_frame ) {
2111 		g3_end_frame();
2112 	}
2113 }
2114 
find_first_vertex(int idx)2115 int poly_list::find_first_vertex(int idx)
2116 {
2117 	vec3d *o_norm = &norm[idx];
2118 	vertex *o_vert = &vert[idx];
2119 	vec3d *p_norm = &norm[0];
2120 	vertex *p_vert = &vert[0];
2121 
2122 	// we should always equal ourselves, so just use that as the stopping point
2123 	for (int i = 0; i < idx; i++) {
2124 		if ( (*p_norm == *o_norm)
2125 			&& (p_vert->world == o_vert->world)
2126 			&& (p_vert->texture_position == o_vert->texture_position) )
2127 		{
2128 			return i;
2129 		}
2130 
2131 		++p_norm;
2132 		++p_vert;
2133 	}
2134 
2135 	return idx;
2136 }
2137 
find_first_vertex_fast(int idx)2138 int poly_list::find_first_vertex_fast(int idx)
2139 {
2140 	uint* first_idx = std::lower_bound(sorted_indices, sorted_indices + n_verts, idx, finder(this, NULL, NULL));
2141 
2142 	if ( first_idx == sorted_indices + n_verts ) {
2143 		// if this happens then idx was never in the index list to begin with which is not good
2144 		mprintf(("Sorted index list missing index %d!\n", idx));
2145 		Int3();
2146 		return idx;
2147 	}
2148 
2149 	return *first_idx;
2150 }
2151 
2152 /**
2153  * Given a list (plist) find the index within the indexed list that the vert at position idx within list is at
2154  */
find_index(poly_list * plist,int idx)2155 int poly_list::find_index(poly_list *plist, int idx)
2156 {
2157 	vec3d *o_norm = &plist->norm[idx];
2158 	vertex *o_vert = &plist->vert[idx];
2159 	vec3d *p_norm = &norm[0];
2160 	vertex *p_vert = &vert[0];
2161 
2162 	for (int i = 0; i < n_verts; i++) {
2163 		if ( (*p_norm == *o_norm)
2164 			&& (p_vert->world == o_vert->world)
2165 			&& (p_vert->texture_position == o_vert->texture_position))
2166 		{
2167 			return i;
2168 		}
2169 
2170 		++p_vert;
2171 		++p_norm;
2172 	}
2173 
2174 	return -1;
2175 }
2176 
find_index_fast(poly_list * plist,int idx)2177 int poly_list::find_index_fast(poly_list *plist, int idx)
2178 {
2179 	// searching for an out of bounds index using the finder means we're trying to find the vert and norm we're passing into the finder instance
2180 	uint* first_idx = std::lower_bound(sorted_indices, sorted_indices + n_verts, n_verts, finder(this, &plist->vert[idx], &plist->norm[idx]));
2181 
2182 	if (first_idx == sorted_indices + n_verts) {
2183 		return -1;
2184 	}
2185 
2186 	return *first_idx;
2187 }
2188 
allocate(int _verts)2189 void poly_list::allocate(int _verts)
2190 {
2191 	if (_verts <= currently_allocated)
2192 		return;
2193 
2194 	if (vert != NULL) {
2195 		vm_free(vert);
2196 		vert = NULL;
2197 	}
2198 
2199 	if (norm != NULL) {
2200 		vm_free(norm);
2201 		norm = NULL;
2202 	}
2203 
2204 	if (tsb != NULL) {
2205 		vm_free(tsb);
2206 		tsb = NULL;
2207 	}
2208 
2209 	if ( submodels != NULL ) {
2210 		vm_free(submodels);
2211 		submodels = NULL;
2212 	}
2213 
2214 	if ( sorted_indices != NULL ) {
2215 		vm_free(sorted_indices);
2216 		sorted_indices = NULL;
2217 	}
2218 
2219 	if (_verts) {
2220 		vert = (vertex*)vm_malloc(sizeof(vertex) * _verts);
2221 		norm = (vec3d*)vm_malloc(sizeof(vec3d) * _verts);
2222 
2223 		if (Cmdline_normal) {
2224 			tsb = (tsb_t*)vm_malloc(sizeof(tsb_t) * _verts);
2225 		}
2226 
2227 		submodels = (int*)vm_malloc(sizeof(int) * _verts);
2228 
2229 		sorted_indices = (uint*)vm_malloc(sizeof(uint) * _verts);
2230 	}
2231 
2232 	n_verts = 0;
2233 	currently_allocated = _verts;
2234 }
2235 
~poly_list()2236 poly_list::~poly_list()
2237 {
2238 	if (vert != NULL) {
2239 		vm_free(vert);
2240 		vert = NULL;
2241 	}
2242 
2243 	if (norm != NULL) {
2244 		vm_free(norm);
2245 		norm = NULL;
2246 	}
2247 
2248 	if (tsb != NULL) {
2249 		vm_free(tsb);
2250 		tsb = NULL;
2251 	}
2252 
2253 	if ( submodels != NULL ) {
2254 		vm_free(submodels);
2255 		submodels = NULL;
2256 	}
2257 
2258 	if (sorted_indices != NULL) {
2259 		vm_free(sorted_indices);
2260 		sorted_indices = NULL;
2261 	}
2262 }
2263 
calculate_tangent()2264 void poly_list::calculate_tangent()
2265 {
2266 	vertex *v0, *v1, *v2;
2267 	vec3d *t0, *t1, *t2;
2268 	vec3d side0, side1;
2269 	vec3d vt0, vt1;
2270 	float deltaU0, deltaV0, deltaU1, deltaV1;
2271 	vec3d tangent, binormal, cross;
2272 	float magg, scale;
2273 
2274 	if ( !Cmdline_normal ) {
2275 		return;
2276 	}
2277 
2278 	Assert( !(n_verts % 3) );
2279 
2280 	for (int i = 0; i < n_verts; i += 3) {
2281 		// vertex (reading)
2282 		v0 = &vert[i];
2283 		v1 = &vert[i+1];
2284 		v2 = &vert[i+2];
2285 		// tangents (writing)
2286 		t0 = &tsb[i].tangent;
2287 		t1 = &tsb[i+1].tangent;
2288 		t2 = &tsb[i+2].tangent;
2289 
2290 
2291 		deltaU0 = v1->texture_position.u - v0->texture_position.u;
2292 		deltaV0 = v1->texture_position.v - v0->texture_position.v;
2293 
2294 		deltaU1 = v2->texture_position.u - v0->texture_position.u;
2295 		deltaV1 = v2->texture_position.v - v0->texture_position.v;
2296 
2297 		// quick short circuit for NULL case
2298 		float n = (deltaU0 * deltaV1) - (deltaU1 * deltaV0);
2299 
2300 		if (n == 0.0f) {
2301 			// hit NULL, so just set identity
2302 			tangent  = vmd_x_vector;
2303 			binormal = vmd_y_vector;
2304 		} else {
2305 			float blah = 1.0f / n;
2306 
2307 			vm_vec_sub(&side0, &v1->world, &v0->world);
2308 			vm_vec_sub(&side1, &v2->world, &v0->world);
2309 
2310 			// tangent
2311 			vm_vec_copy_scale(&vt0, &side0, deltaV1);
2312 			vm_vec_copy_scale(&vt1, &side1, deltaV0);
2313 			vm_vec_sub(&tangent, &vt0, &vt1);
2314 			vm_vec_scale(&tangent, blah);
2315 
2316 			// binormal
2317 			vm_vec_copy_scale(&vt0, &side0, deltaU1);
2318 			vm_vec_copy_scale(&vt1, &side1, deltaU0);
2319 			vm_vec_sub(&binormal, &vt0, &vt1);
2320 			vm_vec_scale(&binormal, blah);
2321 		}
2322 
2323 		// orthogonalize tangent (for all 3 verts)
2324 		magg = vm_vec_dot(&norm[i], &tangent);
2325 		vm_vec_scale_sub(t0, &tangent, &norm[i], magg);
2326 		vm_vec_normalize_safe(t0);
2327 
2328 		magg = vm_vec_dot(&norm[i+1], &tangent);
2329 		vm_vec_scale_sub(t1, &tangent, &norm[i+1], magg);
2330 		vm_vec_normalize_safe(t1);
2331 
2332 		magg = vm_vec_dot(&norm[i+2], &tangent);
2333 		vm_vec_scale_sub(t2, &tangent, &norm[i+2], magg);
2334 		vm_vec_normalize_safe(t2);
2335 
2336 		// compute handedness (for all 3 verts)
2337 		vm_vec_cross(&cross, &norm[i], &tangent);
2338 		scale = vm_vec_dot(&cross, &binormal);
2339 		tsb[i].scaler = (scale < 0.0f) ? -1.0f : 1.0f;
2340 
2341 		vm_vec_cross(&cross, &norm[i+1], &tangent);
2342 		scale = vm_vec_dot(&cross, &binormal);
2343 		tsb[i+1].scaler = (scale < 0.0f) ? -1.0f : 1.0f;
2344 
2345 		vm_vec_cross(&cross, &norm[i+2], &tangent);
2346 		scale = vm_vec_dot(&cross, &binormal);
2347 		tsb[i+2].scaler = (scale < 0.0f) ? -1.0f : 1.0f;
2348 	}
2349 }
2350 
2351 static poly_list buffer_list_internal;
2352 
make_index_buffer(SCP_vector<int> & vertex_list)2353 void poly_list::make_index_buffer(SCP_vector<int> &vertex_list)
2354 {
2355 	int nverts = 0;
2356 	int j, z = 0;
2357 	ubyte *nverts_good = NULL;
2358 
2359 	// calculate tangent space data (must be done early)
2360 	calculate_tangent();
2361 
2362 	// using vm_malloc() here rather than 'new' so we get the extra out-of-memory check
2363 	nverts_good = (ubyte *) vm_malloc(n_verts);
2364 
2365     Assert( nverts_good != NULL );
2366 	if ( nverts_good == NULL )
2367 		return;
2368 
2369 	memset( nverts_good, 0, n_verts );
2370 
2371 	vertex_list.reserve(n_verts);
2372 
2373 	generate_sorted_index_list();
2374 
2375 	for (j = 0; j < n_verts; j++) {
2376 		if (find_first_vertex_fast(j) == j) {
2377 			nverts++;
2378 			nverts_good[j] = 1;
2379 			vertex_list.push_back(j);
2380 		}
2381 	}
2382 
2383 	// if there is nothig to change then bail
2384 	if (n_verts == nverts) {
2385 		if (nverts_good != NULL) {
2386 			vm_free(nverts_good);
2387 		}
2388 
2389 		return;
2390 	}
2391 
2392 	buffer_list_internal.n_verts = 0;
2393 	buffer_list_internal.allocate(nverts);
2394 
2395 	for (j = 0; j < n_verts; j++) {
2396 		if ( !nverts_good[j] ) {
2397 			continue;
2398 		}
2399 
2400 		buffer_list_internal.vert[z] = vert[j];
2401 		buffer_list_internal.norm[z] = norm[j];
2402 
2403 		if (Cmdline_normal) {
2404 			buffer_list_internal.tsb[z] = tsb[j];
2405 		}
2406 
2407 		buffer_list_internal.submodels[z] = submodels[j];
2408 
2409 		buffer_list_internal.n_verts++;
2410 		z++;
2411 	}
2412 
2413 	Assert(nverts == buffer_list_internal.n_verts);
2414 
2415 	if (nverts_good != NULL) {
2416 		vm_free(nverts_good);
2417 	}
2418 
2419 	buffer_list_internal.generate_sorted_index_list();
2420 
2421 	(*this) = buffer_list_internal;
2422 }
2423 
operator =(const poly_list & other_list)2424 poly_list& poly_list::operator = (const poly_list &other_list)
2425 {
2426 	allocate(other_list.n_verts);
2427 
2428 	memcpy(norm, other_list.norm, sizeof(vec3d) * other_list.n_verts);
2429 	memcpy(vert, other_list.vert, sizeof(vertex) * other_list.n_verts);
2430 
2431 	if (Cmdline_normal) {
2432 		memcpy(tsb, other_list.tsb, sizeof(tsb_t) * other_list.n_verts);
2433 	}
2434 
2435 	memcpy(submodels, other_list.submodels, sizeof(int) * other_list.n_verts);
2436 
2437 	memcpy(sorted_indices, other_list.sorted_indices, sizeof(uint) * other_list.n_verts);
2438 
2439 	n_verts = other_list.n_verts;
2440 
2441 	return *this;
2442 }
2443 
generate_sorted_index_list()2444 void poly_list::generate_sorted_index_list()
2445 {
2446 	for ( int j = 0; j < n_verts; ++j) {
2447 		sorted_indices[j] = j;
2448 	}
2449 
2450 	std::sort(sorted_indices, sorted_indices + n_verts, finder(this));
2451 }
2452 
operator ()(const uint a,const uint b)2453 bool poly_list::finder::operator()(const uint a, const uint b)
2454 {
2455 	vertex *vert_a;
2456 	vertex *vert_b;
2457 	vec3d *norm_a;
2458 	vec3d *norm_b;
2459 
2460 	Assert(search_list != NULL);
2461 
2462 	if ( a == (uint)search_list->n_verts ) {
2463 		Assert(vert_to_find != NULL);
2464 		Assert(norm_to_find != NULL);
2465 		Assert(a != b);
2466 
2467 		vert_a = vert_to_find;
2468 		norm_a = norm_to_find;
2469 	} else {
2470 		vert_a = &search_list->vert[a];
2471 		norm_a = &search_list->norm[a];
2472 	}
2473 
2474 	if ( b == (uint)search_list->n_verts ) {
2475 		Assert(vert_to_find != NULL);
2476 		Assert(norm_to_find != NULL);
2477 		Assert(a != b);
2478 
2479 		vert_b = vert_to_find;
2480 		norm_b = norm_to_find;
2481 	} else {
2482 		vert_b = &search_list->vert[b];
2483 		norm_b = &search_list->norm[b];
2484 	}
2485 
2486 	if (norm_a->xyz.x != norm_b->xyz.x) {
2487 		return norm_a->xyz.x < norm_b->xyz.x;
2488 	}
2489 
2490 	if (norm_a->xyz.y != norm_b->xyz.y) {
2491 		return norm_a->xyz.y < norm_b->xyz.y;
2492 	}
2493 
2494 	if (norm_a->xyz.z != norm_b->xyz.z) {
2495 		return norm_a->xyz.z < norm_b->xyz.z;
2496 	}
2497 
2498 	if (vert_a->world.xyz.x != vert_b->world.xyz.x) {
2499 		return vert_a->world.xyz.x < vert_b->world.xyz.x;
2500 	}
2501 
2502 	if (vert_a->world.xyz.y != vert_b->world.xyz.y) {
2503 		return vert_a->world.xyz.y < vert_b->world.xyz.y;
2504 	}
2505 
2506 	if (vert_a->world.xyz.z != vert_b->world.xyz.z) {
2507 		return vert_a->world.xyz.z < vert_b->world.xyz.z;
2508 	}
2509 
2510 	if (vert_a->texture_position.u != vert_b->texture_position.u) {
2511 		return vert_a->texture_position.u < vert_b->texture_position.u;
2512 	}
2513 
2514 	if ( vert_a->texture_position.v != vert_b->texture_position.v ) {
2515 		return vert_a->texture_position.v < vert_b->texture_position.v;
2516 	}
2517 
2518 	if ( !compare_indices ) {
2519 		return vert_a->texture_position.v < vert_b->texture_position.v;
2520 	} else {
2521 		return a < b;
2522 	}
2523 }
2524 
gr_set_bitmap(int bitmap_num,int alphablend_mode,int bitblt_mode,float alpha)2525 void gr_set_bitmap(int bitmap_num, int alphablend_mode, int bitblt_mode, float alpha)
2526 {
2527 	gr_screen.current_alpha           = alpha;
2528 	gr_screen.current_alphablend_mode = alphablend_mode;
2529 	gr_screen.current_bitblt_mode     = bitblt_mode;
2530 	gr_screen.current_bitmap          = bitmap_num;
2531 }
2532 
output_uniform_debug_data()2533 static void output_uniform_debug_data()
2534 {
2535 	int line_height = gr_get_font_height() + 1;
2536 
2537 	gr_set_color_fast(&Color_bright_white);
2538 
2539 	gr_printf_no_resize(gr_screen.center_offset_x + 20, gr_screen.center_offset_y + 160,
2540 	                    "Uniform buffer size: " SIZE_T_ARG, UniformBufferManager->getBufferSize());
2541 	gr_printf_no_resize(gr_screen.center_offset_x + 20, gr_screen.center_offset_y + 160 + line_height,
2542 	                    "Currently used data: " SIZE_T_ARG, UniformBufferManager->getCurrentlyUsedSize());
2543 }
2544 
gr_flip(bool execute_scripting)2545 void gr_flip(bool execute_scripting)
2546 {
2547 	// Execute external "On Frame" work items
2548 	executor::OnFrameExecutor->process();
2549 
2550 	// m!m avoid running CHA_ONFRAME when the "Quit mission" popup is shown. See mantis 2446 for reference
2551 	// Cyborg - A similar bug will occur when a mission is restarted so check for that, too.
2552 	if (execute_scripting && !popup_active() && !((gameseq_get_state() == GS_STATE_GAME_PLAY) && !(Game_mode & GM_IN_MISSION))) {
2553 		TRACE_SCOPE(tracing::LuaOnFrame);
2554 
2555 		// WMC - Do conditional hooks. Yippee!
2556 		OnFrameHook->run();
2557 		// WMC - Do scripting reset stuff
2558 		Script_system.EndFrame();
2559 	}
2560 
2561 	gr_reset_immediate_buffer();
2562 
2563 	// Do per frame operations on the matrix state
2564 	gr_matrix_on_frame();
2565 
2566 	gr_reset_clip();
2567 
2568 	mouse_reset_deltas();
2569 
2570 	if (Cmdline_graphics_debug_output) {
2571 		output_uniform_debug_data();
2572 	}
2573 
2574 	// Use this opportunity for retiring the uniform buffers
2575 	uniform_buffer_managers_retire_buffers();
2576 
2577 	TRACE_SCOPE(tracing::PageFlip);
2578 	gr_screen.gf_flip();
2579 }
2580 
gr_print_timestamp(int x,int y,fix timestamp,int resize_mode)2581 void gr_print_timestamp(int x, int y, fix timestamp, int resize_mode)
2582 {
2583 	int seconds = fl2i(f2fl(timestamp));
2584 
2585 	// format the time information into strings
2586 	SCP_string time;
2587 	sprintf(time, "%.1d:%.2d:%.2d", (seconds / 3600) % 10, (seconds / 60) % 60, seconds % 60);
2588 
2589 	gr_string(x, y, time.c_str(), resize_mode);
2590 }
uniform_buffer_managers_init()2591 static void uniform_buffer_managers_init() { UniformBufferManager.reset(new graphics::util::UniformBufferManager()); }
uniform_buffer_managers_deinit()2592 static void uniform_buffer_managers_deinit() { UniformBufferManager.reset(); }
uniform_buffer_managers_retire_buffers()2593 static void uniform_buffer_managers_retire_buffers() { UniformBufferManager->onFrameEnd(); }
2594 
gr_get_uniform_buffer(uniform_block_type type,size_t num_elements,size_t element_size_override)2595 graphics::util::UniformBuffer gr_get_uniform_buffer(uniform_block_type type, size_t num_elements, size_t element_size_override)
2596 {
2597 	return UniformBufferManager->getUniformBuffer(type, num_elements, element_size_override);
2598 }
2599 
gr_enumerate_displays()2600 SCP_vector<DisplayData> gr_enumerate_displays()
2601 {
2602 	// It seems that linux cannot handle having the video subsystem inited
2603 	// too late
2604 	if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) {
2605 		return SCP_vector<DisplayData>();
2606 	}
2607 
2608 	SCP_vector<DisplayData> data;
2609 
2610 	auto num_displays = SDL_GetNumVideoDisplays();
2611 	for (auto i = 0; i < num_displays; ++i) {
2612 		DisplayData display;
2613 		display.index = i;
2614 
2615 		SDL_Rect bounds;
2616 		if (SDL_GetDisplayBounds(i, &bounds) == 0) {
2617 			display.x = bounds.x;
2618 			display.y = bounds.y;
2619 			display.width = bounds.w;
2620 			display.height = bounds.h;
2621 		}
2622 
2623 		auto name = SDL_GetDisplayName(i);
2624 		if (name != nullptr) {
2625 			display.name = name;
2626 		}
2627 
2628 		auto num_mods = SDL_GetNumDisplayModes(i);
2629 		for (auto j = 0; j < num_mods; ++j) {
2630 			SDL_DisplayMode mode;
2631 			if (SDL_GetDisplayMode(i, j, &mode) != 0) {
2632 				continue;
2633 			}
2634 
2635 			VideoModeData videoMode;
2636 			videoMode.width = mode.w;
2637 			videoMode.height = mode.h;
2638 
2639 			int sdlBits = SDL_BITSPERPIXEL(mode.format);
2640 
2641 			if (SDL_ISPIXELFORMAT_ALPHA(mode.format)) {
2642 				videoMode.bit_depth = sdlBits;
2643 			} else {
2644 				// Fix a few values
2645 				if (sdlBits == 24) {
2646 					videoMode.bit_depth = 32;
2647 				} else if (sdlBits == 15) {
2648 					videoMode.bit_depth = 16;
2649 				} else {
2650 					videoMode.bit_depth = sdlBits;
2651 				}
2652 			}
2653 
2654 			display.video_modes.push_back(videoMode);
2655 		}
2656 
2657 		data.push_back(display);
2658 	}
2659 
2660 	SDL_QuitSubSystem(SDL_INIT_VIDEO);
2661 
2662 	return data;
2663 }
2664 
2665 namespace std {
2666 
operator ()(const vertex_format_data & data) const2667 size_t hash<vertex_format_data>::operator()(const vertex_format_data& data) const {
2668 	size_t seed = 0;
2669 	boost::hash_combine(seed, (size_t)data.format_type);
2670 	boost::hash_combine(seed, data.offset);
2671 	boost::hash_combine(seed, data.stride);
2672 	return seed;
2673 }
operator ()(const vertex_layout & data) const2674 size_t hash<vertex_layout>::operator()(const vertex_layout& data) const {
2675 	return data.hash();
2676 }
2677 
2678 }
resident_vertex_format(vertex_format_data::vertex_format format_type) const2679 bool vertex_layout::resident_vertex_format(vertex_format_data::vertex_format format_type) const {
2680 	return ( Vertex_mask & vertex_format_data::mask(format_type) ) ? true : false;
2681 }
add_vertex_component(vertex_format_data::vertex_format format_type,size_t stride,size_t offset)2682 void vertex_layout::add_vertex_component(vertex_format_data::vertex_format format_type, size_t stride, size_t offset) {
2683 	// A stride value of 0 is not handled consistently by the graphics API so we must enforce that that does not happen
2684 	Assertion(stride != 0, "The stride of a vertex component may not be zero!");
2685 
2686 	if ( resident_vertex_format(format_type) ) {
2687 		// we already have a vertex component of this format type
2688 		return;
2689 	}
2690 
2691 	if (Vertex_mask == 0) {
2692 		// This is the first element so we need to initialize the global stride here
2693 		Vertex_stride = stride;
2694 	}
2695 
2696 	Assertion(Vertex_stride == stride, "The strides of all elements must be the same in a vertex layout!");
2697 
2698 	Vertex_mask |= (1 << format_type);
2699 	Vertex_components.push_back(vertex_format_data(format_type, stride, offset));
2700 }
operator ==(const vertex_layout & other) const2701 bool vertex_layout::operator==(const vertex_layout& other) const {
2702 	if (Vertex_mask != other.Vertex_mask) {
2703 		return false;
2704 	}
2705 
2706 	if (Vertex_components.size() != other.Vertex_components.size()) {
2707 		return false;
2708 	}
2709 
2710 	return std::equal(Vertex_components.cbegin(),
2711 					  Vertex_components.cend(),
2712 					  other.Vertex_components.cbegin());
2713 }
hash() const2714 size_t vertex_layout::hash() const {
2715 	size_t seed = 0;
2716 	boost::hash_combine(seed, Vertex_mask);
2717 
2718 	for (auto& comp : Vertex_components) {
2719 		boost::hash_combine(seed, comp);
2720 	}
2721 
2722 	return seed;
2723 }
2724 
2725 static std::unique_ptr<graphics::util::GPUMemoryHeap> gpu_heaps [static_cast<size_t>(GpuHeap::NUM_VALUES)];
2726 
gpu_heap_init()2727 static void gpu_heap_init() {
2728 	for (size_t i = 0; i < static_cast<size_t>(GpuHeap::NUM_VALUES); ++i) {
2729 		auto enumVal = static_cast<GpuHeap>(i);
2730 
2731 		gpu_heaps[i].reset(new graphics::util::GPUMemoryHeap(enumVal));
2732 	}
2733 }
2734 
gpu_heap_deinit()2735 static void gpu_heap_deinit() {
2736 	for (auto& heap : gpu_heaps) {
2737 		heap.reset();
2738 	}
2739 }
2740 
get_gpu_heap(GpuHeap heap_type)2741 static graphics::util::GPUMemoryHeap* get_gpu_heap(GpuHeap heap_type) {
2742 	Assertion(heap_type != GpuHeap::NUM_VALUES, "Invalid heap type value detected.");
2743 
2744 	return gpu_heaps[static_cast<size_t>(heap_type)].get();
2745 }
2746 
gr_heap_allocate(GpuHeap heap_type,size_t size,void * data,size_t & offset_out,gr_buffer_handle & handle_out)2747 void gr_heap_allocate(GpuHeap heap_type, size_t size, void* data, size_t& offset_out, gr_buffer_handle& handle_out) {
2748 	TRACE_SCOPE(tracing::GpuHeapAllocate);
2749 
2750 	auto gpuHeap = get_gpu_heap(heap_type);
2751 
2752 	offset_out = gpuHeap->allocateGpuData(size, data);
2753 	handle_out = gpuHeap->bufferHandle();
2754 }
2755 
gr_heap_deallocate(GpuHeap heap_type,size_t data_offset)2756 void gr_heap_deallocate(GpuHeap heap_type, size_t data_offset)
2757 {
2758 	TRACE_SCOPE(tracing::GpuHeapDeallocate);
2759 
2760 	auto gpuHeap = get_gpu_heap(heap_type);
2761 
2762 	gpuHeap->freeGpuData(data_offset);
2763 }
2764 
2765 // I feel dirty...
make_gamma_ramp(float gamma,ushort * ramp)2766 static void make_gamma_ramp(float gamma, ushort* ramp)
2767 {
2768 	ushort x, y;
2769 	ushort base_ramp[256];
2770 
2771 	Assert(ramp != nullptr);
2772 
2773 	// generate the base ramp values first off
2774 
2775 	// if no gamma set then just do this quickly
2776 	if (gamma <= 0.0f) {
2777 		memset(ramp, 0, 3 * 256 * sizeof(ushort));
2778 		return;
2779 	}
2780 	// identity gamma, avoid all of the math
2781 	else if (gamma == 1.0f || Gr_original_gamma_ramp == nullptr) {
2782 		if (Gr_original_gamma_ramp != nullptr) {
2783 			memcpy(ramp, Gr_original_gamma_ramp, 3 * 256 * sizeof(ushort));
2784 		}
2785 		// set identity if no original ramp
2786 		else {
2787 			for (x = 0; x < 256; x++) {
2788 				ramp[x]       = (x << 8) | x;
2789 				ramp[x + 256] = (x << 8) | x;
2790 				ramp[x + 512] = (x << 8) | x;
2791 			}
2792 		}
2793 
2794 		return;
2795 	}
2796 	// for everything else we need to actually figure it up
2797 	else {
2798 		double g = 1.0 / (double)gamma;
2799 		double val;
2800 
2801 		Assert(Gr_original_gamma_ramp != nullptr);
2802 
2803 		for (x = 0; x < 256; x++) {
2804 			val = (pow(x / 255.0, g) * 65535.0 + 0.5);
2805 			CLAMP(val, 0, 65535);
2806 
2807 			base_ramp[x] = (ushort)val;
2808 		}
2809 
2810 		for (y = 0; y < 3; y++) {
2811 			for (x = 0; x < 256; x++) {
2812 				val = (base_ramp[x] * 2) - Gr_original_gamma_ramp[x + y * 256];
2813 				CLAMP(val, 0, 65535);
2814 
2815 				ramp[x + y * 256] = (ushort)val;
2816 			}
2817 		}
2818 	}
2819 }
2820 
gr_set_gamma(float gamma)2821 void gr_set_gamma(float gamma)
2822 {
2823 	Gr_gamma = gamma;
2824 
2825 	// new way - but not while running FRED
2826 	if (!Fred_running && !Cmdline_no_set_gamma && os::getSDLMainWindow() != nullptr) {
2827 		if (Gr_original_gamma_ramp == nullptr) {
2828 			// First time we are here so get the current (original) gamma ramp here so we can reset it later
2829 			Gr_original_gamma_ramp = (ushort*)vm_malloc(3 * 256 * sizeof(ushort), memory::quiet_alloc);
2830 
2831 			if (Gr_original_gamma_ramp == nullptr) {
2832 				mprintf(("  Unable to allocate memory for gamma ramp!  Disabling...\n"));
2833 				Cmdline_no_set_gamma = 1;
2834 			} else {
2835 				SDL_GetWindowGammaRamp(os::getSDLMainWindow(), Gr_original_gamma_ramp, (Gr_original_gamma_ramp + 256),
2836 									   (Gr_original_gamma_ramp + 512));
2837 			}
2838 		}
2839 
2840 		auto gamma_ramp = (ushort*)vm_malloc(3 * 256 * sizeof(ushort), memory::quiet_alloc);
2841 
2842 		if (gamma_ramp == nullptr) {
2843 			Int3();
2844 			return;
2845 		}
2846 
2847 		memset(gamma_ramp, 0, 3 * 256 * sizeof(ushort));
2848 
2849 		// Create the Gamma lookup table
2850 		make_gamma_ramp(gamma, gamma_ramp);
2851 
2852 		SDL_SetWindowGammaRamp(os::getSDLMainWindow(), gamma_ramp, (gamma_ramp + 256), (gamma_ramp + 512));
2853 
2854 		vm_free(gamma_ramp);
2855 	}
2856 }
2857 
gr_get_post_process_effect_names(SCP_vector<SCP_string> & names)2858 void gr_get_post_process_effect_names(SCP_vector<SCP_string>& names)
2859 {
2860 	if (graphics::Post_processing_manager == nullptr) {
2861 		names.clear();
2862 		return;
2863 	}
2864 
2865 	const auto& effects = graphics::Post_processing_manager->getPostEffects();
2866 	for (const auto& eff : effects) {
2867 		names.push_back(eff.name);
2868 	}
2869 }
2870