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