diff --git a/changelog.txt b/changelog.txt index 2da4fa8..e802dfa 100644 --- a/changelog.txt +++ b/changelog.txt @@ -37,6 +37,12 @@ add: r_gpuPreference <0 to 2> (default: 0) sets the GPU selection preference 1 - low power (integrated graphics) 2 - none +add: r_gpuIndex <0 to GPU count> (default: 0) sets the specific GPU to use + 0 - default (falls back to r_gpuPreference) + I - uses the GPU at index I from /gpulist + +add: /gpulist lists available GPUs, the numbers can be used to set r_gpuIndex + add: r_sleepThreshold <2000 to 4000> (default: 2500) is the frame sleep time cushion, in microseconds it's a trade-off between frame time consistency and CPU usage set to 2000 if you have a struggling old/low-power CPU diff --git a/code/renderer/rhi_d3d12.cpp b/code/renderer/rhi_d3d12.cpp index 653ad27..6f62bc5 100644 --- a/code/renderer/rhi_d3d12.cpp +++ b/code/renderer/rhi_d3d12.cpp @@ -634,6 +634,12 @@ namespace RHI ResourceStates::Flags newState = ResourceStates::Common; }; + struct GPU + { + char name[256]; + LUID uniqueId; + }; + struct RHIPrivate { bool initialized; @@ -733,6 +739,8 @@ namespace RHI PIX pix; int64_t beforeInputSamplingUS; int64_t beforeRenderingUS; + GPU gpus[16]; + uint32_t gpuCount; // immediate-mode barrier API TextureBarrier textureBarriers[64]; @@ -1453,6 +1461,52 @@ namespace RHI return true; } + static void CreateAdapterList() + { + IDXGIAdapter1* adapter = NULL; + UINT enumIndex = 0; + rhi.gpuCount = 0; + while(rhi.gpuCount < ARRAY_LEN(rhi.gpus) && + SUCCEEDED(rhi.factory->EnumAdapters1(enumIndex++, &adapter))) + { + DXGI_ADAPTER_DESC1 desc; + if(IsSuitableAdapter(adapter) && SUCCEEDED(adapter->GetDesc1(&desc))) + { + GPU& gpu = rhi.gpus[rhi.gpuCount++]; + gpu.uniqueId = desc.AdapterLuid; + Q_strncpyz(gpu.name, GetUTF8String(desc.Description, "???"), sizeof(gpu.name)); + } + COM_RELEASE(adapter); + } + } + + static IDXGIAdapter1* GetAdapterAtIndex(int gpuIndex) + { + if(gpuIndex < 0 || gpuIndex >= ARRAY_LEN(rhi.gpus)) + { + ri.Printf(PRINT_WARNING, "GPU index %d is invalid", gpuIndex + 1); + return NULL; + } + const LUID uniqueId = rhi.gpus[gpuIndex].uniqueId; + + IDXGIAdapter1* adapter = NULL; + UINT enumIndex = 0; + while(SUCCEEDED(rhi.factory->EnumAdapters1(enumIndex++, &adapter))) + { + DXGI_ADAPTER_DESC1 desc; + if(SUCCEEDED(adapter->GetDesc1(&desc)) && + desc.AdapterLuid.LowPart == uniqueId.LowPart && + desc.AdapterLuid.HighPart == uniqueId.HighPart) + { + return adapter; + } + COM_RELEASE(adapter); + } + + ri.Printf(PRINT_WARNING, "GPU at index %d (%s) is no longer available", gpuIndex + 1, rhi.gpus[gpuIndex].name); + return NULL; + } + static IDXGIAdapter1* FindMostSuitableAdapter(IDXGIFactory1* factory, int enginePreference) { IDXGIAdapter1* adapter = NULL; @@ -3145,7 +3199,15 @@ namespace RHI D3D(CreateDXGIFactory1(IID_PPV_ARGS(&rhi.factory))); #endif - rhi.adapter = FindMostSuitableAdapter(rhi.factory, r_gpuPreference->integer); + CreateAdapterList(); + if(r_gpuIndex->integer > 0) + { + rhi.adapter = GetAdapterAtIndex(r_gpuIndex->integer - 1); + } + if(rhi.adapter == NULL) + { + rhi.adapter = FindMostSuitableAdapter(rhi.factory, r_gpuPreference->integer); + } { char adapterName[256]; const char* adapterNamePtr = "unknown"; @@ -3158,6 +3220,24 @@ namespace RHI ri.Printf(PRINT_ALL, "Selected graphics adapter: %s\n", adapterNamePtr); Q_strncpyz(rhi.adapterName, adapterNamePtr, sizeof(rhi.adapterName)); } + { + Cvar_SetRange(r_gpuIndex->name, r_gpuIndex->type, "0", va("%d", rhi.gpuCount)); + + char values[256]; + StringList stringList; + stringList.Init(values, sizeof(values)); + stringList.Append("0"); + stringList.Append("Default GPU"); + stringList.Append(""); + for(uint32_t i = 0; i < rhi.gpuCount; ++i) + { + stringList.Append(va("%d", (int)i + 1)); + stringList.Append(rhi.gpus[i].name); + stringList.Append(""); + } + stringList.Terminate(); + Cvar_SetMenuData(r_gpuIndex->name, CVARCAT_DISPLAY | CVARCAT_PERFORMANCE, "GPU selection", "Choose the GPU to use", "", values); + } D3D(D3D12CreateDevice(rhi.adapter, FeatureLevel, IID_PPV_ARGS(&rhi.device))); @@ -3475,6 +3555,13 @@ namespace RHI destroyWindow = true; } + if(!destroyWindow && + r_gpuIndex->latchedString != NULL && + Q_stricmp(r_gpuIndex->latchedString, r_gpuIndex->string) != 0) + { + destroyWindow = true; + } + if(rhi.frameBegun) { backEnd.renderFrame = qfalse; @@ -5257,6 +5344,17 @@ namespace RHI barrier.UAV.pResource = NULL; rhi.commandList->ResourceBarrier(1, &barrier); } + + void PrintGPUList() + { + CreateAdapterList(); + + ri.Printf(PRINT_ALL, "%s0^7. Default\n", S_COLOR_VAL); + for(uint32_t i = 0; i < rhi.gpuCount; ++i) + { + ri.Printf(PRINT_ALL, "%s%d^7. %s\n", S_COLOR_VAL, (int)i + 1, rhi.gpus[i].name); + } + } } void R_WaitBeforeInputSampling() diff --git a/code/renderer/rhi_local.h b/code/renderer/rhi_local.h index 302d708..07c1c5f 100644 --- a/code/renderer/rhi_local.h +++ b/code/renderer/rhi_local.h @@ -1102,4 +1102,35 @@ namespace RHI uint32_t size = 0; uint32_t offset = 0; }; + + struct StringList + { + void Init(char* buffer_, size_t byteCount) + { + buffer = buffer_; + written = 0; + remaining = byteCount; + } + + void Append(const char* string) + { + const size_t l = strlen(string); + if(remaining >= l + 2) + { + memcpy(buffer + written, string, l + 1); + written += l + 1; + remaining -= l + 1; + } + } + + void Terminate() + { + buffer[written++] = '\0'; + remaining--; + } + + char* buffer = NULL; + size_t written = 0; + size_t remaining = 0; + }; } diff --git a/code/renderer/rhi_public.h b/code/renderer/rhi_public.h index 723119d..2137b3f 100644 --- a/code/renderer/rhi_public.h +++ b/code/renderer/rhi_public.h @@ -51,4 +51,6 @@ namespace RHI uint32_t srcRowByteCount; uint32_t dstRowByteCount; }; + + void PrintGPUList(); } diff --git a/code/renderer/tr_help.h b/code/renderer/tr_help.h index cd724b2..ac9ec6e 100644 --- a/code/renderer/tr_help.h +++ b/code/renderer/tr_help.h @@ -128,6 +128,15 @@ S_COLOR_VAL " 0 " S_COLOR_HELP "= High performance\n" \ S_COLOR_VAL " 1 " S_COLOR_HELP "= Low power\n" \ S_COLOR_VAL " 2 " S_COLOR_HELP "= None" +#define help_r_gpuIndex \ +"sets the index of the GPU to use\n" \ +S_COLOR_VAL " 0 " S_COLOR_HELP "= Default\n" \ +"Use " S_COLOR_CMD "gpulist " S_COLOR_HELP "to see the numbered list of GPUs." + +#define help_gpulist \ +"prints usable GPUs\n" \ +"The numbers can be used to set " S_COLOR_CVAR "r_gpuIndex" S_COLOR_HELP "." + #define help_r_colorMipLevels \ "colorizes textures based on their mip level" diff --git a/code/renderer/tr_init.cpp b/code/renderer/tr_init.cpp index 502cbe2..d669899 100644 --- a/code/renderer/tr_init.cpp +++ b/code/renderer/tr_init.cpp @@ -69,6 +69,7 @@ cvar_t *r_dither; cvar_t *r_rtColorFormat; cvar_t *r_depthClamp; cvar_t *r_gpuPreference; +cvar_t *r_gpuIndex; cvar_t *r_mipGenFilter; cvar_t *r_mipGenGamma; @@ -316,6 +317,7 @@ static const cmdTableItem_t r_cmds[] = { "shadermixeduse", R_ShaderMixedUse_f, NULL, "prints all mixed use issues" }, { "skinlist", R_SkinList_f, NULL, "prints loaded skins" }, { "modellist", R_Modellist_f, NULL, "prints loaded models" }, + { "gpulist", RHI::PrintGPUList, NULL, help_gpulist }, { "screenshot", R_ScreenShotTGA_f, NULL, "takes a TARGA (.tga) screenshot" }, { "screenshotJPEG", R_ScreenShotJPG_f, NULL, "takes a JPEG (.jpg) screenshot" }, { "screenshotnc", R_ScreenShotNoConTGA_f, NULL, "takes a TARGA screenshot w/o the console" }, @@ -439,11 +441,15 @@ static const cvarTableItem_t r_cvars[] = }, { &r_gpuPreference, "r_gpuPreference", "0", CVAR_ARCHIVE | CVAR_LATCH, CVART_INTEGER, "0", XSTRING(GPUPREF_MAX), help_r_gpuPreference, - "GPU selection", CVARCAT_DISPLAY | CVARCAT_PERFORMANCE, "Choose between low-power and high-performance devices", "", + "GPU preference", CVARCAT_DISPLAY | CVARCAT_PERFORMANCE, "Choose between low-power and high-performance devices", "", CVAR_GUI_VALUE("0", "High performance", "") CVAR_GUI_VALUE("1", "Low power", "") CVAR_GUI_VALUE("2", "None", "") }, + { + // GUI settings and data range are set by the RHI during init + &r_gpuIndex, "r_gpuIndex", "0", CVAR_ARCHIVE | CVAR_LATCH, CVART_INTEGER, "0", NULL, help_r_gpuIndex + }, { &r_vsync, "r_vsync", "0", CVAR_ARCHIVE | CVAR_LATCH, CVART_BOOL, NULL, NULL, "enables v-sync", "V-Sync", CVARCAT_DISPLAY | CVARCAT_PERFORMANCE, "Enabling locks the framerate to the monitor's refresh rate", "", diff --git a/code/renderer/tr_local.h b/code/renderer/tr_local.h index 33fb694..bf4f067 100644 --- a/code/renderer/tr_local.h +++ b/code/renderer/tr_local.h @@ -1060,6 +1060,7 @@ extern cvar_t *r_dither; // enables dithering extern cvar_t *r_rtColorFormat; // color render target format, see RTCF_* extern cvar_t *r_depthClamp; // disables clipping vertices against the near and far clip planes extern cvar_t *r_gpuPreference; // shall we use high-performance or low-power devices? +extern cvar_t *r_gpuIndex; // the index of the specific device to use extern cvar_t *r_mipGenFilter; // if the string is invalid, Lanczos 4 is used extern cvar_t *r_mipGenGamma; // what gamma-space do we consider the textures to be in