diff --git a/lib/ivis_opengl/gfx_api.h b/lib/ivis_opengl/gfx_api.h index 8fd7f868545..8f3f2bd1b0b 100644 --- a/lib/ivis_opengl/gfx_api.h +++ b/lib/ivis_opengl/gfx_api.h @@ -381,6 +381,7 @@ namespace gfx_api virtual void set_polygon_offset(const float& offset, const float& slope) = 0; virtual void set_depth_range(const float& min, const float& max) = 0; virtual int32_t get_context_value(const context_value property) = 0; + virtual uint64_t get_estimated_vram_mb() = 0; static context& get(); static bool initialize(const gfx_api::backend_Impl_Factory& impl, int32_t antialiasing, swap_interval_mode mode, optional mipLodBias, uint32_t depthMapResolution, gfx_api::backend_type backend); static bool isInitialized(); diff --git a/lib/ivis_opengl/gfx_api_gl.cpp b/lib/ivis_opengl/gfx_api_gl.cpp index c44e66986b3..ea0ea78b17b 100644 --- a/lib/ivis_opengl/gfx_api_gl.cpp +++ b/lib/ivis_opengl/gfx_api_gl.cpp @@ -2699,6 +2699,34 @@ int32_t gl_context::get_context_value(const context_value property) return value; } +uint64_t gl_context::get_estimated_vram_mb() +{ + if (GLAD_GL_NVX_gpu_memory_info) + { + // If GL_NVX_gpu_memory_info is available, get the total graphics memory + GLint total_graphics_mem_kb = 0; + glGetIntegerv(GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX, &total_graphics_mem_kb); + + if (total_graphics_mem_kb > 0) + { + return static_cast(total_graphics_mem_kb / 1024); + } + } + else if (GLAD_GL_ATI_meminfo) + { + // For GL_ATI_meminfo, get the current free texture memory (stats_kb[0]) + GLint stats_kb[4] = {0, 0, 0, 0}; + glGetIntegerv(GL_TEXTURE_FREE_MEMORY_ATI, stats_kb); + if (stats_kb[0] > 0) + { + uint64_t currentFreeTextureMemory_mb = static_cast(stats_kb[0] / 1024); + return currentFreeTextureMemory_mb; + } + } + + return 0; +} + // MARK: gl_context - debug void gl_context::debugStringMarker(const char *str) diff --git a/lib/ivis_opengl/gfx_api_gl.h b/lib/ivis_opengl/gfx_api_gl.h index fd52b9bd2d4..6e7cbb1b1bc 100644 --- a/lib/ivis_opengl/gfx_api_gl.h +++ b/lib/ivis_opengl/gfx_api_gl.h @@ -295,6 +295,7 @@ struct gl_context final : public gfx_api::context virtual void set_polygon_offset(const float& offset, const float& slope) override; virtual void set_depth_range(const float& min, const float& max) override; virtual int32_t get_context_value(const context_value property) override; + virtual uint64_t get_estimated_vram_mb() override; virtual size_t numDepthPasses() override; virtual bool setDepthPassProperties(size_t numDepthPasses, size_t depthBufferResolution) override; diff --git a/lib/ivis_opengl/gfx_api_null.cpp b/lib/ivis_opengl/gfx_api_null.cpp index 54652c22cb7..5bdde547be3 100644 --- a/lib/ivis_opengl/gfx_api_null.cpp +++ b/lib/ivis_opengl/gfx_api_null.cpp @@ -326,6 +326,11 @@ int32_t null_context::get_context_value(const context_value property) return 0; } +uint64_t null_context::get_estimated_vram_mb() +{ + return 0; +} + // MARK: null_context - debug void null_context::debugStringMarker(const char *str) @@ -376,7 +381,7 @@ uint64_t null_context::debugGetPerfValue(PERF_POINT pp) std::map null_context::getBackendGameInfo() { std::map backendGameInfo; - backendGameInfo["null_gfx_backend"] = true; + backendGameInfo["null_gfx_backend"] = "true"; return backendGameInfo; } diff --git a/lib/ivis_opengl/gfx_api_null.h b/lib/ivis_opengl/gfx_api_null.h index 5e31a703ae6..9101ad5d7f2 100644 --- a/lib/ivis_opengl/gfx_api_null.h +++ b/lib/ivis_opengl/gfx_api_null.h @@ -123,6 +123,7 @@ struct null_context final : public gfx_api::context virtual void set_polygon_offset(const float& offset, const float& slope) override; virtual void set_depth_range(const float& min, const float& max) override; virtual int32_t get_context_value(const context_value property) override; + virtual uint64_t get_estimated_vram_mb() override; virtual void beginRenderPass() override; virtual void endRenderPass() override; diff --git a/lib/ivis_opengl/gfx_api_vk.cpp b/lib/ivis_opengl/gfx_api_vk.cpp index 51d29ccaa52..e26995cb422 100644 --- a/lib/ivis_opengl/gfx_api_vk.cpp +++ b/lib/ivis_opengl/gfx_api_vk.cpp @@ -4318,7 +4318,7 @@ bool VkRoot::createSwapchain() return true; } -static uint32_t getVKSuggestedDefaultDepthBufferResolution(const vk::PhysicalDeviceMemoryProperties& memprops) +static optional getVKLargestDeviceLocalMemoryHeapIndex(const vk::PhysicalDeviceMemoryProperties& memprops) { optional largestDeviceLocalMemoryHeap; for (uint32_t i = 0; i < memprops.memoryTypeCount; ++i) @@ -4344,7 +4344,12 @@ static uint32_t getVKSuggestedDefaultDepthBufferResolution(const vk::PhysicalDev } } } + return largestDeviceLocalMemoryHeap; +} +static uint32_t getVKSuggestedDefaultDepthBufferResolution(const vk::PhysicalDeviceMemoryProperties& memprops) +{ + optional largestDeviceLocalMemoryHeap = getVKLargestDeviceLocalMemoryHeapIndex(memprops); ASSERT_OR_RETURN(2048, largestDeviceLocalMemoryHeap.has_value(), "Couldn't find the largest device local memory heap?"); auto largestDeviceLocalMemoryHeapSize = memprops.memoryHeaps[largestDeviceLocalMemoryHeap.value()].size; @@ -5927,6 +5932,14 @@ int32_t VkRoot::get_context_value(const gfx_api::context::context_value property return 0; } +uint64_t VkRoot::get_estimated_vram_mb() +{ + optional largestDeviceLocalMemoryHeap = getVKLargestDeviceLocalMemoryHeapIndex(memprops); + ASSERT_OR_RETURN(0, largestDeviceLocalMemoryHeap.has_value(), "Couldn't find the largest device local memory heap?"); + auto largestDeviceLocalMemoryHeapSize = memprops.memoryHeaps[largestDeviceLocalMemoryHeap.value()].size; + return static_cast(largestDeviceLocalMemoryHeapSize / 1048576); +} + // DEBUG-handling void VkRoot::debugStringMarker(const char *str) diff --git a/lib/ivis_opengl/gfx_api_vk.h b/lib/ivis_opengl/gfx_api_vk.h index e740a241a34..07360ca71d5 100644 --- a/lib/ivis_opengl/gfx_api_vk.h +++ b/lib/ivis_opengl/gfx_api_vk.h @@ -839,6 +839,7 @@ struct VkRoot final : gfx_api::context public: virtual int32_t get_context_value(const gfx_api::context::context_value property) override; + virtual uint64_t get_estimated_vram_mb() override; virtual void debugStringMarker(const char *str) override; virtual void debugSceneBegin(const char *descr) override; virtual void debugSceneEnd(const char *descr) override; diff --git a/src/configuration.cpp b/src/configuration.cpp index 8e2f68e01c9..5653516880f 100644 --- a/src/configuration.cpp +++ b/src/configuration.cpp @@ -600,7 +600,7 @@ bool loadConfig() } else { - debug(LOG_WARNING, "Unsupported / invalid terrainShaderQuality value: %d; defaulting to: %d", intValue, static_cast(getTerrainShaderQuality())); + debug(LOG_WARNING, "Unsupported / invalid terrainShaderQuality value: %d; using default", intValue); } } war_setShadowFilterSize(iniGetInteger("shadowFilterSize", (int)war_getShadowFilterSize()).value()); diff --git a/src/init.cpp b/src/init.cpp index d23aabb3271..49020cf6a30 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -473,6 +473,8 @@ optional getTerrainOverrideBaseSourcePath(TerrainShaderQuality qual return nullopt; case TerrainShaderQuality::NORMAL_MAPPING: return "terrain_overrides/high"; + case TerrainShaderQuality::UNINITIALIZED_PICK_DEFAULT: + return nullopt; } return nullopt; // silence compiler warning } diff --git a/src/terrain.cpp b/src/terrain.cpp index 5c0586cafb0..4f425d08d77 100644 --- a/src/terrain.cpp +++ b/src/terrain.cpp @@ -35,6 +35,7 @@ #include "lib/framework/frameresource.h" #include "lib/framework/opengl.h" #include "lib/framework/physfs_ext.h" +#include "lib/framework/wzapp.h" #include "lib/ivis_opengl/ivisdef.h" #include "lib/ivis_opengl/imd.h" #include "lib/ivis_opengl/piefunc.h" @@ -59,6 +60,8 @@ #include "loop.h" #include "wzcrashhandlingproviders.h" +#include + // TODO: Fix and remove after merging terrain rendering changes #if defined(__clang__) #pragma clang diagnostic ignored "-Wfloat-conversion" @@ -161,7 +164,7 @@ GLsizei dreCount; /// Are we actually drawing something using the DrawRangeElements functions? bool drawRangeElementsStarted = false; -TerrainShaderQuality terrainShaderQuality = TerrainShaderQuality::NORMAL_MAPPING; +TerrainShaderQuality terrainShaderQuality = TerrainShaderQuality::UNINITIALIZED_PICK_DEFAULT; TerrainShaderType terrainShaderType = TerrainShaderType::SINGLE_PASS; bool initializedTerrainShaderType = false; @@ -1934,6 +1937,9 @@ static void drawTerrainCombined(const glm::mat4 &ModelViewProjection, const glm: case TerrainShaderQuality::NORMAL_MAPPING: drawTerrainCombinedmpl(ModelViewProjection, ViewMatrix, ModelUVLightmap, cameraPos, sunPos, shadowCascades); break; + case TerrainShaderQuality::UNINITIALIZED_PICK_DEFAULT: + // should not happen + break; } } @@ -2209,6 +2215,9 @@ void drawWater(const glm::mat4 &ModelViewProjection, const Vector3f &cameraPos, case TerrainShaderQuality::NORMAL_MAPPING: drawWaterHighImpl(ModelViewProjection, cameraPos, sunPos); return; + case TerrainShaderQuality::UNINITIALIZED_PICK_DEFAULT: + // should not happen + break; } } @@ -2362,10 +2371,79 @@ bool isSupportedTerrainShaderQualityOption(TerrainShaderQuality value) return false; // silence compiler warning } +static TerrainShaderQuality determineDefaultTerrainQuality() +{ + // Based on system properties, determine a reasonable default (for performance reasons) + // (Uses a heuristic based on system RAM, graphics renderer, and estimated VRAM) + + // If < 7.5 GiB system RAM, default to medium ("normal") + auto systemRAMinMiB = wzGetCurrentSystemRAM(); + if (systemRAMinMiB < 7680) + { + debug(LOG_INFO, "Due to system RAM (%" PRIu64 " MiB), defaulting to terrain quality: Normal", systemRAMinMiB); + return TerrainShaderQuality::MEDIUM; + } + + // For specific older integrated cards on OpenGL, default to medium ("normal") + auto backendInfo = gfx_api::context::get().getBackendGameInfo(); + auto it = backendInfo.find("openGL_renderer"); + if (it != backendInfo.end()) + { + // If opengl_renderer starts with "Intel(R) HD Graphics" + if (it->second.rfind("Intel(R) HD Graphics", 0) == 0) + { + // default to medium ("normal") + return TerrainShaderQuality::MEDIUM; + } + // If opengl_renderer starts with "Intel(R) Graphics Media Accelerator" + if (it->second.rfind("Intel(R) Graphics Media Accelerator", 0) == 0) + { + // default to medium ("normal") + return TerrainShaderQuality::MEDIUM; + } + } + + // If it's the null backend (headless mode) + it = backendInfo.find("null_gfx_backend"); + if (it != backendInfo.end()) + { + // default to medium ("normal") + debug(LOG_INFO, "Due to null gfx backend, defaulting to terrain quality: Normal"); + return TerrainShaderQuality::MEDIUM; + } + + // Try to get the estimated available VRAM + auto estimatedVRAMinMiB = gfx_api::context::get().get_estimated_vram_mb(); + if (estimatedVRAMinMiB > 0) + { + // If estimated VRAM < 2 GiB + if (estimatedVRAMinMiB < 2048) + { + // default to medium ("normal") + debug(LOG_INFO, "Due to estimated VRAM (%" PRIu64 " MiB), defaulting to terrain quality: Normal", estimatedVRAMinMiB); + return TerrainShaderQuality::MEDIUM; + } + } + +#if INTPTR_MAX <= INT32_MAX + // On 32-bit builds, default to MEDIUM + debug(LOG_INFO, "32-bit build defaulting to terrain quality: Normal"); + return TerrainShaderQuality::MEDIUM; +#else + // If all of the above check out, default to NORMAL_MAPPING + return TerrainShaderQuality::NORMAL_MAPPING; +#endif +} + void initTerrainShaderType() { terrainShaderType = determineSupportedTerrainShader(); initializedTerrainShaderType = true; + if (terrainShaderQuality == TerrainShaderQuality::UNINITIALIZED_PICK_DEFAULT) + { + terrainShaderQuality = determineDefaultTerrainQuality(); + debug(LOG_INFO, "Defaulting terrain quality to: %s", to_display_string(terrainShaderQuality).c_str()); + } setTerrainShaderQuality(terrainShaderQuality, true); // checks and resets unsupported values } @@ -2402,6 +2480,8 @@ std::string to_display_string(TerrainShaderQuality value) { switch (value) { + case TerrainShaderQuality::UNINITIALIZED_PICK_DEFAULT: + return ""; case TerrainShaderQuality::CLASSIC: return _("Classic"); case TerrainShaderQuality::MEDIUM: diff --git a/src/terrain_defs.h b/src/terrain_defs.h index b9804489ee9..5f8233e890a 100644 --- a/src/terrain_defs.h +++ b/src/terrain_defs.h @@ -21,6 +21,7 @@ enum TerrainShaderQuality { + UNINITIALIZED_PICK_DEFAULT = -1, // a non-selectable option for first-run - pick a default based on the current system settings CLASSIC = 0, // classic, pixel-art, tile-based textures MEDIUM = 1, // the mode used by at least WZ 3.2.x - 4.3.x NORMAL_MAPPING = 2 // the highest-quality mode, which adds normal / specular / height maps and advanced lighting diff --git a/src/wzscriptdebug.cpp b/src/wzscriptdebug.cpp index 4bca069dbaf..4feb6a042b3 100644 --- a/src/wzscriptdebug.cpp +++ b/src/wzscriptdebug.cpp @@ -949,6 +949,8 @@ class WzGraphicsPanel : public W_FORM case TerrainShaderQuality::NORMAL_MAPPING: gfx_api::TerrainCombined_High::get().recompile(); break; + case TerrainShaderQuality::UNINITIALIZED_PICK_DEFAULT: + break; } debug(LOG_INFO, "Done"); }, prevButton); @@ -965,6 +967,8 @@ class WzGraphicsPanel : public W_FORM case TerrainShaderQuality::NORMAL_MAPPING: gfx_api::WaterHighPSO::get().recompile(); break; + case TerrainShaderQuality::UNINITIALIZED_PICK_DEFAULT: + break; } debug(LOG_INFO, "Done"); }, prevButton);