/* * Copyright (C) Volition, Inc. 1999. All rights reserved. * * All source code herein is the property of Volition, Inc. You may not sell * or otherwise commercially exploit the source or things you created based on the * source. * */ #ifdef _WIN32 #include #include #endif #include "globalincs/alphacolors.h" #include "globalincs/systemvars.h" #include "2d.h" #include "grinternal.h" #include "grstub.h" #include "light.h" #include "material.h" #include "matrix.h" #include "cmdline/cmdline.h" #include "debugconsole/console.h" #include "executor/global_executors.h" #include "graphics/paths/PathRenderer.h" #include "graphics/post_processing.h" #include "graphics/util/GPUMemoryHeap.h" #include "graphics/util/UniformBuffer.h" #include "graphics/util/UniformBufferManager.h" #include "io/mouse.h" #include "libs/jansson.h" #include "options/Option.h" #include "osapi/osapi.h" #include "parse/parselo.h" #include "popup/popup.h" #include "render/3d.h" #include "scripting/hook_api.h" #include "scripting/scripting.h" #include "tracing/tracing.h" #include "utils/boost/hash_combine.h" #include "gamesequence/gamesequence.h" #ifdef WITH_OPENGL #include "graphics/opengl/gropengl.h" #endif #ifdef WITH_VULKAN #include "graphics/vulkan/gr_vulkan.h" #endif #include #include #include #if (SDL_VERSION_ATLEAST(1, 2, 7)) #include "SDL_cpuinfo.h" #endif const char* Resolution_prefixes[GR_NUM_RESOLUTIONS] = {"", "2_"}; screen gr_screen; color_gun Gr_red, Gr_green, Gr_blue, Gr_alpha; color_gun Gr_t_red, Gr_t_green, Gr_t_blue, Gr_t_alpha; color_gun Gr_ta_red, Gr_ta_green, Gr_ta_blue, Gr_ta_alpha; color_gun *Gr_current_red, *Gr_current_green, *Gr_current_blue, *Gr_current_alpha; ubyte Gr_original_palette[768]; // The palette ubyte Gr_current_palette[768]; char Gr_current_palette_name[128] = NOX("none"); // cursor stuff io::mouse::Cursor* Web_cursor = NULL; int Gr_inited = 0; uint Gr_signature = 0; float Gr_gamma = 1.0f; static SCP_vector gamma_value_enumerator() { SCP_vector vals; // We want to divide the possible values into increments of 0.05 constexpr auto UPPER_LIMIT = (int)(5.0 / 0.05); for (int i = 2; i <= UPPER_LIMIT; ++i) { vals.push_back(0.05f * i); } return vals; } static SCP_string gamma_display(float value) { SCP_string out; sprintf(out, "%.2f", value); return out; } static bool gamma_change_listener(float new_val, bool initial) { if (!initial) { // This is not valid for the initial config load since that happens before the graphics system is initialized gr_set_gamma(new_val); } else { Gr_gamma = new_val; } return true; } static auto GammaOption = options::OptionBuilder("Graphics.Gamma", "Brightness", "The brighness value used for the game window") .category("Graphics") .default_val(1.0f) .enumerator(gamma_value_enumerator) .display(gamma_display) .change_listener(gamma_change_listener) .finish(); const SCP_vector> DetailLevelValues = {{ 0, "Minimum" }, { 1, "Low" }, { 2, "Medium" }, { 3, "High" }, { 4, "Ultra" }, }; const auto LightingOption = options::OptionBuilder("Graphics.Lighting", "Lighting", "Level of detail of the lighting") .importance(1) .category("Graphics") .values(DetailLevelValues) .default_val(MAX_DETAIL_LEVEL) .change_listener([](int val, bool initial) { Detail.lighting = val; if (!initial) { gr_recompile_all_shaders(nullptr); } return true; }) .finish(); os::ViewportState Gr_configured_window_state = os::ViewportState::Windowed; static bool mode_change_func(os::ViewportState state, bool initial) { Gr_configured_window_state = state; if (initial) { return false; } auto window = os::getMainViewport(); if (window == nullptr) { return false; } window->setState(state); return true; } static auto WindowModeOption = options::OptionBuilder("Graphics.WindowMode", "Window Mode", "Controls how the game window is created.") .category("Graphics") .level(options::ExpertLevel::Beginner) .values({{os::ViewportState::Fullscreen, "Fullscreen"}, {os::ViewportState::Borderless, "Borderless"}, {os::ViewportState::Windowed, "Windowed"}}) .importance(98) .default_val(os::ViewportState::Fullscreen) .change_listener(mode_change_func) .finish(); const std::shared_ptr OnFrameHook = scripting::OverridableHook::Factory( "On Frame", "Called every frame as the last action before showing the frame result to the user.", {}, CHA_ONFRAME); // z-buffer stuff int gr_zbuffering = 0; int gr_zbuffering_mode = 0; int gr_global_zbuffering = 0; // stencil buffer stuff int gr_stencil_mode = 0; // Default clipping distances const float Default_min_draw_distance = 1.0f; const float Default_max_draw_distance = 1e10; float Min_draw_distance = Default_min_draw_distance; float Max_draw_distance = Default_max_draw_distance; // Pre-computed screen resize vars static float Gr_full_resize_X = 1.0f, Gr_full_resize_Y = 1.0f; static float Gr_full_center_resize_X = 1.0f, Gr_full_center_resize_Y = 1.0f; static float Gr_resize_X = 1.0f, Gr_resize_Y = 1.0f; static float Gr_menu_offset_X = 0.0f, Gr_menu_offset_Y = 0.0f; static float Gr_menu_zoomed_offset_X = 0.0f, Gr_menu_zoomed_offset_Y = 0.0f; float Gr_save_full_resize_X = 1.0f, Gr_save_full_resize_Y = 1.0f; float Gr_save_full_center_resize_X = 1.0f, Gr_save_full_center_resize_Y = 1.0f; float Gr_save_resize_X = 1.0f, Gr_save_resize_Y = 1.0f; float Gr_save_menu_offset_X = 0.0f, Gr_save_menu_offset_Y = 0.0f; float Gr_save_menu_zoomed_offset_X = 0.0f, Gr_save_menu_zoomed_offset_Y = 0.0f; bool Save_custom_screen_size; bool Deferred_lighting = false; bool High_dynamic_range = false; static ushort* Gr_original_gamma_ramp = nullptr; static int videodisplay_deserializer(const json_t* value) { int id; json_error_t err; if (json_unpack_ex((json_t*)value, &err, 0, "i", &id) != 0) { throw json_exception(err); } return id; } static json_t* videodisplay_serializer(int value) { return json_pack("i", value); } static SCP_vector videodisplay_enumerator() { SCP_vector vals; for (int i = 0; i < SDL_GetNumVideoDisplays(); ++i) { vals.push_back(i); } return vals; } static SCP_string videodisplay_display(int id) { SCP_string out; sprintf(out, "(%d) %s", id + 1, SDL_GetDisplayName(id)); return out; } static bool videodisplay_change(int display, bool initial) { if (initial) { return false; } auto window = os::getSDLMainWindow(); if (window == nullptr) { return false; } SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED_DISPLAY(display), SDL_WINDOWPOS_CENTERED_DISPLAY(display)); return true; } static auto VideoDisplayOption = options::OptionBuilder("Graphics.Display", "Primary display", "The display used for rendering.") .category("Graphics") .level(options::ExpertLevel::Beginner) .deserializer(videodisplay_deserializer) .serializer(videodisplay_serializer) .enumerator(videodisplay_enumerator) .display(videodisplay_display) .default_val(0) .change_listener(videodisplay_change) .importance(99) .finish(); struct ResolutionInfo { uint32_t width = 0; uint32_t height = 0; ResolutionInfo(uint32_t _width, uint32_t _height) : width(_width), height(_height) {} ResolutionInfo() = default; friend bool operator==(const ResolutionInfo& lhs, const ResolutionInfo& rhs) { return lhs.width == rhs.width && lhs.height == rhs.height; } friend bool operator!=(const ResolutionInfo& lhs, const ResolutionInfo& rhs) { return !(rhs == lhs); } }; static ResolutionInfo resolution_deserializer(const json_t* el) { int width; int height; json_error_t err; if (json_unpack_ex((json_t*)el, &err, 0, "{s:i, s:i}", "width", &width, "height", &height) != 0) { throw json_exception(err); } return {(uint32_t)width, (uint32_t)height}; } static json_t* resolution_serializer(const ResolutionInfo& value) { return json_pack("{s:i, s:i}", "width", value.width, "height", value.height); } static SCP_vector resolution_enumerator() { SCP_vector out; auto display = VideoDisplayOption->getValue(); for (auto i = 0; i < SDL_GetNumDisplayModes(display); ++i) { SDL_DisplayMode mode; if (SDL_GetDisplayMode(display, i, &mode) != 0) { continue; } auto res = ResolutionInfo(mode.w, mode.h); if (std::find(out.begin(), out.end(), res) == out.end()) { out.push_back(res); } } return out; } static SCP_string resolution_display(const ResolutionInfo& info) { SCP_string str; sprintf(str, "%dx%d", info.width, info.height); return str; } static ResolutionInfo resolution_default() { SDL_DisplayMode mode; if (SDL_GetDesktopDisplayMode(VideoDisplayOption->getValue(), &mode) != 0) { return {}; } return {(uint32_t)mode.w, (uint32_t)mode.h}; } static bool resolution_change(const ResolutionInfo& /*info*/, bool initial) { if (initial) { return false; } return false; // The following code should change the size of the window properly but FSO currently can't handle that /* auto window = os::getSDLMainWindow(); if (window == nullptr) { return; } auto display = VideoDisplayOption->getValue(); if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) { SDL_DisplayMode target; target.w = info.width; target.h = info.height; target.format = 0; // don't care target.refresh_rate = 0; // don't care target.driverdata = 0; // initialize to 0 SDL_DisplayMode closest; if (SDL_GetClosestDisplayMode(display, &target, &closest) == nullptr) { return; } SDL_SetWindowDisplayMode(window, &closest); } else { SDL_SetWindowSize(window, info.width, info.height); // Recenter the window SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED_DISPLAY(display), SDL_WINDOWPOS_CENTERED_DISPLAY(display)); } */ } static auto ResolutionOption = options::OptionBuilder("Graphics.Resolution", "Resolution", "The rendering resolution.") .category("Graphics") .level(options::ExpertLevel::Beginner) .deserializer(resolution_deserializer) .serializer(resolution_serializer) .enumerator(resolution_enumerator) .display(resolution_display) .default_func(resolution_default) .change_listener(resolution_change) .importance(100) .finish(); bool Gr_enable_soft_particles = false; static auto SoftParticlesOption = options::OptionBuilder("Graphics.SoftParticles", "Soft Particles", "Enable or disable soft particle rendering.") .category("Graphics") .level(options::ExpertLevel::Advanced) .default_val(true) .bind_to_once(&Gr_enable_soft_particles) .importance(68) .finish(); flagset Gr_framebuffer_effects; static auto FramebufferEffectsOption = options::OptionBuilder>( "Graphics.FramebufferEffects", "Framebuffer effects", "Controls which framebuffer effects will be applied to the scene.") .category("Graphics") .level(options::ExpertLevel::Advanced) .values({{{}, "None"}, {{FramebufferEffects::Shockwaves}, "Shockwaves"}, {{FramebufferEffects::Thrusters}, "Thrusters"}, {{FramebufferEffects::Shockwaves, FramebufferEffects::Thrusters}, "All"}}) .default_val({FramebufferEffects::Shockwaves, FramebufferEffects::Thrusters}) .bind_to_once(&Gr_framebuffer_effects) .importance(77) .finish(); AntiAliasMode Gr_aa_mode = AntiAliasMode::None; AntiAliasMode Gr_aa_mode_last_frame = AntiAliasMode::None; static auto AAOption = options::OptionBuilder("Graphics.AAMode", "Anti Aliasing", "Controls the anti aliasing mode of the engine.") .category("Graphics") .level(options::ExpertLevel::Advanced) .values({{AntiAliasMode::None, "None"}, {AntiAliasMode::FXAA_Low, "FXAA Low"}, {AntiAliasMode::FXAA_Medium, "FXAA Medium"}, {AntiAliasMode::FXAA_High, "FXAA High"}, {AntiAliasMode::SMAA_Low, "SMAA Low"}, {AntiAliasMode::SMAA_Medium, "SMAA Medium"}, {AntiAliasMode::SMAA_High, "SMAA High"}, {AntiAliasMode::SMAA_Ultra, "SMAA Ultra"}}) .default_val(AntiAliasMode::None) .bind_to(&Gr_aa_mode) .importance(79) .finish(); bool gr_is_fxaa_mode(AntiAliasMode mode) { return mode == AntiAliasMode::FXAA_Low || mode == AntiAliasMode::FXAA_Medium || mode == AntiAliasMode::FXAA_High; } bool gr_is_smaa_mode(AntiAliasMode mode) { return mode == AntiAliasMode::SMAA_Low || mode == AntiAliasMode::SMAA_Medium || mode == AntiAliasMode::SMAA_High || mode == AntiAliasMode::SMAA_Ultra; } bool Gr_post_processing_enabled = true; static auto PostProcessOption = options::OptionBuilder("Graphis.PostProcessing", "Post processing", "Controls whether post processing is enabled in the engine") .category("Graphics") .level(options::ExpertLevel::Advanced) .default_val(false) .bind_to_once(&Gr_post_processing_enabled) .importance(69) .finish(); bool Gr_enable_vsync = true; static auto VSyncOption = options::OptionBuilder("Graphis.VSync", "Vertical Sync", "Controls how the engine does vertical synchronization") .category("Graphics") .level(options::ExpertLevel::Advanced) .default_val(true) .bind_to_once(&Gr_enable_vsync) .importance(70) .finish(); static std::unique_ptr UniformBufferManager; // Forward definitions static void uniform_buffer_managers_init(); static void uniform_buffer_managers_deinit(); static void uniform_buffer_managers_retire_buffers(); static void gpu_heap_init(); static void gpu_heap_deinit(); 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, bool force_stretch) { bool do_zoom = zoom_w > 0 && zoom_h > 0 && (zoom_w != w || zoom_h != h); Gr_full_resize_X = (float)max_w / (float)w; Gr_full_resize_Y = (float)max_h / (float)h; Gr_full_center_resize_X = (float)center_w / (float)w; Gr_full_center_resize_Y = (float)center_h / (float)h; if (do_zoom) { float aspect_quotient = ((float)center_w / (float)center_h) / ((float)zoom_w / (float)zoom_h); Gr_resize_X = (float)center_w / (float)zoom_w / ((aspect_quotient > 1.0f) ? aspect_quotient : 1.0f); Gr_resize_Y = (float)center_h / (float)zoom_h * ((aspect_quotient < 1.0f) ? aspect_quotient : 1.0f); Gr_menu_offset_X = ((center_w - w * Gr_resize_X) / 2.0f) + gr_screen.center_offset_x; Gr_menu_offset_Y = ((center_h - h * Gr_resize_Y) / 2.0f) + gr_screen.center_offset_y; Gr_menu_zoomed_offset_X = (Gr_menu_offset_X >= 0.0f) ? Gr_menu_offset_X : gr_screen.center_offset_x; Gr_menu_zoomed_offset_Y = (Gr_menu_offset_Y >= 0.0f) ? Gr_menu_offset_Y : gr_screen.center_offset_y; if (force_stretch || Cmdline_stretch_menu) { if (Gr_menu_offset_X > (float)gr_screen.center_offset_x) { Gr_resize_X = Gr_full_center_resize_X; Gr_menu_offset_X = Gr_menu_zoomed_offset_X = (float)gr_screen.center_offset_x; } if (Gr_menu_offset_Y > (float)gr_screen.center_offset_y) { Gr_resize_Y = Gr_full_center_resize_Y; Gr_menu_offset_Y = Gr_menu_zoomed_offset_Y = (float)gr_screen.center_offset_y; } } } else { if (force_stretch || Cmdline_stretch_menu) { Gr_resize_X = Gr_full_center_resize_X; Gr_resize_Y = Gr_full_center_resize_Y; Gr_menu_offset_X = Gr_menu_zoomed_offset_X = (float)gr_screen.center_offset_x; Gr_menu_offset_Y = Gr_menu_zoomed_offset_Y = (float)gr_screen.center_offset_y; } else { float aspect_quotient = ((float)center_w / (float)center_h) / ((float)w / (float)h); Gr_resize_X = Gr_full_center_resize_X / ((aspect_quotient > 1.0f) ? aspect_quotient : 1.0f); Gr_resize_Y = Gr_full_center_resize_Y * ((aspect_quotient < 1.0f) ? aspect_quotient : 1.0f); 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; 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; } } gr_screen.custom_size = (w != max_w || w != center_w || h != max_h || h != center_h); if (gr_screen.rendering_to_texture == -1) { gr_screen.max_w_unscaled = w; gr_screen.max_h_unscaled = h; if (do_zoom) { gr_screen.max_w_unscaled_zoomed = gr_screen.max_w_unscaled + fl2i(Gr_menu_offset_X * 2.0f / Gr_resize_X); gr_screen.max_h_unscaled_zoomed = gr_screen.max_h_unscaled + fl2i(Gr_menu_offset_Y * 2.0f / Gr_resize_Y); if (gr_screen.max_w_unscaled_zoomed > gr_screen.max_w_unscaled) { gr_screen.max_w_unscaled_zoomed = gr_screen.max_w_unscaled; } if (gr_screen.max_h_unscaled_zoomed > gr_screen.max_h_unscaled) { gr_screen.max_h_unscaled_zoomed = gr_screen.max_h_unscaled; } } else { gr_screen.max_w_unscaled_zoomed = gr_screen.max_w_unscaled; gr_screen.max_h_unscaled_zoomed = gr_screen.max_h_unscaled; } } } void gr_reset_screen_scale() { Gr_full_resize_X = Gr_save_full_resize_X; Gr_full_resize_Y = Gr_save_full_resize_Y; Gr_full_center_resize_X = Gr_save_full_center_resize_X; Gr_full_center_resize_Y = Gr_save_full_center_resize_Y; Gr_resize_X = Gr_save_resize_X; Gr_resize_Y = Gr_save_resize_Y; Gr_menu_offset_X = Gr_save_menu_offset_X; Gr_menu_offset_Y = Gr_save_menu_offset_Y; Gr_menu_zoomed_offset_X = Gr_save_menu_zoomed_offset_X; Gr_menu_zoomed_offset_Y = Gr_save_menu_zoomed_offset_Y; gr_screen.custom_size = Save_custom_screen_size; if (gr_screen.rendering_to_texture == -1) { gr_screen.max_w_unscaled = gr_screen.max_w_unscaled_zoomed = (gr_screen.res == GR_1024) ? 1024 : 640; gr_screen.max_h_unscaled = gr_screen.max_h_unscaled_zoomed = (gr_screen.res == GR_1024) ? 768 : 480; } } /** * This function is to be called if you wish to scale GR_1024 or GR_640 x and y positions or * lengths in order to keep the correctly scaled to nonstandard resolutions * * @param x X value, can be NULL * @param y Y value, can be NULL * @param w width, can be NULL * @param h height, can be NULL * @param resize_mode * @return always true unless error */ bool gr_resize_screen_pos(int *x, int *y, int *w, int *h, int resize_mode) { if ( resize_mode == GR_RESIZE_NONE || (!gr_screen.custom_size && (gr_screen.rendering_to_texture == -1)) ) { return false; } float xy_tmp = 0.0f; if ( x ) { switch (resize_mode) { case GR_RESIZE_FULL: xy_tmp = (*x) * Gr_full_resize_X; break; case GR_RESIZE_FULL_CENTER: xy_tmp = (*x) * Gr_full_center_resize_X + (float)gr_screen.center_offset_x; break; case GR_RESIZE_MENU: xy_tmp = (*x) * Gr_resize_X + Gr_menu_offset_X; break; case GR_RESIZE_MENU_ZOOMED: xy_tmp = (*x) * Gr_resize_X + Gr_menu_zoomed_offset_X; break; case GR_RESIZE_MENU_NO_OFFSET: xy_tmp = (*x) * Gr_resize_X; break; } (*x) = fl2ir(xy_tmp); } if ( y ) { switch (resize_mode) { case GR_RESIZE_FULL: xy_tmp = (*y) * Gr_full_resize_Y; break; case GR_RESIZE_FULL_CENTER: xy_tmp = (*y) * Gr_full_center_resize_Y + (float)gr_screen.center_offset_y; break; case GR_RESIZE_MENU: xy_tmp = (*y) * Gr_resize_Y + Gr_menu_offset_Y; break; case GR_RESIZE_MENU_ZOOMED: xy_tmp = (*y) * Gr_resize_Y + Gr_menu_zoomed_offset_Y; break; case GR_RESIZE_MENU_NO_OFFSET: xy_tmp = (*y) * Gr_resize_Y; break; } (*y) = fl2ir(xy_tmp); } if ( w ) { switch (resize_mode) { case GR_RESIZE_FULL: xy_tmp = (*w) * Gr_full_resize_X; break; case GR_RESIZE_FULL_CENTER: xy_tmp = (*w) * Gr_full_center_resize_X; break; case GR_RESIZE_MENU: case GR_RESIZE_MENU_ZOOMED: case GR_RESIZE_MENU_NO_OFFSET: xy_tmp = (*w) * Gr_resize_X; break; } (*w) = fl2ir(xy_tmp); } if ( h ) { switch (resize_mode) { case GR_RESIZE_FULL: xy_tmp = (*h) * Gr_full_resize_Y; break; case GR_RESIZE_FULL_CENTER: xy_tmp = (*h) * Gr_full_center_resize_Y; break; case GR_RESIZE_MENU: case GR_RESIZE_MENU_ZOOMED: case GR_RESIZE_MENU_NO_OFFSET: xy_tmp = (*h) * Gr_resize_Y; break; } (*h) = fl2ir(xy_tmp); } return true; } /** * * @param x X value, can be NULL * @param y Y value, can be NULL * @param w width, can be NULL * @param h height, can be NULL * @param resize_mode * @return always true unless error */ bool gr_unsize_screen_pos(int *x, int *y, int *w, int *h, int resize_mode) { if ( resize_mode == GR_RESIZE_NONE || (!gr_screen.custom_size && (gr_screen.rendering_to_texture == -1)) ) { return false; } float xy_tmp = 0.0f; if ( x ) { switch (resize_mode) { case GR_RESIZE_FULL: xy_tmp = (*x) / Gr_full_resize_X; break; case GR_RESIZE_FULL_CENTER: xy_tmp = ((*x) - (float)gr_screen.center_offset_x) / Gr_full_center_resize_X; break; case GR_RESIZE_MENU: xy_tmp = ((*x) - Gr_menu_offset_X) / Gr_resize_X; break; case GR_RESIZE_MENU_ZOOMED: xy_tmp = ((*x) - Gr_menu_zoomed_offset_X) / Gr_resize_X; break; case GR_RESIZE_MENU_NO_OFFSET: xy_tmp = (*x) / Gr_resize_X; break; } (*x) = fl2ir(xy_tmp); } if ( y ) { switch (resize_mode) { case GR_RESIZE_FULL: xy_tmp = (*y) / Gr_full_resize_Y; break; case GR_RESIZE_FULL_CENTER: xy_tmp = ((*y) - (float)gr_screen.center_offset_y) / Gr_full_center_resize_Y; break; case GR_RESIZE_MENU: xy_tmp = ((*y) - Gr_menu_offset_Y) / Gr_resize_Y; break; case GR_RESIZE_MENU_ZOOMED: xy_tmp = ((*y) - Gr_menu_zoomed_offset_Y) / Gr_resize_Y; break; case GR_RESIZE_MENU_NO_OFFSET: xy_tmp = (*y) / Gr_resize_Y; break; } (*y) = fl2ir(xy_tmp); } if ( w ) { switch (resize_mode) { case GR_RESIZE_FULL: xy_tmp = (*w) / Gr_full_resize_X; break; case GR_RESIZE_FULL_CENTER: xy_tmp = (*w) / Gr_full_center_resize_X; break; case GR_RESIZE_MENU: case GR_RESIZE_MENU_ZOOMED: case GR_RESIZE_MENU_NO_OFFSET: xy_tmp = (*w) / Gr_resize_X; break; } (*w) = fl2ir(xy_tmp); } if ( h ) { switch (resize_mode) { case GR_RESIZE_FULL: xy_tmp = (*h) / Gr_full_resize_Y; break; case GR_RESIZE_FULL_CENTER: xy_tmp = (*h) / Gr_full_center_resize_Y; break; case GR_RESIZE_MENU: case GR_RESIZE_MENU_ZOOMED: case GR_RESIZE_MENU_NO_OFFSET: xy_tmp = (*h) / Gr_resize_Y; break; } (*h) = fl2ir(xy_tmp); } return true; } /** * This function is to be called if you wish to scale GR_1024 or GR_640 x and y positions or * lengths in order to keep the correctly scaled to nonstandard resolutions * * @param x X value, can be NULL * @param y Y value, can be NULL * @param w width, can be NULL * @param h height, can be NULL * @param resize_mode * @return always true unless error */ bool gr_resize_screen_posf(float *x, float *y, float *w, float *h, int resize_mode) { if ( resize_mode == GR_RESIZE_NONE || (!gr_screen.custom_size && (gr_screen.rendering_to_texture == -1)) ) { return false; } float xy_tmp = 0.0f; if ( x ) { switch (resize_mode) { case GR_RESIZE_FULL: xy_tmp = (*x) * Gr_full_resize_X; break; case GR_RESIZE_FULL_CENTER: xy_tmp = (*x) * Gr_full_center_resize_X + (float)gr_screen.center_offset_x; break; case GR_RESIZE_MENU: xy_tmp = (*x) * Gr_resize_X + Gr_menu_offset_X; break; case GR_RESIZE_MENU_ZOOMED: xy_tmp = (*x) * Gr_resize_X + Gr_menu_zoomed_offset_X; break; case GR_RESIZE_MENU_NO_OFFSET: xy_tmp = (*x) * Gr_resize_X; break; } (*x) = xy_tmp; } if ( y ) { switch (resize_mode) { case GR_RESIZE_FULL: xy_tmp = (*y) * Gr_full_resize_Y; break; case GR_RESIZE_FULL_CENTER: xy_tmp = (*y) * Gr_full_center_resize_Y + (float)gr_screen.center_offset_y; break; case GR_RESIZE_MENU: xy_tmp = (*y) * Gr_resize_Y + Gr_menu_offset_Y; break; case GR_RESIZE_MENU_ZOOMED: xy_tmp = (*y) * Gr_resize_Y + Gr_menu_zoomed_offset_Y; break; case GR_RESIZE_MENU_NO_OFFSET: xy_tmp = (*y) * Gr_resize_Y; break; } (*y) = xy_tmp; } if ( w ) { switch (resize_mode) { case GR_RESIZE_FULL: xy_tmp = (*w) * Gr_full_resize_X; break; case GR_RESIZE_FULL_CENTER: xy_tmp = (*w) * Gr_full_center_resize_X; break; case GR_RESIZE_MENU: case GR_RESIZE_MENU_ZOOMED: case GR_RESIZE_MENU_NO_OFFSET: xy_tmp = (*w) * Gr_resize_X; break; } (*w) = xy_tmp; } if ( h ) { switch (resize_mode) { case GR_RESIZE_FULL: xy_tmp = (*h) * Gr_full_resize_Y; break; case GR_RESIZE_FULL_CENTER: xy_tmp = (*h) * Gr_full_center_resize_Y; break; case GR_RESIZE_MENU: case GR_RESIZE_MENU_ZOOMED: case GR_RESIZE_MENU_NO_OFFSET: xy_tmp = (*h) * Gr_resize_Y; break; } (*h) = xy_tmp; } return true; } /** * * @param x X value, can be NULL * @param y Y value, can be NULL * @param w width, can be NULL * @param h height, can be NULL * @param resize_mode * @return always true unless error */ bool gr_unsize_screen_posf(float *x, float *y, float *w, float *h, int resize_mode) { if ( resize_mode == GR_RESIZE_NONE || (!gr_screen.custom_size && (gr_screen.rendering_to_texture == -1)) ) { return false; } float xy_tmp = 0.0f; if ( x ) { switch (resize_mode) { case GR_RESIZE_FULL: xy_tmp = (*x) / Gr_full_resize_X; break; case GR_RESIZE_FULL_CENTER: xy_tmp = ((*x) - (float)gr_screen.center_offset_x) / Gr_full_center_resize_X; break; case GR_RESIZE_MENU: xy_tmp = ((*x) - Gr_menu_offset_X) / Gr_resize_X; break; case GR_RESIZE_MENU_ZOOMED: xy_tmp = ((*x) - Gr_menu_zoomed_offset_X) / Gr_resize_X; break; case GR_RESIZE_MENU_NO_OFFSET: xy_tmp = (*x) / Gr_resize_X; break; } (*x) = xy_tmp; } if ( y ) { switch (resize_mode) { case GR_RESIZE_FULL: xy_tmp = (*y) / Gr_full_resize_Y; break; case GR_RESIZE_FULL_CENTER: xy_tmp = ((*y) - (float)gr_screen.center_offset_y) / Gr_full_center_resize_Y; break; case GR_RESIZE_MENU: xy_tmp = ((*y) - Gr_menu_offset_Y) / Gr_resize_Y; break; case GR_RESIZE_MENU_ZOOMED: xy_tmp = ((*y) - Gr_menu_zoomed_offset_Y) / Gr_resize_Y; break; case GR_RESIZE_MENU_NO_OFFSET: xy_tmp = (*y) / Gr_resize_Y; break; } (*y) = xy_tmp; } if ( w ) { switch (resize_mode) { case GR_RESIZE_FULL: xy_tmp = (*w) / Gr_full_resize_X; break; case GR_RESIZE_FULL_CENTER: xy_tmp = (*w) / Gr_full_center_resize_X; break; case GR_RESIZE_MENU: case GR_RESIZE_MENU_ZOOMED: case GR_RESIZE_MENU_NO_OFFSET: xy_tmp = (*w) / Gr_resize_X; break; } (*w) = xy_tmp; } if ( h ) { switch (resize_mode) { case GR_RESIZE_FULL: xy_tmp = (*h) / Gr_full_resize_Y; break; case GR_RESIZE_FULL_CENTER: xy_tmp = (*h) / Gr_full_center_resize_Y; break; case GR_RESIZE_MENU: case GR_RESIZE_MENU_ZOOMED: case GR_RESIZE_MENU_NO_OFFSET: xy_tmp = (*h) / Gr_resize_Y; break; } (*h) = xy_tmp; } return true; } void gr_close() { if ( !Gr_inited ) { return; } if (Gr_original_gamma_ramp != nullptr && os::getSDLMainWindow() != nullptr) { SDL_SetWindowGammaRamp(os::getSDLMainWindow(), Gr_original_gamma_ramp, (Gr_original_gamma_ramp + 256), (Gr_original_gamma_ramp + 512)); } // This is valid even if Gr_original_gamma_ramp is nullptr vm_free(Gr_original_gamma_ramp); Gr_original_gamma_ramp = nullptr; gpu_heap_deinit(); // Cleanup uniform buffer managers uniform_buffer_managers_deinit(); font::close(); gr_light_shutdown(); graphics::paths::PathRenderer::shutdown(); switch (gr_screen.mode) { case GR_OPENGL: #ifdef WITH_OPENGL gr_opengl_cleanup(true); #endif break; case GR_VULKAN: #ifdef WITH_VULKAN graphics::vulkan::cleanup(); #endif break; case GR_STUB: break; default: Int3(); // Invalid graphics mode } bm_close(); Gr_inited = 0; } /** * Set screen clear color */ DCF(clear_color, "set clear color r, g, b") { ubyte r, g, b; dc_stuff_ubyte(&r); dc_stuff_ubyte(&g); dc_stuff_ubyte(&b); // set the color gr_set_clear_color(r, g, b); } void gr_set_palette_internal( const char * /*name*/, ubyte * palette, int /*restrict_font_to_128*/ ) { if ( palette == NULL ) { // Create a default palette int r,g,b,i; i = 0; for (r=0; r<6; r++ ) for (g=0; g<6; g++ ) for (b=0; b<6; b++ ) { Gr_current_palette[i*3+0] = (unsigned char)(r*51); Gr_current_palette[i*3+1] = (unsigned char)(g*51); Gr_current_palette[i*3+2] = (unsigned char)(b*51); i++; } for ( i=216;i<256; i++ ) { Gr_current_palette[i*3+0] = (unsigned char)((i-216)*6); Gr_current_palette[i*3+1] = (unsigned char)((i-216)*6); Gr_current_palette[i*3+2] = (unsigned char)((i-216)*6); } memmove( Gr_original_palette, Gr_current_palette, 768 ); } else { memmove( Gr_original_palette, palette, 768 ); memmove( Gr_current_palette, palette, 768 ); } if ( Gr_inited ) { // Since the palette set code might shuffle the palette, // reload it into the source palette if (palette) { memmove(palette, Gr_current_palette, 768); } } } void gr_screen_resize(int width, int height) { 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; 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; gr_screen.offset_x = gr_screen.offset_x_unscaled = 0; gr_screen.offset_y = gr_screen.offset_y_unscaled = 0; gr_screen.clip_left = gr_screen.clip_left_unscaled = 0; gr_screen.clip_top = gr_screen.clip_top_unscaled = 0; gr_screen.clip_right = gr_screen.clip_right_unscaled = gr_screen.max_w - 1; gr_screen.clip_bottom = gr_screen.clip_bottom_unscaled = gr_screen.max_h - 1; gr_screen.clip_width = gr_screen.clip_width_unscaled = gr_screen.max_w; gr_screen.clip_height = gr_screen.clip_height_unscaled = gr_screen.max_h; gr_screen.clip_aspect = i2fl(gr_screen.clip_width) / i2fl(gr_screen.clip_height); if (gr_screen.custom_size) { gr_unsize_screen_pos( &gr_screen.max_w_unscaled, &gr_screen.max_h_unscaled ); gr_unsize_screen_pos( &gr_screen.max_w_unscaled_zoomed, &gr_screen.max_h_unscaled_zoomed ); gr_unsize_screen_pos( &gr_screen.clip_right_unscaled, &gr_screen.clip_bottom_unscaled ); gr_unsize_screen_pos( &gr_screen.clip_width_unscaled, &gr_screen.clip_height_unscaled ); } gr_screen.save_max_w_unscaled = gr_screen.max_w_unscaled; gr_screen.save_max_h_unscaled = gr_screen.max_h_unscaled; gr_screen.save_max_w_unscaled_zoomed = gr_screen.max_w_unscaled_zoomed; gr_screen.save_max_h_unscaled_zoomed = gr_screen.max_h_unscaled_zoomed; gr_setup_viewport(); } int gr_get_resolution_class(int width, int height) { if ((width >= GR_1024_THRESHOLD_WIDTH) && (height >= GR_1024_THRESHOLD_HEIGHT)) { return GR_1024; } else { return GR_640; } } static void init_colors() { int bpp = gr_screen.bits_per_pixel; Assertion((bpp == 16) || (bpp == 32), "Invalid bits-per-pixel value %d!", bpp); // screen format switch (bpp) { case 16: { Gr_red.bits = 5; Gr_red.shift = 11; Gr_red.scale = 8; Gr_red.mask = 0xF800; Gr_green.bits = 6; Gr_green.shift = 5; Gr_green.scale = 4; Gr_green.mask = 0x7E0; Gr_blue.bits = 5; Gr_blue.shift = 0; Gr_blue.scale = 8; Gr_blue.mask = 0x1F; break; } case 32: { Gr_red.bits = 8; Gr_red.shift = 16; Gr_red.scale = 1; Gr_red.mask = 0xff0000; Gr_green.bits = 8; Gr_green.shift = 8; Gr_green.scale = 1; Gr_green.mask = 0x00ff00; Gr_blue.bits = 8; Gr_blue.shift = 0; Gr_blue.scale = 1; Gr_blue.mask = 0x0000ff; Gr_alpha.bits = 8; Gr_alpha.shift = 24; Gr_alpha.mask = 0xff000000; Gr_alpha.scale = 1; break; } } // texture format Gr_t_red.bits = 5; Gr_t_red.mask = 0x7c00; Gr_t_red.shift = 10; Gr_t_red.scale = 8; Gr_t_green.bits = 5; Gr_t_green.mask = 0x03e0; Gr_t_green.shift = 5; Gr_t_green.scale = 8; Gr_t_blue.bits = 5; Gr_t_blue.mask = 0x001f; Gr_t_blue.shift = 0; Gr_t_blue.scale = 8; Gr_t_alpha.bits = 1; Gr_t_alpha.mask = 0x8000; Gr_t_alpha.scale = 255; Gr_t_alpha.shift = 15; // alpha-texture format Gr_ta_red.bits = 4; Gr_ta_red.mask = 0x0f00; Gr_ta_red.shift = 8; Gr_ta_red.scale = 17; Gr_ta_green.bits = 4; Gr_ta_green.mask = 0x00f0; Gr_ta_green.shift = 4; Gr_ta_green.scale = 17; Gr_ta_blue.bits = 4; Gr_ta_blue.mask = 0x000f; Gr_ta_blue.shift = 0; Gr_ta_blue.scale = 17; Gr_ta_alpha.bits = 4; Gr_ta_alpha.mask = 0xf000; Gr_ta_alpha.shift = 12; Gr_ta_alpha.scale = 17; } static bool gr_init_sub(std::unique_ptr&& graphicsOps, int mode, int width, int height, int depth, float center_aspect_ratio) { int res = GR_1024; bool rc = false; gr_screen = {}; float aspect_ratio = (float)width / (float)height; if ( (((width == 640) && (height == 480)) || ((width == 1024) && (height == 768))) && (aspect_ratio == center_aspect_ratio) ) { gr_screen.custom_size = false; } else { gr_screen.custom_size = true; } gr_screen.save_max_w = gr_screen.max_w = gr_screen.max_w_unscaled = gr_screen.max_w_unscaled_zoomed = width; gr_screen.save_max_h = gr_screen.max_h = gr_screen.max_h_unscaled = gr_screen.max_h_unscaled_zoomed = height; if (aspect_ratio > center_aspect_ratio) { gr_screen.save_center_w = gr_screen.center_w = fl2ir(height * center_aspect_ratio); gr_screen.save_center_h = gr_screen.center_h = height; } else if (aspect_ratio < center_aspect_ratio) { gr_screen.save_center_w = gr_screen.center_w = width; gr_screen.save_center_h = gr_screen.center_h = fl2ir(width / center_aspect_ratio); } else { gr_screen.save_center_w = gr_screen.center_w = width; gr_screen.save_center_h = gr_screen.center_h = height; } gr_screen.save_center_offset_x = gr_screen.center_offset_x = (width - gr_screen.center_w) / 2; gr_screen.save_center_offset_y = gr_screen.center_offset_y = (height - gr_screen.center_h) / 2; res = gr_get_resolution_class(gr_screen.center_w, gr_screen.center_h); if (Fred_running) { gr_screen.custom_size = false; res = GR_640; mode = GR_OPENGL; } Save_custom_screen_size = gr_screen.custom_size; Gr_save_full_resize_X = Gr_full_resize_X = (float)width / ((res == GR_1024) ? 1024.0f : 640.0f); Gr_save_full_resize_Y = Gr_full_resize_Y = (float)height / ((res == GR_1024) ? 768.0f : 480.0f); Gr_save_full_center_resize_X = Gr_full_center_resize_X = (float)gr_screen.center_w / ((res == GR_1024) ? 1024.0f : 640.0f); Gr_save_full_center_resize_Y = Gr_full_center_resize_Y = (float)gr_screen.center_h / ((res == GR_1024) ? 768.0f : 480.0f); if (gr_screen.custom_size && !Cmdline_stretch_menu) { float aspect_quotient = center_aspect_ratio / (4.0f / 3.0f); Gr_save_resize_X = Gr_resize_X = Gr_full_center_resize_X / ((aspect_quotient > 1.0f) ? aspect_quotient : 1.0f); Gr_save_resize_Y = Gr_resize_Y = Gr_full_center_resize_Y * ((aspect_quotient < 1.0f) ? aspect_quotient : 1.0f); 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; 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; } else { Gr_save_resize_X = Gr_resize_X = Gr_full_center_resize_X; Gr_save_resize_Y = Gr_resize_Y = Gr_full_center_resize_Y; Gr_save_menu_offset_X = Gr_menu_offset_X = (float)gr_screen.center_offset_x; Gr_save_menu_offset_Y = Gr_menu_offset_Y = (float)gr_screen.center_offset_y; } Gr_save_menu_zoomed_offset_X = Gr_menu_zoomed_offset_X = Gr_menu_offset_X; Gr_save_menu_zoomed_offset_Y = Gr_menu_zoomed_offset_Y = Gr_menu_offset_Y; gr_screen.signature = Gr_signature++; gr_screen.bits_per_pixel = depth; gr_screen.bytes_per_pixel= depth / 8; gr_screen.rendering_to_texture = -1; gr_screen.envmap_render_target = -1; gr_screen.line_width = 1.0f; gr_screen.mode = mode; gr_screen.res = res; gr_screen.aspect = 1.0f; // Normal PC screen gr_screen.offset_x = gr_screen.offset_x_unscaled = 0; gr_screen.offset_y = gr_screen.offset_y_unscaled = 0; gr_screen.clip_left = gr_screen.clip_left_unscaled = 0; gr_screen.clip_top = gr_screen.clip_top_unscaled = 0; gr_screen.clip_right = gr_screen.clip_right_unscaled = gr_screen.max_w - 1; gr_screen.clip_bottom = gr_screen.clip_bottom_unscaled = gr_screen.max_h - 1; gr_screen.clip_width = gr_screen.clip_width_unscaled = gr_screen.max_w; gr_screen.clip_height = gr_screen.clip_height_unscaled = gr_screen.max_h; gr_screen.clip_aspect = i2fl(gr_screen.clip_width) / i2fl(gr_screen.clip_height); gr_screen.clip_center_x = (gr_screen.clip_left + gr_screen.clip_right) * 0.5f; gr_screen.clip_center_y = (gr_screen.clip_top + gr_screen.clip_bottom) * 0.5f; if (gr_screen.custom_size) { gr_unsize_screen_pos(&gr_screen.max_w_unscaled, &gr_screen.max_h_unscaled); gr_unsize_screen_pos(&gr_screen.max_w_unscaled_zoomed, &gr_screen.max_h_unscaled_zoomed); gr_unsize_screen_pos(&gr_screen.clip_right_unscaled, &gr_screen.clip_bottom_unscaled); gr_unsize_screen_pos(&gr_screen.clip_width_unscaled, &gr_screen.clip_height_unscaled); } gr_screen.save_max_w_unscaled = gr_screen.max_w_unscaled; gr_screen.save_max_h_unscaled = gr_screen.max_h_unscaled; gr_screen.save_max_w_unscaled_zoomed = gr_screen.max_w_unscaled_zoomed; gr_screen.save_max_h_unscaled_zoomed = gr_screen.max_h_unscaled_zoomed; init_colors(); switch (mode) { case GR_OPENGL: #ifdef WITH_OPENGL rc = gr_opengl_init(std::move(graphicsOps)); #else Error(LOCATION, "OpenGL renderer was requested but that was not compiled into this build."); rc = false; #endif break; case GR_VULKAN: #ifdef WITH_VULKAN rc = graphics::vulkan::initialize(std::move(graphicsOps)); #else Error(LOCATION, "Vulkan renderer was requested but that was not compiled into this build."); rc = false; #endif break; case GR_STUB: SCP_UNUSED(graphicsOps); rc = gr_stub_init(); break; default: Int3(); // Invalid graphics mode } return rc != 0; } static void init_window_icon() { auto view = os::getMainViewport(); if (view == nullptr) { // Graphics backend has no viewport return; } auto sdl_wnd = view->toSDLWindow(); if (sdl_wnd == nullptr) { // No support for changing the icon return; } auto icon_handle = bm_load(Window_icon_path); if (icon_handle < 0) { Warning(LOCATION, "Failed to load window icon '%s'!", Window_icon_path.c_str()); return; } auto surface = bm_to_sdl_surface(icon_handle); if (surface == nullptr) { Warning(LOCATION, "Convert icon '%s' to a SDL surface!", Window_icon_path.c_str()); bm_release(icon_handle); return; } SDL_SetWindowIcon(sdl_wnd, surface); SDL_FreeSurface(surface); bm_release(icon_handle); } SCP_string gr_capability_to_string(gr_capability capability) { switch (capability) { case CAPABILITY_BPTC: return "BPTC Texture Compression"; default: return "Invalid Capability"; } } bool gr_init(std::unique_ptr&& graphicsOps, int d_mode, int d_width, int d_height, int d_depth) { int width = 1024, height = 768, depth = 32, mode = GR_OPENGL; float center_aspect_ratio = -1.0f; const char *ptr = NULL; // If already inited, shutdown the previous graphics if (Gr_inited) { switch (gr_screen.mode) { case GR_OPENGL: #ifdef WITH_OPENGL gr_opengl_cleanup(false); #endif break; case GR_STUB: break; default: Int3(); // Invalid graphics mode } } if (Using_in_game_options) { auto res = ResolutionOption->getValue(); width = res.width; height = res.height; } else if ( !Is_standalone ) { // We cannot continue without this, quit, but try to help the user out first ptr = os_config_read_string(nullptr, NOX("VideocardFs2open"), nullptr); // if we don't have a config string then construct one, using OpenGL 1024x768 32-bit as the default if (ptr == nullptr) { // If we don't have a display mode, use SDL to get default settings // We need to initialize SDL to do this if (SDL_InitSubSystem(SDL_INIT_VIDEO) == 0) { auto display = static_cast(os_config_read_uint("Video", "Display", 0)); SDL_DisplayMode displayMode; if (SDL_GetDesktopDisplayMode(display, &displayMode) == 0) { width = displayMode.w; height = displayMode.h; int sdlBits = SDL_BITSPERPIXEL(displayMode.format); if (SDL_ISPIXELFORMAT_ALPHA(displayMode.format)) { depth = sdlBits; } else { // Fix a few values if (sdlBits == 24) { depth = 32; } else if (sdlBits == 15) { depth = 16; } else { depth = sdlBits; } } SCP_string videomode; sprintf(videomode, "OGL -(%dx%d)x%d bit", width, height, depth); os_config_write_string(nullptr, NOX("VideocardFs2open"), videomode.c_str()); } } } else { Assert(ptr != nullptr); // NOTE: The "ptr+5" is to skip over the initial "????-" in the video string. // If the format of that string changes you'll have to change this too!!! if (sscanf(ptr + 5, "(%dx%d)x%d ", &width, &height, &depth) != 3) { Error(LOCATION, "Can't understand 'VideocardFs2open' config entry!"); } // Get the first 4 characters of the video string which is the chosen API SCP_string videoApi(ptr, ptr + 4); if (videoApi == "OGL ") { d_mode = GR_OPENGL; // GR_OPENGL; } else if (videoApi == "VK ") { d_mode = GR_VULKAN; } else { ReleaseWarning(LOCATION, "Unknown video API '%s'", videoApi.c_str()); } } if (Cmdline_res != nullptr) { int tmp_width = 0; int tmp_height = 0; if ( sscanf(Cmdline_res, "%dx%d", &tmp_width, &tmp_height) == 2 ) { width = tmp_width; height = tmp_height; } } Gr_enable_soft_particles = Cmdline_softparticles != 0; } if (Cmdline_center_res != NULL) { int tmp_center_width = 0; int tmp_center_height = 0; if ( (sscanf(Cmdline_center_res, "%dx%d", &tmp_center_width, &tmp_center_height) == 2) && (tmp_center_width > 0) && (tmp_center_height > 0) ) { center_aspect_ratio = (float)tmp_center_width / (float)tmp_center_height; } } if (d_mode == GR_DEFAULT) { // OpenGL should be default mode = GR_OPENGL; } else { mode = d_mode; } // see if we passed good values, and use those instead of the config settings if ( (d_width != GR_DEFAULT) && (d_height != GR_DEFAULT) ) { width = d_width; height = d_height; } if (d_depth != GR_DEFAULT) { depth = d_depth; } // if we are in standalone mode then just use special defaults if (Is_standalone) { mode = GR_STUB; width = 640; height = 480; depth = 16; center_aspect_ratio = -1.0f; } if (gr_get_resolution_class(width, height) != GR_640) { // check for hi-res interface files so that we can verify our width/height is correct // if we don't have it then fall back to 640x480 mode instead if ( !cf_exists_full("2_ChoosePilot-m.pcx", CF_TYPE_ANY)) { if ( (width == 1024) && (height == 768) ) { width = 640; height = 480; center_aspect_ratio = -1.0f; } else { width = 800; height = 600; center_aspect_ratio = -1.0f; } } } // These compiler macros will force windowed mode at the specified resolution if // built in debug mode. This helps if you run with the debugger active as the // game won't be switching from fullscreen to minimized every time you hit a breakpoint or // warning message. #ifdef _DEBUG #ifdef _FORCE_DEBUG_WIDESCREEN width = 1280; height = 800; depth = 32; center_aspect_ratio = -1.0f; Cmdline_window = 1; #elif defined(_FORCE_DEBUG_1024) width = 1024; height = 768; depth = 32; center_aspect_ratio = -1.0f; Cmdline_window = 1; #elif defined(_FORCE_DEBUG_640) width = 640; height = 480; depth = 32; center_aspect_ratio = -1.0f; Cmdline_window = 1; #endif #endif if (center_aspect_ratio <= 0.0f) { float aspect_ratio = (float)width / (float)height; if (aspect_ratio > 3.5f) { center_aspect_ratio = aspect_ratio * 0.3f; } else { center_aspect_ratio = aspect_ratio; } } // now try to actually init everything... if ( gr_init_sub(std::move(graphicsOps), mode, width, height, depth, center_aspect_ratio) == false ) { return false; } gr_set_palette_internal(Gr_current_palette_name, NULL, 0); bm_init(); init_window_icon(); io::mouse::CursorManager::init(); mprintf(("Initializing path renderer...\n")); graphics::paths::PathRenderer::init(); gr_light_init(); // Initialize uniform buffer managers uniform_buffer_managers_init(); gpu_heap_init(); mprintf(("Checking graphics capabilities:\n")); mprintf((" Persistent buffer mapping: %s\n", gr_is_capable(CAPABILITY_PERSISTENT_BUFFER_MAPPING) ? "Enabled" : "Disabled")); mprintf(("Checking mod required rendering features...\n")); for (gr_capability ext : Required_render_ext) { if (!gr_is_capable(ext)) { Error(LOCATION, "Feature %s required by mod not supported by system.\n", gr_capability_to_string(ext).c_str()); } } mprintf((" All required features are supported.\n")); bool missing_installation = false; if (!running_unittests && Web_cursor == nullptr) { if (Is_standalone) { // Cursors don't work in standalone mode, just check if the animation exists. auto handle = bm_load_animation("cursorweb"); if (handle < 0) { missing_installation = true; } else { bm_release(handle); } } else { Web_cursor = io::mouse::CursorManager::get()->loadCursor("cursorweb", true); missing_installation = Web_cursor == nullptr; } } if (missing_installation) { Error(LOCATION, "\nWeb cursor bitmap not found. This is most likely due to one of three reasons:\n" " 1) You're running FreeSpace Open from somewhere other than your FreeSpace 2 folder;\n" " 2) You've somehow corrupted your FreeSpace 2 installation, e.g. by modifying or removing the retail VP files;\n" " 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" "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"); } mprintf(("GRAPHICS: Initializing default colors...\n")); gr_set_color(0,0,0); gr_set_clear_color(0, 0, 0); gr_set_shader(NULL); if (!Is_standalone) { if (Using_in_game_options) { // The value should have been loaded into the variable already so we can use that here gr_set_gamma(Gr_gamma); } else { // D3D's gamma system now works differently. 1.0 is the default value ptr = os_config_read_string(nullptr, NOX("GammaD3D"), NOX("1.0")); gr_set_gamma((float)atof(ptr)); } } Gr_inited = 1; return true; } void gr_force_windowed() { if ( !Gr_inited ) { return; } if ( Os_debugger_running ) { os_sleep(1000); } } int gr_activated = 0; void gr_activate(int active) { if (gr_activated == active) { return; } gr_activated = active; if ( !Gr_inited || os::getMainViewport() == nullptr) { return; } if (active) { if (Cmdline_fullscreen_window || Cmdline_window) { os::getMainViewport()->restore(); } else { os::getMainViewport()->setState(os::ViewportState::Fullscreen); } } else { os::getMainViewport()->minimize(); } if (active) { if (!Cmdline_fullscreen_window && !Cmdline_window) { gr_set_gamma(Gr_gamma); } } } // color stuff void gr_get_color( int *r, int *g, int *b ) { if (r) *r = gr_screen.current_color.red; if (g) *g = gr_screen.current_color.green; if (b) *b = gr_screen.current_color.blue; } void gr_init_color(color *c, int r, int g, int b) { CAP(r, 0, 255); CAP(g, 0, 255); CAP(b, 0, 255); c->screen_sig = gr_screen.signature; c->red = (ubyte)r; c->green = (ubyte)g; c->blue = (ubyte)b; c->alpha = 255; c->ac_type = AC_TYPE_NONE; c->alphacolor = -1; c->is_alphacolor = 0; c->magic = 0xAC01; c->raw8 = 0; } void gr_init_alphacolor( color *clr, int r, int g, int b, int alpha, int type ) { CAP(r, 0, 255); CAP(g, 0, 255); CAP(b, 0, 255); CAP(alpha, 0, 255); gr_init_color( clr, r, g, b ); clr->alpha = (ubyte)alpha; clr->ac_type = (ubyte)type; clr->alphacolor = -1; clr->is_alphacolor = 1; } void gr_set_color( int r, int g, int b ) { Assert((r >= 0) && (r < 256)); Assert((g >= 0) && (g < 256)); Assert((b >= 0) && (b < 256)); gr_init_color( &gr_screen.current_color, r, g, b ); } void gr_set_color_fast(color *dst) { if ( dst->screen_sig != gr_screen.signature ) { if (dst->is_alphacolor) { gr_init_alphacolor( dst, dst->red, dst->green, dst->blue, dst->alpha, dst->ac_type ); } else { gr_init_color( dst, dst->red, dst->green, dst->blue ); } } gr_screen.current_color = *dst; } // shader functions void gr_create_shader(shader *shade, ubyte r, ubyte g, ubyte b, ubyte c ) { shade->screen_sig = gr_screen.signature; shade->r = r; shade->g = g; shade->b = b; shade->c = c; } void gr_set_shader(shader *shade) { if (shade) { if (shade->screen_sig != gr_screen.signature) { gr_create_shader( shade, shade->r, shade->g, shade->b, shade->c ); } gr_screen.current_shader = *shade; } else { gr_create_shader( &gr_screen.current_shader, 0, 0, 0, 0 ); } } // new bitmap functions void gr_bitmap(int _x, int _y, int resize_mode) { GR_DEBUG_SCOPE("2D Bitmap"); int _w, _h; float x, y, w, h; vertex verts[4]; if (gr_screen.mode == GR_STUB) { return; } bm_get_info(gr_screen.current_bitmap, &_w, &_h, NULL, NULL, NULL); x = i2fl(_x); y = i2fl(_y); w = i2fl(_w); h = i2fl(_h); auto do_resize = gr_resize_screen_posf(&x, &y, &w, &h, resize_mode); x += ((do_resize) ? gr_screen.offset_x : gr_screen.offset_x_unscaled); y += ((do_resize) ? gr_screen.offset_y : gr_screen.offset_y_unscaled); memset(verts, 0, sizeof(verts)); verts[0].screen.xyw.x = x; verts[0].screen.xyw.y = y; verts[0].texture_position.u = 0.0f; verts[0].texture_position.v = 0.0f; verts[1].screen.xyw.x = x + w; verts[1].screen.xyw.y = y; verts[1].texture_position.u = 1.0f; verts[1].texture_position.v = 0.0f; verts[2].screen.xyw.x = x + w; verts[2].screen.xyw.y = y + h; verts[2].texture_position.u = 1.0f; verts[2].texture_position.v = 1.0f; verts[3].screen.xyw.x = x; verts[3].screen.xyw.y = y + h; verts[3].texture_position.u = 0.0f; verts[3].texture_position.v = 1.0f; // turn off zbuffering //int saved_zbuffer_mode = gr_zbuffer_get(); //gr_zbuffer_set(GR_ZBUFF_NONE); material mat_params; material_set_interface( &mat_params, gr_screen.current_bitmap, gr_screen.current_alphablend_mode == GR_ALPHABLEND_FILTER ? true : false, gr_screen.current_alpha ); g3_render_primitives_textured(&mat_params, verts, 4, PRIM_TYPE_TRIFAN, true); //gr_zbuffer_set(saved_zbuffer_mode); } void gr_bitmap_uv(int _x, int _y, int _w, int _h, float _u0, float _v0, float _u1, float _v1, int resize_mode) { GR_DEBUG_SCOPE("2D Bitmap UV"); float x, y, w, h; vertex verts[4]; x = i2fl(_x); y = i2fl(_y); w = i2fl(_w); h = i2fl(_h); // I will tidy this up later - RT if ( resize_mode != GR_RESIZE_NONE && (gr_screen.custom_size || (gr_screen.rendering_to_texture != -1)) ) { gr_resize_screen_posf(&x, &y, &w, &h, resize_mode); } memset(verts, 0, sizeof(verts)); verts[0].screen.xyw.x = x; verts[0].screen.xyw.y = y; verts[0].texture_position.u = _u0; verts[0].texture_position.v = _v0; verts[1].screen.xyw.x = x + w; verts[1].screen.xyw.y = y; verts[1].texture_position.u = _u1; verts[1].texture_position.v = _v0; verts[2].screen.xyw.x = x + w; verts[2].screen.xyw.y = y + h; verts[2].texture_position.u = _u1; verts[2].texture_position.v = _v1; verts[3].screen.xyw.x = x; verts[3].screen.xyw.y = y + h; verts[3].texture_position.u = _u0; verts[3].texture_position.v = _v1; material material_params; material_set_interface( &material_params, gr_screen.current_bitmap, gr_screen.current_alphablend_mode == GR_ALPHABLEND_FILTER ? true : false, gr_screen.current_alpha ); g3_render_primitives_textured(&material_params, verts, 4, PRIM_TYPE_TRIFAN, true); } /** * Given endpoints, and thickness, calculate coords of the endpoint * Adapted from gr_pline_helper() */ void gr_pline_helper(vec3d *out, vec3d *in1, vec3d *in2, int thickness) { vec3d slope; // slope of the line if ( vm_vec_same(in1, in2) ) { slope = vmd_zero_vector; } else { vm_vec_sub(&slope, in2, in1); float temp = -slope.xyz.x; slope.xyz.x = slope.xyz.y; slope.xyz.y = temp; vm_vec_normalize(&slope); } // get the points vm_vec_scale_add(out, in1, &slope, (float)thickness); } /** * Special function for drawing polylines. * * This function is specifically intended for polylines where each section * is no more than 90 degrees away from a previous section. * Moreover, it is _really_ intended for use with 45 degree angles. * Adapted from gr_pline_special() */ void gr_pline_special(SCP_vector *pts, int thickness, int resize_mode) { vec3d s1, s2, e1, e2, dir; vec3d last_e1, last_e2; vertex v[4]; int started_frame = 0; size_t num_pts = pts->size(); // if we have less than 2 pts, bail if ( num_pts < 2 ) { return; } extern int G3_count; if ( G3_count == 0 ) { g3_start_frame(1); started_frame = 1; } float sw = 0.1f; color clr = gr_screen.current_color; material material_def; material_def.set_depth_mode(ZBUFFER_TYPE_NONE); material_def.set_blend_mode(ALPHA_BLEND_ALPHA_BLEND_ALPHA); material_def.set_cull_mode(false); // draw each section last_e1 = vmd_zero_vector; last_e2 = vmd_zero_vector; int j; for(size_t idx=0; idxat(idx); // start 1 (on the line) e1 = pts->at(idx + 1); // end 1 (on the line) gr_pline_helper(&s2, &s1, &e1, thickness); // start 2 vm_vec_sub(&dir, &e1, &s1); vm_vec_add(&e2, &s2, &dir); // end 2 // stuff coords v[0].screen.xyw.x = (float)ceil(s1.xyz.x); v[0].screen.xyw.y = (float)ceil(s1.xyz.y); v[0].screen.xyw.w = sw; v[0].texture_position.u = 0.5f; v[0].texture_position.v = 0.5f; v[0].flags = PF_PROJECTED; v[0].codes = 0; v[0].r = clr.red; v[0].g = clr.green; v[0].b = clr.blue; v[0].a = clr.alpha; v[1].screen.xyw.x = (float)ceil(s2.xyz.x); v[1].screen.xyw.y = (float)ceil(s2.xyz.y); v[1].screen.xyw.w = sw; v[1].texture_position.u = 0.5f; v[1].texture_position.v = 0.5f; v[1].flags = PF_PROJECTED; v[1].codes = 0; v[1].r = clr.red; v[1].g = clr.green; v[1].b = clr.blue; v[1].a = clr.alpha; v[2].screen.xyw.x = (float)ceil(e2.xyz.x); v[2].screen.xyw.y = (float)ceil(e2.xyz.y); v[2].screen.xyw.w = sw; v[2].texture_position.u = 0.5f; v[2].texture_position.v = 0.5f; v[2].flags = PF_PROJECTED; v[2].codes = 0; v[2].r = clr.red; v[2].g = clr.green; v[2].b = clr.blue; v[2].a = clr.alpha; v[3].screen.xyw.x = (float)ceil(e1.xyz.x); v[3].screen.xyw.y = (float)ceil(e1.xyz.y); v[3].screen.xyw.w = sw; v[3].texture_position.u = 0.5f; v[3].texture_position.v = 0.5f; v[3].flags = PF_PROJECTED; v[3].codes = 0; v[3].r = clr.red; v[3].g = clr.green; v[3].b = clr.blue; v[3].a = clr.alpha; //We could really do this better...but oh well. _WMC if ( resize_mode != GR_RESIZE_NONE ) { for ( j = 0; j<4; j++ ) { gr_resize_screen_posf(&v[j].screen.xyw.x, &v[j].screen.xyw.y, NULL, NULL, resize_mode); } } // draw the polys //g3_draw_poly_constant_sw(4, verts, TMAP_FLAG_GOURAUD | TMAP_FLAG_RGB, 0.1f); g3_render_primitives_colored(&material_def, v, 4, PRIM_TYPE_TRIFAN, true); // if we're past the first section, draw a "patch" triangle to fill any gaps if ( idx > 0 ) { // stuff coords v[0].screen.xyw.x = (float)ceil(s1.xyz.x); v[0].screen.xyw.y = (float)ceil(s1.xyz.y); v[0].screen.xyw.w = sw; v[0].texture_position.u = 0.5f; v[0].texture_position.v = 0.5f; v[0].flags = PF_PROJECTED; v[0].codes = 0; v[0].r = clr.red; v[0].g = clr.green; v[0].b = clr.blue; v[0].a = clr.alpha; v[1].screen.xyw.x = (float)ceil(s2.xyz.x); v[1].screen.xyw.y = (float)ceil(s2.xyz.y); v[1].screen.xyw.w = sw; v[1].texture_position.u = 0.5f; v[1].texture_position.v = 0.5f; v[1].flags = PF_PROJECTED; v[1].codes = 0; v[1].r = clr.red; v[1].g = clr.green; v[1].b = clr.blue; v[1].a = clr.alpha; v[2].screen.xyw.x = (float)ceil(last_e2.xyz.x); v[2].screen.xyw.y = (float)ceil(last_e2.xyz.y); v[2].screen.xyw.w = sw; v[2].texture_position.u = 0.5f; v[2].texture_position.v = 0.5f; v[2].flags = PF_PROJECTED; v[2].codes = 0; v[2].r = clr.red; v[2].g = clr.green; v[2].b = clr.blue; v[2].a = clr.alpha; //Inefficiency or flexibility? you be the judge -WMC if ( resize_mode != GR_RESIZE_NONE ) { for ( j = 0; j<3; j++ ) { gr_resize_screen_posf(&v[j].screen.xyw.x, &v[j].screen.xyw.y, NULL, NULL, resize_mode); } } g3_render_primitives_colored(&material_def, v, 3, PRIM_TYPE_TRIFAN, true); } // store our endpoints last_e1 = e1; last_e2 = e2; } if ( started_frame ) { g3_end_frame(); } } int poly_list::find_first_vertex(int idx) { vec3d *o_norm = &norm[idx]; vertex *o_vert = &vert[idx]; vec3d *p_norm = &norm[0]; vertex *p_vert = &vert[0]; // we should always equal ourselves, so just use that as the stopping point for (int i = 0; i < idx; i++) { if ( (*p_norm == *o_norm) && (p_vert->world == o_vert->world) && (p_vert->texture_position == o_vert->texture_position) ) { return i; } ++p_norm; ++p_vert; } return idx; } int poly_list::find_first_vertex_fast(int idx) { uint* first_idx = std::lower_bound(sorted_indices, sorted_indices + n_verts, idx, finder(this, NULL, NULL)); if ( first_idx == sorted_indices + n_verts ) { // if this happens then idx was never in the index list to begin with which is not good mprintf(("Sorted index list missing index %d!\n", idx)); Int3(); return idx; } return *first_idx; } /** * Given a list (plist) find the index within the indexed list that the vert at position idx within list is at */ int poly_list::find_index(poly_list *plist, int idx) { vec3d *o_norm = &plist->norm[idx]; vertex *o_vert = &plist->vert[idx]; vec3d *p_norm = &norm[0]; vertex *p_vert = &vert[0]; for (int i = 0; i < n_verts; i++) { if ( (*p_norm == *o_norm) && (p_vert->world == o_vert->world) && (p_vert->texture_position == o_vert->texture_position)) { return i; } ++p_vert; ++p_norm; } return -1; } int poly_list::find_index_fast(poly_list *plist, int idx) { // 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 uint* first_idx = std::lower_bound(sorted_indices, sorted_indices + n_verts, n_verts, finder(this, &plist->vert[idx], &plist->norm[idx])); if (first_idx == sorted_indices + n_verts) { return -1; } return *first_idx; } void poly_list::allocate(int _verts) { if (_verts <= currently_allocated) return; if (vert != NULL) { vm_free(vert); vert = NULL; } if (norm != NULL) { vm_free(norm); norm = NULL; } if (tsb != NULL) { vm_free(tsb); tsb = NULL; } if ( submodels != NULL ) { vm_free(submodels); submodels = NULL; } if ( sorted_indices != NULL ) { vm_free(sorted_indices); sorted_indices = NULL; } if (_verts) { vert = (vertex*)vm_malloc(sizeof(vertex) * _verts); norm = (vec3d*)vm_malloc(sizeof(vec3d) * _verts); if (Cmdline_normal) { tsb = (tsb_t*)vm_malloc(sizeof(tsb_t) * _verts); } submodels = (int*)vm_malloc(sizeof(int) * _verts); sorted_indices = (uint*)vm_malloc(sizeof(uint) * _verts); } n_verts = 0; currently_allocated = _verts; } poly_list::~poly_list() { if (vert != NULL) { vm_free(vert); vert = NULL; } if (norm != NULL) { vm_free(norm); norm = NULL; } if (tsb != NULL) { vm_free(tsb); tsb = NULL; } if ( submodels != NULL ) { vm_free(submodels); submodels = NULL; } if (sorted_indices != NULL) { vm_free(sorted_indices); sorted_indices = NULL; } } void poly_list::calculate_tangent() { vertex *v0, *v1, *v2; vec3d *t0, *t1, *t2; vec3d side0, side1; vec3d vt0, vt1; float deltaU0, deltaV0, deltaU1, deltaV1; vec3d tangent, binormal, cross; float magg, scale; if ( !Cmdline_normal ) { return; } Assert( !(n_verts % 3) ); for (int i = 0; i < n_verts; i += 3) { // vertex (reading) v0 = &vert[i]; v1 = &vert[i+1]; v2 = &vert[i+2]; // tangents (writing) t0 = &tsb[i].tangent; t1 = &tsb[i+1].tangent; t2 = &tsb[i+2].tangent; deltaU0 = v1->texture_position.u - v0->texture_position.u; deltaV0 = v1->texture_position.v - v0->texture_position.v; deltaU1 = v2->texture_position.u - v0->texture_position.u; deltaV1 = v2->texture_position.v - v0->texture_position.v; // quick short circuit for NULL case float n = (deltaU0 * deltaV1) - (deltaU1 * deltaV0); if (n == 0.0f) { // hit NULL, so just set identity tangent = vmd_x_vector; binormal = vmd_y_vector; } else { float blah = 1.0f / n; vm_vec_sub(&side0, &v1->world, &v0->world); vm_vec_sub(&side1, &v2->world, &v0->world); // tangent vm_vec_copy_scale(&vt0, &side0, deltaV1); vm_vec_copy_scale(&vt1, &side1, deltaV0); vm_vec_sub(&tangent, &vt0, &vt1); vm_vec_scale(&tangent, blah); // binormal vm_vec_copy_scale(&vt0, &side0, deltaU1); vm_vec_copy_scale(&vt1, &side1, deltaU0); vm_vec_sub(&binormal, &vt0, &vt1); vm_vec_scale(&binormal, blah); } // orthogonalize tangent (for all 3 verts) magg = vm_vec_dot(&norm[i], &tangent); vm_vec_scale_sub(t0, &tangent, &norm[i], magg); vm_vec_normalize_safe(t0); magg = vm_vec_dot(&norm[i+1], &tangent); vm_vec_scale_sub(t1, &tangent, &norm[i+1], magg); vm_vec_normalize_safe(t1); magg = vm_vec_dot(&norm[i+2], &tangent); vm_vec_scale_sub(t2, &tangent, &norm[i+2], magg); vm_vec_normalize_safe(t2); // compute handedness (for all 3 verts) vm_vec_cross(&cross, &norm[i], &tangent); scale = vm_vec_dot(&cross, &binormal); tsb[i].scaler = (scale < 0.0f) ? -1.0f : 1.0f; vm_vec_cross(&cross, &norm[i+1], &tangent); scale = vm_vec_dot(&cross, &binormal); tsb[i+1].scaler = (scale < 0.0f) ? -1.0f : 1.0f; vm_vec_cross(&cross, &norm[i+2], &tangent); scale = vm_vec_dot(&cross, &binormal); tsb[i+2].scaler = (scale < 0.0f) ? -1.0f : 1.0f; } } static poly_list buffer_list_internal; void poly_list::make_index_buffer(SCP_vector &vertex_list) { int nverts = 0; int j, z = 0; ubyte *nverts_good = NULL; // calculate tangent space data (must be done early) calculate_tangent(); // using vm_malloc() here rather than 'new' so we get the extra out-of-memory check nverts_good = (ubyte *) vm_malloc(n_verts); Assert( nverts_good != NULL ); if ( nverts_good == NULL ) return; memset( nverts_good, 0, n_verts ); vertex_list.reserve(n_verts); generate_sorted_index_list(); for (j = 0; j < n_verts; j++) { if (find_first_vertex_fast(j) == j) { nverts++; nverts_good[j] = 1; vertex_list.push_back(j); } } // if there is nothig to change then bail if (n_verts == nverts) { if (nverts_good != NULL) { vm_free(nverts_good); } return; } buffer_list_internal.n_verts = 0; buffer_list_internal.allocate(nverts); for (j = 0; j < n_verts; j++) { if ( !nverts_good[j] ) { continue; } buffer_list_internal.vert[z] = vert[j]; buffer_list_internal.norm[z] = norm[j]; if (Cmdline_normal) { buffer_list_internal.tsb[z] = tsb[j]; } buffer_list_internal.submodels[z] = submodels[j]; buffer_list_internal.n_verts++; z++; } Assert(nverts == buffer_list_internal.n_verts); if (nverts_good != NULL) { vm_free(nverts_good); } buffer_list_internal.generate_sorted_index_list(); (*this) = buffer_list_internal; } poly_list& poly_list::operator = (const poly_list &other_list) { allocate(other_list.n_verts); memcpy(norm, other_list.norm, sizeof(vec3d) * other_list.n_verts); memcpy(vert, other_list.vert, sizeof(vertex) * other_list.n_verts); if (Cmdline_normal) { memcpy(tsb, other_list.tsb, sizeof(tsb_t) * other_list.n_verts); } memcpy(submodels, other_list.submodels, sizeof(int) * other_list.n_verts); memcpy(sorted_indices, other_list.sorted_indices, sizeof(uint) * other_list.n_verts); n_verts = other_list.n_verts; return *this; } void poly_list::generate_sorted_index_list() { for ( int j = 0; j < n_verts; ++j) { sorted_indices[j] = j; } std::sort(sorted_indices, sorted_indices + n_verts, finder(this)); } bool poly_list::finder::operator()(const uint a, const uint b) { vertex *vert_a; vertex *vert_b; vec3d *norm_a; vec3d *norm_b; Assert(search_list != NULL); if ( a == (uint)search_list->n_verts ) { Assert(vert_to_find != NULL); Assert(norm_to_find != NULL); Assert(a != b); vert_a = vert_to_find; norm_a = norm_to_find; } else { vert_a = &search_list->vert[a]; norm_a = &search_list->norm[a]; } if ( b == (uint)search_list->n_verts ) { Assert(vert_to_find != NULL); Assert(norm_to_find != NULL); Assert(a != b); vert_b = vert_to_find; norm_b = norm_to_find; } else { vert_b = &search_list->vert[b]; norm_b = &search_list->norm[b]; } if (norm_a->xyz.x != norm_b->xyz.x) { return norm_a->xyz.x < norm_b->xyz.x; } if (norm_a->xyz.y != norm_b->xyz.y) { return norm_a->xyz.y < norm_b->xyz.y; } if (norm_a->xyz.z != norm_b->xyz.z) { return norm_a->xyz.z < norm_b->xyz.z; } if (vert_a->world.xyz.x != vert_b->world.xyz.x) { return vert_a->world.xyz.x < vert_b->world.xyz.x; } if (vert_a->world.xyz.y != vert_b->world.xyz.y) { return vert_a->world.xyz.y < vert_b->world.xyz.y; } if (vert_a->world.xyz.z != vert_b->world.xyz.z) { return vert_a->world.xyz.z < vert_b->world.xyz.z; } if (vert_a->texture_position.u != vert_b->texture_position.u) { return vert_a->texture_position.u < vert_b->texture_position.u; } if ( vert_a->texture_position.v != vert_b->texture_position.v ) { return vert_a->texture_position.v < vert_b->texture_position.v; } if ( !compare_indices ) { return vert_a->texture_position.v < vert_b->texture_position.v; } else { return a < b; } } void gr_set_bitmap(int bitmap_num, int alphablend_mode, int bitblt_mode, float alpha) { gr_screen.current_alpha = alpha; gr_screen.current_alphablend_mode = alphablend_mode; gr_screen.current_bitblt_mode = bitblt_mode; gr_screen.current_bitmap = bitmap_num; } static void output_uniform_debug_data() { if (gr_screen.mode == GR_STUB) { return; } int line_height = gr_get_font_height() + 1; gr_set_color_fast(&Color_bright_white); gr_printf_no_resize(gr_screen.center_offset_x + 20, gr_screen.center_offset_y + 160, "Uniform buffer size: " SIZE_T_ARG, UniformBufferManager->getBufferSize()); gr_printf_no_resize(gr_screen.center_offset_x + 20, gr_screen.center_offset_y + 160 + line_height, "Currently used data: " SIZE_T_ARG, UniformBufferManager->getCurrentlyUsedSize()); } void gr_flip(bool execute_scripting) { // Execute external "On Frame" work items executor::OnFrameExecutor->process(); // m!m avoid running CHA_ONFRAME when the "Quit mission" popup is shown. See mantis 2446 for reference // Cyborg - A similar bug will occur when a mission is restarted so check for that, too. if (execute_scripting && !popup_active() && !((gameseq_get_state() == GS_STATE_GAME_PLAY) && !(Game_mode & GM_IN_MISSION))) { TRACE_SCOPE(tracing::LuaOnFrame); // WMC - Do conditional hooks. Yippee! if (OnFrameHook->isActive()) { OnFrameHook->run(); } // WMC - Do scripting reset stuff Script_system.EndFrame(); } gr_reset_immediate_buffer(); // Do per frame operations on the matrix state gr_matrix_on_frame(); gr_reset_clip(); mouse_reset_deltas(); if (Cmdline_graphics_debug_output) { output_uniform_debug_data(); } // Use this opportunity for retiring the uniform buffers uniform_buffer_managers_retire_buffers(); TRACE_SCOPE(tracing::PageFlip); gr_screen.gf_flip(); } void gr_print_timestamp(int x, int y, fix timestamp, int resize_mode) { int seconds = fl2i(f2fl(timestamp)); // format the time information into strings SCP_string time; sprintf(time, "%.1d:%.2d:%.2d", (seconds / 3600) % 10, (seconds / 60) % 60, seconds % 60); gr_string(x, y, time.c_str(), resize_mode); } static void uniform_buffer_managers_init() { if (gr_screen.mode == GR_STUB) { return; } UniformBufferManager.reset(new graphics::util::UniformBufferManager()); } static void uniform_buffer_managers_deinit() { if (gr_screen.mode == GR_STUB) { return; } UniformBufferManager.reset(); } static void uniform_buffer_managers_retire_buffers() { if (gr_screen.mode == GR_STUB) { return; } UniformBufferManager->onFrameEnd(); } graphics::util::UniformBuffer gr_get_uniform_buffer(uniform_block_type type, size_t num_elements, size_t element_size_override) { return UniformBufferManager->getUniformBuffer(type, num_elements, element_size_override); } SCP_vector gr_enumerate_displays() { // It seems that linux cannot handle having the video subsystem inited // too late if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) { return SCP_vector(); } SCP_vector data; auto num_displays = SDL_GetNumVideoDisplays(); for (auto i = 0; i < num_displays; ++i) { DisplayData display; display.index = i; SDL_Rect bounds; if (SDL_GetDisplayBounds(i, &bounds) == 0) { display.x = bounds.x; display.y = bounds.y; display.width = bounds.w; display.height = bounds.h; } auto name = SDL_GetDisplayName(i); if (name != nullptr) { display.name = name; } auto num_mods = SDL_GetNumDisplayModes(i); for (auto j = 0; j < num_mods; ++j) { SDL_DisplayMode mode; if (SDL_GetDisplayMode(i, j, &mode) != 0) { continue; } VideoModeData videoMode; videoMode.width = mode.w; videoMode.height = mode.h; int sdlBits = SDL_BITSPERPIXEL(mode.format); if (SDL_ISPIXELFORMAT_ALPHA(mode.format)) { videoMode.bit_depth = sdlBits; } else { // Fix a few values if (sdlBits == 24) { videoMode.bit_depth = 32; } else if (sdlBits == 15) { videoMode.bit_depth = 16; } else { videoMode.bit_depth = sdlBits; } } display.video_modes.push_back(videoMode); } data.push_back(display); } SDL_QuitSubSystem(SDL_INIT_VIDEO); return data; } namespace std { size_t hash::operator()(const vertex_format_data& data) const { size_t seed = 0; boost::hash_combine(seed, (size_t)data.format_type); boost::hash_combine(seed, data.offset); boost::hash_combine(seed, data.stride); return seed; } size_t hash::operator()(const vertex_layout& data) const { return data.hash(); } } bool vertex_layout::resident_vertex_format(vertex_format_data::vertex_format format_type) const { return ( Vertex_mask & vertex_format_data::mask(format_type) ) ? true : false; } void vertex_layout::add_vertex_component(vertex_format_data::vertex_format format_type, size_t stride, size_t offset) { // A stride value of 0 is not handled consistently by the graphics API so we must enforce that that does not happen Assertion(stride != 0, "The stride of a vertex component may not be zero!"); if ( resident_vertex_format(format_type) ) { // we already have a vertex component of this format type return; } if (Vertex_mask == 0) { // This is the first element so we need to initialize the global stride here Vertex_stride = stride; } Assertion(Vertex_stride == stride, "The strides of all elements must be the same in a vertex layout!"); Vertex_mask |= (1 << format_type); Vertex_components.push_back(vertex_format_data(format_type, stride, offset)); } bool vertex_layout::operator==(const vertex_layout& other) const { if (Vertex_mask != other.Vertex_mask) { return false; } if (Vertex_components.size() != other.Vertex_components.size()) { return false; } return std::equal(Vertex_components.cbegin(), Vertex_components.cend(), other.Vertex_components.cbegin()); } size_t vertex_layout::hash() const { size_t seed = 0; boost::hash_combine(seed, Vertex_mask); for (auto& comp : Vertex_components) { boost::hash_combine(seed, comp); } return seed; } static std::unique_ptr gpu_heaps [static_cast(GpuHeap::NUM_VALUES)]; static void gpu_heap_init() { if (gr_screen.mode == GR_STUB) { return; } for (size_t i = 0; i < static_cast(GpuHeap::NUM_VALUES); ++i) { auto enumVal = static_cast(i); gpu_heaps[i].reset(new graphics::util::GPUMemoryHeap(enumVal)); } } static void gpu_heap_deinit() { for (auto& heap : gpu_heaps) { heap.reset(); } } static graphics::util::GPUMemoryHeap* get_gpu_heap(GpuHeap heap_type) { Assertion(heap_type != GpuHeap::NUM_VALUES, "Invalid heap type value detected."); return gpu_heaps[static_cast(heap_type)].get(); } void gr_heap_allocate(GpuHeap heap_type, size_t size, void* data, size_t& offset_out, gr_buffer_handle& handle_out) { TRACE_SCOPE(tracing::GpuHeapAllocate); auto gpuHeap = get_gpu_heap(heap_type); offset_out = gpuHeap->allocateGpuData(size, data); handle_out = gpuHeap->bufferHandle(); } void gr_heap_deallocate(GpuHeap heap_type, size_t data_offset) { TRACE_SCOPE(tracing::GpuHeapDeallocate); auto gpuHeap = get_gpu_heap(heap_type); gpuHeap->freeGpuData(data_offset); } // I feel dirty... static void make_gamma_ramp(float gamma, ushort* ramp) { ushort x, y; ushort base_ramp[256]; Assert(ramp != nullptr); // generate the base ramp values first off // if no gamma set then just do this quickly if (gamma <= 0.0f) { memset(ramp, 0, 3 * 256 * sizeof(ushort)); return; } // identity gamma, avoid all of the math else if (gamma == 1.0f || Gr_original_gamma_ramp == nullptr) { if (Gr_original_gamma_ramp != nullptr) { memcpy(ramp, Gr_original_gamma_ramp, 3 * 256 * sizeof(ushort)); } // set identity if no original ramp else { for (x = 0; x < 256; x++) { ramp[x] = (x << 8) | x; ramp[x + 256] = (x << 8) | x; ramp[x + 512] = (x << 8) | x; } } return; } // for everything else we need to actually figure it up else { double g = 1.0 / (double)gamma; double val; Assert(Gr_original_gamma_ramp != nullptr); for (x = 0; x < 256; x++) { val = (pow(x / 255.0, g) * 65535.0 + 0.5); CLAMP(val, 0, 65535); base_ramp[x] = (ushort)val; } for (y = 0; y < 3; y++) { for (x = 0; x < 256; x++) { val = (base_ramp[x] * 2) - Gr_original_gamma_ramp[x + y * 256]; CLAMP(val, 0, 65535); ramp[x + y * 256] = (ushort)val; } } } } void gr_set_gamma(float gamma) { if (gr_screen.mode == GR_STUB) { return; } Gr_gamma = gamma; // new way - but not while running FRED if (!Fred_running && !Cmdline_no_set_gamma && os::getSDLMainWindow() != nullptr) { if (Gr_original_gamma_ramp == nullptr) { // First time we are here so get the current (original) gamma ramp here so we can reset it later Gr_original_gamma_ramp = (ushort*)vm_malloc(3 * 256 * sizeof(ushort), memory::quiet_alloc); if (Gr_original_gamma_ramp == nullptr) { mprintf((" Unable to allocate memory for gamma ramp! Disabling...\n")); Cmdline_no_set_gamma = 1; } else { SDL_GetWindowGammaRamp(os::getSDLMainWindow(), Gr_original_gamma_ramp, (Gr_original_gamma_ramp + 256), (Gr_original_gamma_ramp + 512)); } } auto gamma_ramp = (ushort*)vm_malloc(3 * 256 * sizeof(ushort), memory::quiet_alloc); if (gamma_ramp == nullptr) { Int3(); return; } memset(gamma_ramp, 0, 3 * 256 * sizeof(ushort)); // Create the Gamma lookup table make_gamma_ramp(gamma, gamma_ramp); SDL_SetWindowGammaRamp(os::getSDLMainWindow(), gamma_ramp, (gamma_ramp + 256), (gamma_ramp + 512)); vm_free(gamma_ramp); } } void gr_get_post_process_effect_names(SCP_vector& names) { if (graphics::Post_processing_manager == nullptr) { names.clear(); return; } const auto& effects = graphics::Post_processing_manager->getPostEffects(); for (const auto& eff : effects) { names.push_back(eff.name); } }