Merge remote-tracking branch 'SRSaunders/hud-vulkan-macos'

This commit is contained in:
Robert Beckebans 2024-02-04 16:32:53 +01:00
commit 436144f6e1
22 changed files with 469 additions and 99 deletions

View file

@ -510,7 +510,7 @@ Recommended in this case is `cmake-vs2022-win64-no-ffmpeg.bat`
Xcode release and universal builds now automatically package the executable into a macOS app bundle, defining an Info.plist file and copying the base directory and custom icon into the application bundle's Contents/Resources folder. This is controlled by adding -DMACOSX_BUNDLE=ON to the CMake options.
Depending on which package manager you install (Homebrew or MacPorts) you may need to change the openal-soft library and include paths specified in the cmake shell scripts. For single architecture builds (debug, release, retail) the default openal-soft paths are set for Homebrew on x86, while for universal builds the default paths are set for MacPorts on x86 or Apple Silicon. If you want to build using the single architecture shell scripts (debug, release, retail) on Apple Silicon, you will need to change the openal-soft paths from `/usr/local/...` to either `/opt/homebrew/...` (Homebrew) or `/opt/local/...` (MacPorts).
For single architecture builds (debug, release, retail) the default openal-soft paths are set for Homebrew, while for universal builds the default paths are set for MacPorts. The single architecture build scripts are now portable and automatically detect Homebrew's openal-soft path prefix for x86 and Apple Silicon. The universal build script remains portable since MacPorts uses the same openal-soft installation path on x86 and Apple Silicon.
4. Compile RBDOOM-3-BFG targets:

View file

@ -429,6 +429,8 @@ if(USE_VULKAN)
if(USE_VMA)
add_definitions(-DUSE_AMD_ALLOCATOR)
include_directories("libs/vma/include")
file(GLOB VMA_INCLUDES libs/vma/include/*.h)
source_group("libs\\vma" FILES ${VMA_INCLUDES})
endif()
endif()
@ -1304,6 +1306,7 @@ set(RBDOOM3_INCLUDES
#${FREETYPE_SOURCES}
${SOUND_INCLUDES}
${OGGVORBIS_INCLUDES}
${VMA_INCLUDES}
${OPTICK_INCLUDES}
${UI_INCLUDES}
${SWF_INCLUDES}
@ -1776,7 +1779,8 @@ else()
# delete precompiled header file after executable is compiled: IDE build case (e.g. Xcode)
else()
add_custom_command(TARGET RBDoom3BFG POST_BUILD
COMMAND ${remove_command} "idlib/precompiled.h.gch"
# SRS - added wildcards to remove tmp files from cmake ZERO_CHECK regeneration
COMMAND ${remove_command} "idlib/precompiled.h*.gch*"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "remove idlib/precompiled.h.gch"
)

View file

@ -2,5 +2,16 @@ cd ..
rm -rf build
mkdir build
cd build
# asemarafa/SRS - Determine the Homebrew path prefix for openal-soft
if [ -z "$OPENAL_PREFIX" ]; then
OPENAL_PREFIX=$(brew --prefix openal-soft 2>/dev/null)
if [ -z "$OPENAL_PREFIX" ]; then
echo "Error: openal-soft is not installed via Homebrew."
echo "Either install it using 'brew install openal-soft' or define the path prefix via OPENAL_PREFIX."
exit 1
fi
fi
# change or remove -DCMAKE_OSX_DEPLOYMENT_TARGET=<version> to match supported runtime targets
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS_RELEASE="-DNDEBUG" -DCMAKE_OSX_DEPLOYMENT_TARGET=12.1 -DFFMPEG=OFF -DBINKDEC=ON -DUSE_MoltenVK=ON -DOPENAL_LIBRARY=/usr/local/opt/openal-soft/lib/libopenal.dylib -DOPENAL_INCLUDE_DIR=/usr/local/opt/openal-soft/include ../neo -Wno-dev
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS_RELEASE="-DNDEBUG" -DCMAKE_OSX_DEPLOYMENT_TARGET=12.1 -DFFMPEG=OFF -DBINKDEC=ON -DUSE_MoltenVK=ON -DOPENAL_LIBRARY=$OPENAL_PREFIX/lib/libopenal.dylib -DOPENAL_INCLUDE_DIR=$OPENAL_PREFIX/include ../neo -Wno-dev

View file

@ -2,5 +2,16 @@ cd ..
rm -rf build
mkdir build
cd build
# asemarafa/SRS - Determine the Homebrew path prefix for openal-soft
if [ -z "$OPENAL_PREFIX" ]; then
OPENAL_PREFIX=$(brew --prefix openal-soft 2>/dev/null)
if [ -z "$OPENAL_PREFIX" ]; then
echo "Error: openal-soft is not installed via Homebrew."
echo "Either install it using 'brew install openal-soft' or define the path prefix via OPENAL_PREFIX."
exit 1
fi
fi
# change or remove -DCMAKE_OSX_DEPLOYMENT_TARGET=<version> to match supported runtime targets
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS_RELEASE="-DNDEBUG -DID_RETAIL" -DCMAKE_OSX_DEPLOYMENT_TARGET=12.1 -DFFMPEG=OFF -DBINKDEC=ON -DUSE_MoltenVK=ON -DOPENAL_LIBRARY=/usr/local/opt/openal-soft/lib/libopenal.dylib -DOPENAL_INCLUDE_DIR=/usr/local/opt/openal-soft/include ../neo -Wno-dev
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS_RELEASE="-DNDEBUG -DID_RETAIL" -DCMAKE_OSX_DEPLOYMENT_TARGET=12.1 -DFFMPEG=OFF -DBINKDEC=ON -DUSE_MoltenVK=ON -DOPENAL_LIBRARY=$OPENAL_PREFIX/lib/libopenal.dylib -DOPENAL_INCLUDE_DIR=$OPENAL_PREFIX/include ../neo -Wno-dev

View file

@ -2,10 +2,22 @@ cd ..
rm -rf xcode-debug
mkdir xcode-debug
cd xcode-debug
# asemarafa/SRS - Determine the Homebrew path prefix for openal-soft
if [ -z "$OPENAL_PREFIX" ]; then
OPENAL_PREFIX=$(brew --prefix openal-soft 2>/dev/null)
if [ -z "$OPENAL_PREFIX" ]; then
echo "Error: openal-soft is not installed via Homebrew."
echo "Either install it using 'brew install openal-soft' or define the path prefix via OPENAL_PREFIX."
exit 1
fi
fi
# note 1: remove or set -DCMAKE_SUPPRESS_REGENERATION=OFF to reenable ZERO_CHECK target which checks for CMakeLists.txt changes and re-runs CMake before builds
# however, if ZERO_CHECK is reenabled **must** add VULKAN_SDK location to Xcode Custom Paths (under Prefs/Locations) otherwise build failures may occur
# note 2: policy CMAKE_POLICY_DEFAULT_CMP0142=NEW suppresses non-existant per-config suffixes on Xcode library search paths, works for cmake version 3.25 and later
# note 3: env variable MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE=1 enables MoltenVK's image view swizzle which may be required on older macOS versions or hardware (see vulkaninfo)
# note 4: env variable MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS=0 disables synchronous queue submits which is optimal for the synchronization method used by the game
# note 5: env variable MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=2 enables MoltenVK's use of Metal argument buffers only if VK_EXT_descriptor_indexing is enabled
cmake -G Xcode -DCMAKE_BUILD_TYPE=Debug -DCMAKE_XCODE_GENERATE_SCHEME=ON -DCMAKE_XCODE_SCHEME_ENVIRONMENT="MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE=1;MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS=0;MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=2" -DCMAKE_XCODE_SCHEME_ENABLE_GPU_API_VALIDATION=OFF -DCMAKE_SUPPRESS_REGENERATION=ON -DOPENAL_LIBRARY=/usr/local/opt/openal-soft/lib/libopenal.dylib -DOPENAL_INCLUDE_DIR=/usr/local/opt/openal-soft/include ../neo -DCMAKE_POLICY_DEFAULT_CMP0142=NEW -Wno-dev
# note 3: env variable MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE=1 enables MoltenVK's image view swizzle which may be required on older macOS versions or hardware (see vulkaninfo) - only used for VulkanSDK < 1.3.275
# note 4: env variable MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS=0 disables synchronous queue submits which is optimal for the synchronization method used by the game - only used for VulkanSDK < 1.3.275
# note 5: env variable MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=2 enables MoltenVK's use of Metal argument buffers only if VK_EXT_descriptor_indexing is enabled - only used for VulkanSDK < 1.3.275
# note 6: env variable MVK_CONFIG_TIMESTAMP_PERIOD_LOWPASS_ALPHA=1.0 disables MoltenVK's timestampPeriod lowpass filter for non-Apple GPUs - only used for VulkanSDK < 1.3.275
cmake -G Xcode -DCMAKE_BUILD_TYPE=Debug -DCMAKE_XCODE_GENERATE_SCHEME=ON -DCMAKE_XCODE_SCHEME_ENVIRONMENT="MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE=1;MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS=0;MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=2;MVK_CONFIG_TIMESTAMP_PERIOD_LOWPASS_ALPHA=1.0" -DCMAKE_XCODE_SCHEME_ENABLE_GPU_API_VALIDATION=OFF -DCMAKE_SUPPRESS_REGENERATION=ON -DOPENAL_LIBRARY=$OPENAL_PREFIX/lib/libopenal.dylib -DOPENAL_INCLUDE_DIR=$OPENAL_PREFIX/include ../neo -DCMAKE_POLICY_DEFAULT_CMP0142=NEW -Wno-dev

View file

@ -2,7 +2,18 @@ cd ..
rm -rf xcode-release
mkdir xcode-release
cd xcode-release
# asemarafa/SRS - Determine the Homebrew path prefix for openal-soft
if [ -z "$OPENAL_PREFIX" ]; then
OPENAL_PREFIX=$(brew --prefix openal-soft 2>/dev/null)
if [ -z "$OPENAL_PREFIX" ]; then
echo "Error: openal-soft is not installed via Homebrew."
echo "Either install it using 'brew install openal-soft' or define the path prefix via OPENAL_PREFIX."
exit 1
fi
fi
# note 1: remove or set -DCMAKE_SUPPRESS_REGENERATION=OFF to reenable ZERO_CHECK target which checks for CMakeLists.txt changes and re-runs CMake before builds
# however, if ZERO_CHECK is reenabled **must** add VULKAN_SDK location to Xcode Custom Paths (under Prefs/Locations) otherwise build failures may occur
# note 2: policy CMAKE_POLICY_DEFAULT_CMP0142=NEW suppresses non-existant per-config suffixes on Xcode library search paths, works for cmake version 3.25 and later
cmake -G Xcode -DCMAKE_BUILD_TYPE=Release -DCMAKE_CONFIGURATION_TYPES="Release;MinSizeRel;RelWithDebInfo" -DMACOSX_BUNDLE=ON -DFFMPEG=OFF -DBINKDEC=ON -DUSE_MoltenVK=ON -DCMAKE_XCODE_GENERATE_SCHEME=ON -DCMAKE_XCODE_SCHEME_ENABLE_GPU_API_VALIDATION=OFF -DCMAKE_SUPPRESS_REGENERATION=ON -DOPENAL_LIBRARY=/usr/local/opt/openal-soft/lib/libopenal.dylib -DOPENAL_INCLUDE_DIR=/usr/local/opt/openal-soft/include ../neo -DCMAKE_POLICY_DEFAULT_CMP0142=NEW -Wno-dev
cmake -G Xcode -DCMAKE_BUILD_TYPE=Release -DCMAKE_CONFIGURATION_TYPES="Release;MinSizeRel;RelWithDebInfo" -DMACOSX_BUNDLE=ON -DFFMPEG=OFF -DBINKDEC=ON -DUSE_MoltenVK=ON -DCMAKE_XCODE_GENERATE_SCHEME=ON -DCMAKE_XCODE_SCHEME_ENABLE_GPU_API_VALIDATION=OFF -DCMAKE_SUPPRESS_REGENERATION=ON -DOPENAL_LIBRARY=$OPENAL_PREFIX/lib/libopenal.dylib -DOPENAL_INCLUDE_DIR=$OPENAL_PREFIX/include ../neo -DCMAKE_POLICY_DEFAULT_CMP0142=NEW -Wno-dev

View file

@ -2,6 +2,15 @@ cd ..
rm -rf xcode-universal
mkdir xcode-universal
cd xcode-universal
# SRS - Determine if openal-soft universal variant is installed via MacPorts
OPENAL_VARIANTS=$(port info --variants openal-soft 2>/dev/null)
if [[ $OPENAL_VARIANTS != *universal* ]]; then
echo "Error: openal-soft universal variant is not installed via MacPorts."
echo "Please install it using 'sudo port install openal-soft +universal'"
exit 1
fi
# note 1: remove or set -DCMAKE_SUPPRESS_REGENERATION=OFF to reenable ZERO_CHECK target which checks for CMakeLists.txt changes and re-runs CMake before builds
# however, if ZERO_CHECK is reenabled **must** add VULKAN_SDK location to Xcode Custom Paths (under Prefs/Locations) otherwise build failures may occur
# note 2: policy CMAKE_POLICY_DEFAULT_CMP0142=NEW suppresses non-existant per-config suffixes on Xcode library search paths, works for cmake version 3.25 and later

View file

@ -315,6 +315,7 @@ void idCommonLocal::TimeRenderDemo( const char* demoName, bool twice, bool quit
while( readDemo )
{
BusyWait(); // SRS - BusyWait() calls UpdateScreen() which draws and renders out-of-sequence but still supports frame timing
commonLocal.frameTiming.finishSyncTime_EndFrame = Sys_Microseconds();
commonLocal.mainFrameTiming = commonLocal.frameTiming;
// ** End of current logical frame **

View file

@ -380,14 +380,26 @@ public:
// RB end
// SRS start
uint64 GetRendererStartFrameSyncMicroseconds() const
void SetRendererMvkEncodeMicroseconds( uint64 mvkEncodeMicroSeconds )
{
return mainFrameTiming.finishSyncTime - mainFrameTiming.startSyncTime;
metal_encode = mvkEncodeMicroSeconds;
return;
}
uint64 GetRendererEndFrameSyncMicroseconds() const
uint64 GetRendererMvkEncodeMicroseconds() const
{
return mainFrameTiming.finishSyncTime_EndFrame - mainFrameTiming.startRenderTime;
return metal_encode;
}
void SetRendererGpuMemoryMB( int gpuMemoryMB )
{
gpu_memory = gpuMemoryMB;
return;
}
int GetRendererGpuMemoryMB() const
{
return gpu_memory;
}
// SRS end
@ -604,6 +616,11 @@ private:
backEndCounters_t stats_backend;
performanceCounters_t stats_frontend;
// SRS - MoltenVK's Vulkan to Metal command buffer encoding time, set default to 0 for non-macOS platforms (Windows and Linux)
uint64 metal_encode = 0;
// SRS - Cross-platform GPU Memory usage counter, set default to 0 in case platform or graphics API does not support queries
int gpu_memory = 0;
// Used during loading screens
int lastPacifierSessionTime;
int lastPacifierGuiTime;

View file

@ -225,8 +225,10 @@ float idConsoleLocal::DrawFPS( float y )
extern idCVar r_swapInterval;
static float previousTimes[FPS_FRAMES];
static float previousCpuUsage[FPS_FRAMES] = {};
static float previousGpuUsage[FPS_FRAMES] = {};
static float previousTimesNormalized[FPS_FRAMES_HISTORY];
static int index;
static int index = 0;
static int previous;
static int valuesOffset = 0;
@ -239,6 +241,8 @@ float idConsoleLocal::DrawFPS( float y )
previous = t;
int fps = 0;
float cpuUsage = 0.0;
float gpuUsage = 0.0;
const float milliSecondsPerFrame = 1000.0f / com_engineHz_latched;
@ -253,6 +257,8 @@ float idConsoleLocal::DrawFPS( float y )
for( int i = 0 ; i < FPS_FRAMES ; i++ )
{
total += previousTimes[i];
cpuUsage += previousCpuUsage[i];
gpuUsage += previousGpuUsage[i];
}
if( !total )
{
@ -260,6 +266,8 @@ float idConsoleLocal::DrawFPS( float y )
}
fps = 1000000 * FPS_FRAMES / total;
fps = ( fps + 500 ) / 1000;
cpuUsage /= FPS_FRAMES;
gpuUsage /= FPS_FRAMES;
const char* s = va( "%ifps", fps );
int w = strlen( s ) * BIGCHAR_WIDTH;
@ -309,12 +317,26 @@ float idConsoleLocal::DrawFPS( float y )
const int64 frameIdleTime = int64( commonLocal.mainFrameTiming.startGameTime ) - int64( commonLocal.mainFrameTiming.finishSyncTime );
const int64 frameBusyTime = int64( commonLocal.frameTiming.finishSyncTime ) - int64( commonLocal.mainFrameTiming.startGameTime );
// SRS - Frame sync time represents swap buffer synchronization + game thread wait + other time spent outside of rendering
const int64 frameSyncTime = int64( commonLocal.frameTiming.finishSyncTime ) - int64( commonLocal.mainFrameTiming.startRenderTime ) - int64( rendererBackEndTime );
// SRS - Frame sync time represents swap buffer synchronization + other frame time spent outside of game thread and renderer backend
const int64 gameThreadWaitTime = int64( commonLocal.mainFrameTiming.finishSyncTime_EndFrame ) - int64( commonLocal.mainFrameTiming.finishRenderTime );
const int64 frameSyncTime = int64( commonLocal.frameTiming.finishSyncTime ) - int64( commonLocal.mainFrameTiming.startRenderTime + rendererBackEndTime ) - gameThreadWaitTime;
// SRS - GPU idle time is simply the difference between measured frame-over-frame time and GPU busy time (directly from GPU timers)
const int64 rendererGPUIdleTime = frameBusyTime + frameIdleTime - rendererGPUTime;
// SRS - Estimate CPU busy time measured from start of game thread until completion of game thread and renderer backend (including excess MoltenVK encoding time if applicable)
#if defined(__APPLE__) && defined( USE_MoltenVK )
const int64 rendererMvkEncodeTime = commonLocal.GetRendererMvkEncodeMicroseconds();
const int64 rendererQueueSubmitTime = int64( commonLocal.mainFrameTiming.finishRenderTime - commonLocal.mainFrameTiming.startRenderTime ) - int64( rendererBackEndTime );
const int64 rendererCPUBusyTime = int64( commonLocal.mainFrameTiming.finishSyncTime_EndFrame - commonLocal.mainFrameTiming.startGameTime ) + Min( Max( int64( 0 ), rendererMvkEncodeTime - rendererQueueSubmitTime - gameThreadWaitTime ), frameSyncTime - rendererQueueSubmitTime );
#else
const int64 rendererCPUBusyTime = int64( commonLocal.mainFrameTiming.finishSyncTime_EndFrame - commonLocal.mainFrameTiming.startGameTime );
#endif
// SRS - Save current CPU and GPU usage factors in ring buffer to calculate smoothed averages for future frames
previousCpuUsage[(index - 1) % FPS_FRAMES] = float( rendererCPUBusyTime ) / float( frameBusyTime + frameIdleTime ) * 100.0;
previousGpuUsage[(index - 1) % FPS_FRAMES] = float( rendererGPUTime ) / float( rendererGPUTime + rendererGPUIdleTime ) * 100.0;
#if 1
// RB: use ImGui to show more detailed stats about the scene loads
@ -322,7 +344,7 @@ float idConsoleLocal::DrawFPS( float y )
{
// start smaller
int32 statsWindowWidth = 320;
int32 statsWindowHeight = 315;
int32 statsWindowHeight = 330;
if( com_showFPS.GetInteger() > 2 )
{
@ -427,7 +449,7 @@ float idConsoleLocal::DrawFPS( float y )
ImGui::TextColored( colorCyan, "API: %s, AA[%i, %i]: %s, %s", API, width, height, aaMode, resolutionText.c_str() );
ImGui::TextColored( colorGold, "Device: %s", deviceManager->GetRendererString() );
ImGui::TextColored( colorGold, "Device: %s, Memory: %i MB", deviceManager->GetRendererString(), commonLocal.GetRendererGpuMemoryMB() );
ImGui::TextColored( colorLtGrey, "GENERAL: views:%i draws:%i tris:%i",
commonLocal.stats_frontend.c_numViews,
@ -477,7 +499,7 @@ float idConsoleLocal::DrawFPS( float y )
if( com_showFPS.GetInteger() > 2 )
{
const char* overlay = va( "Average FPS %i", fps );
const char* overlay = va( "Average FPS %-4i", fps );
ImGui::PlotLines( "Relative\nFrametime ms", previousTimesNormalized, FPS_FRAMES_HISTORY, valuesOffset, overlay, -10.0f, 10.0f, ImVec2( 0, 50 ) );
}
@ -494,12 +516,20 @@ float idConsoleLocal::DrawFPS( float y )
ImGui::TextColored( gameThreadRenderTime > maxTime ? colorRed : colorWhite, "RF: %5llu us SSR: %5llu us", gameThreadRenderTime, rendererGPU_SSRTime );
ImGui::TextColored( rendererBackEndTime > maxTime ? colorRed : colorWhite, "RB: %5llu us Ambient Pass: %5llu us", rendererBackEndTime, rendererGPUAmbientPassTime );
ImGui::TextColored( rendererGPUShadowAtlasTime > maxTime ? colorRed : colorWhite, "Shadows: %5llu us Shadow Atlas: %5llu us", rendererShadowsTime, rendererGPUShadowAtlasTime );
#if defined(__APPLE__) && defined( USE_MoltenVK )
// SRS - For more recent versions of MoltenVK with enhanced performance statistics (v1.2.6 and later), display the Vulkan to Metal encoding thread time on macOS
ImGui::TextColored( rendererMvkEncodeTime > maxTime || rendererGPUInteractionsTime > maxTime ? colorRed : colorWhite, "Encode: %5lld us Interactions: %5llu us", rendererMvkEncodeTime, rendererGPUInteractionsTime );
ImGui::TextColored( rendererGPUShaderPassesTime > maxTime ? colorRed : colorWhite, "Sync: %5lld us Shader Pass: %5llu us", frameSyncTime, rendererGPUShaderPassesTime );
#else
ImGui::TextColored( rendererGPUInteractionsTime > maxTime ? colorRed : colorWhite, "Sync: %5lld us Interactions: %5llu us", frameSyncTime, rendererGPUInteractionsTime );
ImGui::TextColored( rendererGPUShaderPassesTime > maxTime ? colorRed : colorWhite, " Shader Pass: %5llu us", rendererGPUShaderPassesTime );
#endif
ImGui::TextColored( rendererGPU_TAATime > maxTime ? colorRed : colorWhite, " TAA: %5llu us", rendererGPU_TAATime );
ImGui::TextColored( rendererGPUPostProcessingTime > maxTime ? colorRed : colorWhite, " PostFX: %5llu us", rendererGPUPostProcessingTime );
ImGui::TextColored( frameBusyTime > maxTime || rendererGPUTime > maxTime ? colorRed : colorWhite, "Total: %5lld us Total: %5lld us", frameBusyTime, rendererGPUTime );
ImGui::TextColored( colorWhite, "Idle: %5lld us Idle: %5lld us", frameIdleTime, rendererGPUIdleTime );
// SRS - Show CPU and GPU overall usage statistics
ImGui::TextColored( colorWhite, "Usage: %3.0f %% Usage: %3.0f %%", cpuUsage, gpuUsage );
ImGui::End();
}

View file

@ -882,13 +882,13 @@ void idCommonLocal::Frame()
}
frameTiming.finishRenderTime = Sys_Microseconds();
// SRS - Use finishSyncTime_EndFrame to record timing just before gameThread.WaitForThread() for com_smp = 1
frameTiming.finishSyncTime_EndFrame = Sys_Microseconds();
// make sure the game / draw thread has completed
// This may block if the game is taking longer than the render back end
gameThread.WaitForThread();
// SRS - Use finishSyncTime_EndFrame to record timing just after gameThread.WaitForThread()
frameTiming.finishSyncTime_EndFrame = Sys_Microseconds();
// Send local usermds to the server.
// This happens after the game frame has run so that prediction data is up to date.
SendUsercmds( Game()->GetLocalClientNum() );

View file

@ -70,5 +70,11 @@
#else
#define OPTICK_ENABLE_GPU_VULKAN (OPTICK_ENABLE_GPU /*&& 0*/)
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Vulkan Functions
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if !defined(OPTICK_STATIC_VULKAN_FUNCTIONS)
#define OPTICK_STATIC_VULKAN_FUNCTIONS (0 /*1*/)
#endif
#endif

View file

@ -96,6 +96,7 @@
// Vulkan Forward Declarations
#define OPTICK_DEFINE_HANDLE(object) typedef struct object##_T *object;
OPTICK_DEFINE_HANDLE(VkInstance);
OPTICK_DEFINE_HANDLE(VkDevice);
OPTICK_DEFINE_HANDLE(VkPhysicalDevice);
OPTICK_DEFINE_HANDLE(VkQueue);
@ -125,6 +126,7 @@ struct VkCommandBufferBeginInfo;
#endif
#endif
typedef void* (VKAPI_PTR *PFN_vkGetInstanceProcAddr_)(VkInstance instance, const char* pName);
typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceProperties_)(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties* pProperties);
typedef int32_t (VKAPI_PTR *PFN_vkCreateQueryPool_)(VkDevice device, const VkQueryPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkQueryPool* pQueryPool);
typedef int32_t (VKAPI_PTR *PFN_vkCreateCommandPool_)(VkDevice device, const VkCommandPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkCommandPool* pCommandPool);
@ -165,6 +167,7 @@ namespace Optick
{
struct OPTICK_API VulkanFunctions
{
PFN_vkGetInstanceProcAddr_ vkGetInstanceProcAddr;
PFN_vkGetPhysicalDeviceProperties_ vkGetPhysicalDeviceProperties;
PFN_vkCreateQueryPool_ vkCreateQueryPool;
PFN_vkCreateCommandPool_ vkCreateCommandPool;
@ -778,7 +781,7 @@ struct OPTICK_API GPUContext
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
OPTICK_API void InitGpuD3D12(ID3D12Device* device, ID3D12CommandQueue** cmdQueues, uint32_t numQueues);
OPTICK_API void InitGpuVulkan(VkDevice* vkDevices, VkPhysicalDevice* vkPhysicalDevices, VkQueue* vkQueues, uint32_t* cmdQueuesFamily, uint32_t numQueues, const VulkanFunctions* functions);
OPTICK_API void InitGpuVulkan(VkInstance vkInstance, VkDevice* vkDevices, VkPhysicalDevice* vkPhysicalDevices, VkQueue* vkQueues, uint32_t* cmdQueuesFamily, uint32_t numQueues, const VulkanFunctions* functions);
OPTICK_API void GpuFlip(void* swapChain, uint32_t frameID = 0);
OPTICK_API GPUContext SetGpuContext(GPUContext context);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -1046,7 +1049,7 @@ struct OptickApp
// GPU events
#define OPTICK_GPU_INIT_D3D12(DEVICE, CMD_QUEUES, NUM_CMD_QUEUS) ::Optick::InitGpuD3D12(DEVICE, CMD_QUEUES, NUM_CMD_QUEUS);
#define OPTICK_GPU_INIT_VULKAN(DEVICES, PHYSICAL_DEVICES, CMD_QUEUES, CMD_QUEUES_FAMILY, NUM_CMD_QUEUS, FUNCTIONS) ::Optick::InitGpuVulkan(DEVICES, PHYSICAL_DEVICES, CMD_QUEUES, CMD_QUEUES_FAMILY, NUM_CMD_QUEUS, FUNCTIONS);
#define OPTICK_GPU_INIT_VULKAN(INSTANCE, DEVICES, PHYSICAL_DEVICES, CMD_QUEUES, CMD_QUEUES_FAMILY, NUM_CMD_QUEUS, FUNCTIONS) ::Optick::InitGpuVulkan(INSTANCE, DEVICES, PHYSICAL_DEVICES, CMD_QUEUES, CMD_QUEUES_FAMILY, NUM_CMD_QUEUS, FUNCTIONS);
// Setup GPU context:
// Params:
@ -1121,7 +1124,7 @@ struct OptickApp
#define OPTICK_SET_MEMORY_ALLOCATOR(ALLOCATE_FUNCTION, DEALLOCATE_FUNCTION, INIT_THREAD_CALLBACK)
#define OPTICK_SHUTDOWN()
#define OPTICK_GPU_INIT_D3D12(DEVICE, CMD_QUEUES, NUM_CMD_QUEUS)
#define OPTICK_GPU_INIT_VULKAN(DEVICES, PHYSICAL_DEVICES, CMD_QUEUES, CMD_QUEUES_FAMILY, NUM_CMD_QUEUS, FUNCTIONS)
#define OPTICK_GPU_INIT_VULKAN(INSTANCE, DEVICES, PHYSICAL_DEVICES, CMD_QUEUES, CMD_QUEUES_FAMILY, NUM_CMD_QUEUS, FUNCTIONS)
#define OPTICK_GPU_CONTEXT(...)
#define OPTICK_GPU_EVENT(NAME)
#define OPTICK_GPU_FLIP(...)

View file

@ -33,6 +33,7 @@
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdexcept>
#if defined(OPTICK_MSVC)
@ -143,11 +144,12 @@ static const ProcessID INVALID_PROCESS_ID = (ProcessID)-1;
#ifdef _DEBUG
#define OPTICK_ASSERT(arg, description) if (!(arg)) { OPTICK_DEBUG_BREAK; }
#define OPTICK_FAILED(description) { OPTICK_DEBUG_BREAK; }
#define OPTICK_VERIFY(arg, description, operation) if (!(arg)) { OPTICK_DEBUG_BREAK; operation; }
#else
#define OPTICK_ASSERT(arg, description)
#define OPTICK_FAILED(description)
#define OPTICK_FAILED(description) { throw std::runtime_error(description); }
#define OPTICK_VERIFY(arg, description, operation) if (!(arg)) { OPTICK_FAILED(description); operation; }
#endif
#define OPTICK_VERIFY(arg, description, operation) if (!(arg)) { OPTICK_DEBUG_BREAK; operation; }
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

View file

@ -71,7 +71,7 @@ namespace Optick
GPUProfilerVulkan();
~GPUProfilerVulkan();
void InitDevice(VkDevice* devices, VkPhysicalDevice* physicalDevices, VkQueue* cmdQueues, uint32_t* cmdQueuesFamily, uint32_t nodeCount, const VulkanFunctions* functions);
void InitDevice(VkInstance instance, VkDevice* devices, VkPhysicalDevice* physicalDevices, VkQueue* cmdQueues, uint32_t* cmdQueuesFamily, uint32_t nodeCount, const VulkanFunctions* functions);
void QueryTimestamp(VkCommandBuffer commandBuffer, int64_t* outCpuTimestamp);
void Flip(VkSwapchainKHR swapChain);
@ -94,10 +94,10 @@ namespace Optick
}
};
void InitGpuVulkan(VkDevice* vkDevices, VkPhysicalDevice* vkPhysicalDevices, VkQueue* vkQueues, uint32_t* cmdQueuesFamily, uint32_t numQueues, const VulkanFunctions* functions)
void InitGpuVulkan(VkInstance vkInstance, VkDevice* vkDevices, VkPhysicalDevice* vkPhysicalDevices, VkQueue* vkQueues, uint32_t* cmdQueuesFamily, uint32_t numQueues, const VulkanFunctions* functions)
{
GPUProfilerVulkan* gpuProfiler = Memory::New<GPUProfilerVulkan>();
gpuProfiler->InitDevice(vkDevices, vkPhysicalDevices, vkQueues, cmdQueuesFamily, numQueues, functions);
gpuProfiler->InitDevice(vkInstance, vkDevices, vkPhysicalDevices, vkQueues, cmdQueuesFamily, numQueues, functions);
Core::Get().InitGPUProfiler(gpuProfiler);
}
@ -107,15 +107,17 @@ namespace Optick
prevPresentID = 0;
}
void GPUProfilerVulkan::InitDevice(VkDevice* devices, VkPhysicalDevice* physicalDevices, VkQueue* cmdQueues, uint32_t* cmdQueuesFamily, uint32_t nodeCount, const VulkanFunctions* functions)
void GPUProfilerVulkan::InitDevice(VkInstance instance, VkDevice* devices, VkPhysicalDevice* physicalDevices, VkQueue* cmdQueues, uint32_t* cmdQueuesFamily, uint32_t nodeCount, const VulkanFunctions* functions)
{
if (functions != nullptr)
{
vulkanFunctions = *functions;
}
else
else
{
#if OPTICK_STATIC_VULKAN_FUNCTIONS
vulkanFunctions = {
nullptr, // don't define vkGetInstanceProcAddr if vulkan functions are static
vkGetPhysicalDeviceProperties,
(PFN_vkCreateQueryPool_)vkCreateQueryPool,
(PFN_vkCreateCommandPool_)vkCreateCommandPool,
@ -142,6 +144,23 @@ namespace Optick
vkFreeCommandBuffers,
nullptr, // dynamically define vkGetPastPresentationTimingGOOGLE if VK_GOOGLE_display_timing extension available
};
#else
OPTICK_FAILED("Either set OPTICK_STATIC_VULKAN_FUNCTIONS = 1 or VulkanFunctions must be defined! Can't initialize GPU Profiler!");
#endif
}
PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr_ = nullptr;
if (vulkanFunctions.vkGetInstanceProcAddr)
{
if (instance)
{
vkGetDeviceProcAddr_ = (PFN_vkGetDeviceProcAddr)(*vulkanFunctions.vkGetInstanceProcAddr)(instance, "vkGetDeviceProcAddr");
vulkanFunctions.vkGetPhysicalDeviceProperties = (PFN_vkGetPhysicalDeviceProperties_)(*vulkanFunctions.vkGetInstanceProcAddr)(instance, "vkGetPhysicalDeviceProperties");
}
else
{
OPTICK_FAILED("VkInstance must be defined if VulkanFunctions::vkGetInstanceProcAddr is defined! Can't initialize GPU Profiler!");
}
}
VkQueryPoolCreateInfo queryPoolCreateInfo;
@ -168,6 +187,40 @@ namespace Optick
VkResult r;
for (uint32_t i = 0; i < nodeCount; ++i)
{
if (vkGetDeviceProcAddr_)
{
vulkanFunctions.vkCreateQueryPool = (PFN_vkCreateQueryPool_)vkGetDeviceProcAddr_(devices[i], "vkCreateQueryPool");
vulkanFunctions.vkCreateCommandPool = (PFN_vkCreateCommandPool_)vkGetDeviceProcAddr_(devices[i], "vkCreateCommandPool");
vulkanFunctions.vkCreateEvent = (PFN_vkCreateEvent_)vkGetDeviceProcAddr_(devices[i], "vkCreateEvent");
vulkanFunctions.vkAllocateCommandBuffers = (PFN_vkAllocateCommandBuffers_)vkGetDeviceProcAddr_(devices[i], "vkAllocateCommandBuffers");
vulkanFunctions.vkCreateFence = (PFN_vkCreateFence_)vkGetDeviceProcAddr_(devices[i], "vkCreateFence");
vulkanFunctions.vkCmdResetQueryPool = (PFN_vkCmdResetQueryPool_)vkGetDeviceProcAddr_(devices[i], "vkCmdResetQueryPool");
vulkanFunctions.vkResetQueryPool = (PFN_vkResetQueryPool_)vkGetDeviceProcAddr_(devices[i], "vkResetQueryPool");
vulkanFunctions.vkCmdWaitEvents = (PFN_vkCmdWaitEvents_)vkGetDeviceProcAddr_(devices[i], "vkCmdWaitEvents");
vulkanFunctions.vkResetEvent = (PFN_vkResetEvent_)vkGetDeviceProcAddr_(devices[i], "vkResetEvent");
vulkanFunctions.vkSetEvent = (PFN_vkSetEvent_)vkGetDeviceProcAddr_(devices[i], "vkSetEvent");
vulkanFunctions.vkQueueSubmit = (PFN_vkQueueSubmit_)vkGetDeviceProcAddr_(devices[i], "vkQueueSubmit");
vulkanFunctions.vkWaitForFences = (PFN_vkWaitForFences_)vkGetDeviceProcAddr_(devices[i], "vkWaitForFences");
vulkanFunctions.vkResetCommandBuffer = (PFN_vkResetCommandBuffer_)vkGetDeviceProcAddr_(devices[i], "vkResetCommandBuffer");
vulkanFunctions.vkCmdWriteTimestamp = (PFN_vkCmdWriteTimestamp_)vkGetDeviceProcAddr_(devices[i], "vkCmdWriteTimestamp");
vulkanFunctions.vkGetQueryPoolResults = (PFN_vkGetQueryPoolResults_)vkGetDeviceProcAddr_(devices[i], "vkGetQueryPoolResults");
vulkanFunctions.vkBeginCommandBuffer = (PFN_vkBeginCommandBuffer_)vkGetDeviceProcAddr_(devices[i], "vkBeginCommandBuffer");
vulkanFunctions.vkEndCommandBuffer = (PFN_vkEndCommandBuffer_)vkGetDeviceProcAddr_(devices[i], "vkEndCommandBuffer");
vulkanFunctions.vkResetFences = (PFN_vkResetFences_)vkGetDeviceProcAddr_(devices[i], "vkResetFences");
vulkanFunctions.vkDestroyCommandPool = (PFN_vkDestroyCommandPool_)vkGetDeviceProcAddr_(devices[i], "vkDestroyCommandPool");
vulkanFunctions.vkDestroyQueryPool = (PFN_vkDestroyQueryPool_)vkGetDeviceProcAddr_(devices[i], "vkDestroyQueryPool");
vulkanFunctions.vkDestroyEvent = (PFN_vkDestroyEvent_)vkGetDeviceProcAddr_(devices[i], "vkDestroyEvent");
vulkanFunctions.vkDestroyFence = (PFN_vkDestroyFence_)vkGetDeviceProcAddr_(devices[i], "vkDestroyFence");
vulkanFunctions.vkFreeCommandBuffers = (PFN_vkFreeCommandBuffers_)vkGetDeviceProcAddr_(devices[i], "vkFreeCommandBuffers");
vulkanFunctions.vkGetPastPresentationTimingGOOGLE = (PFN_vkGetPastPresentationTimingGOOGLE_)vkGetDeviceProcAddr_(devices[i], "vkGetPastPresentationTimingGOOGLE");
}
#if OPTICK_STATIC_VULKAN_FUNCTIONS
else if (!vulkanFunctions.vkGetPastPresentationTimingGOOGLE)
{
vulkanFunctions.vkGetPastPresentationTimingGOOGLE = (PFN_vkGetPastPresentationTimingGOOGLE_)vkGetDeviceProcAddr(devices[i], "vkGetPastPresentationTimingGOOGLE");
}
#endif
VkPhysicalDeviceProperties properties = { 0 };
(*vulkanFunctions.vkGetPhysicalDeviceProperties)(physicalDevices[i], &properties);
GPUProfiler::InitNode(properties.deviceName, i);
@ -175,7 +228,6 @@ namespace Optick
NodePayload* nodePayload = Memory::New<NodePayload>();
nodePayloads[i] = nodePayload;
nodePayload->vulkanFunctions = &vulkanFunctions;
nodePayload->vulkanFunctions->vkGetPastPresentationTimingGOOGLE = (PFN_vkGetPastPresentationTimingGOOGLE_)vkGetDeviceProcAddr(devices[i], "vkGetPastPresentationTimingGOOGLE");
nodePayload->device = devices[i];
nodePayload->physicalDevice = physicalDevices[i];
nodePayload->queue = cmdQueues[i];
@ -498,7 +550,7 @@ namespace Optick
#include "optick_common.h"
namespace Optick
{
void InitGpuVulkan(VkDevice* /*vkDevices*/, VkPhysicalDevice* /*vkPhysicalDevices*/, VkQueue* /*vkQueues*/, uint32_t* /*cmdQueuesFamily*/, uint32_t /*numQueues*/, const VulkanFunctions* /*functions*/)
void InitGpuVulkan(VkInstance /*vkInstance*/, VkDevice* /*vkDevices*/, VkPhysicalDevice* /*vkPhysicalDevices*/, VkQueue* /*vkQueues*/, uint32_t* /*cmdQueuesFamily*/, uint32_t /*numQueues*/, const VulkanFunctions* /*functions*/)
{
OPTICK_FAILED("OPTICK_ENABLE_GPU_VULKAN is disabled! Can't initialize GPU Profiler!");
}

View file

@ -289,7 +289,7 @@ Server::Server(short port) : socket(Memory::New<Socket>()), saveCb(nullptr)
{
if (!socket->Bind(port, 4))
{
OPTICK_FAILED("Failed to bind a socket! Most probably the port is blocked by anti-virus! Change the port and verify that your game has enough permissions to communicate over the TCP\IP.");
OPTICK_FAILED("Failed to bind a socket! Most probably the port is blocked by anti-virus! Change the port and verify that your game has enough permissions to communicate over the TCP/IP.");
}
else
{
@ -499,4 +499,4 @@ Server & Server::Get()
}
#endif //USE_OPTICK
#endif //USE_OPTICK

View file

@ -44,7 +44,7 @@ static const char* MD5_SnapshotName = "_MD5_Snapshot_";
static const byte MD5B_VERSION = 106;
static const unsigned int MD5B_MAGIC = ( '5' << 24 ) | ( 'D' << 16 ) | ( 'M' << 8 ) | MD5B_VERSION;
idCVar r_useGPUSkinning( "r_useGPUSkinning", "1", CVAR_INTEGER, "animate normals and tangents instead of deriving" );
idCVar r_useGPUSkinning( "r_useGPUSkinning", "1", CVAR_INTEGER | CVAR_NOCHEAT, "animate normals and tangents instead of deriving" );
/***********************************************************************

View file

@ -1791,11 +1791,12 @@ void idRenderBackend::GL_EndFrame()
commandList->close();
deviceManager->GetDevice()->executeCommandList( commandList );
// required for Vulkan: transition our swap image to present
deviceManager->EndFrame();
// SRS - execute after EndFrame() to avoid need for barrier command list on Vulkan
deviceManager->GetDevice()->executeCommandList( commandList );
// update jitter for perspective matrix
taaPass->AdvanceFrame();
}

View file

@ -5386,12 +5386,13 @@ void idRenderBackend::ExecuteBackEndCommands( const emptyCommand_t* cmds )
DrawFlickerBox();
GL_EndFrame();
// stop rendering on this thread
uint64 backEndFinishTime = Sys_Microseconds();
pc.cpuTotalMicroSec = backEndFinishTime - backEndStartTime;
// SRS - capture backend timing before GL_EndFrame() since it can block when r_mvkSynchronousQueueSubmits is enabled on macOS/MoltenVK
GL_EndFrame();
if( r_debugRenderToTexture.GetInteger() == 1 )
{
common->Printf( "3d: %i, 2d: %i, SetBuf: %i, CpyRenders: %i, CpyFrameBuf: %i\n", c_draw3d, c_draw2d, c_setBuffers, c_copyRenders, pc.c_copyFrameBuffer );

View file

@ -27,6 +27,7 @@
#include "renderer/RenderCommon.h"
#include "renderer/RenderSystem.h"
#include "framework/Common_local.h"
#include <sys/DeviceManager.h>
#include <Windows.h>
@ -57,7 +58,7 @@ class DeviceManager_DX12 : public DeviceManager
RefCountPtr<IDXGISwapChain3> m_SwapChain;
DXGI_SWAP_CHAIN_DESC1 m_SwapChainDesc{};
DXGI_SWAP_CHAIN_FULLSCREEN_DESC m_FullScreenDesc{};
RefCountPtr<IDXGIAdapter> m_DxgiAdapter;
RefCountPtr<IDXGIAdapter3> m_DxgiAdapter;
bool m_TearingSupported = false;
std::vector<RefCountPtr<ID3D12Resource>> m_SwapChainBuffers;
@ -388,7 +389,7 @@ bool DeviceManager_DX12::CreateDeviceAndSwapChain()
}
}
m_DxgiAdapter = targetAdapter;
targetAdapter->QueryInterface( IID_PPV_ARGS( &m_DxgiAdapter ) );
D3D12_COMMAND_QUEUE_DESC queueDesc;
ZeroMemory( &queueDesc, sizeof( queueDesc ) );
@ -566,6 +567,12 @@ void DeviceManager_DX12::ResizeSwapChain()
void DeviceManager_DX12::BeginFrame()
{
OPTICK_CATEGORY( "DX12_BeginFrame", Optick::Category::Wait );
// SRS - get DXGI GPU memory usage for display in statistics overlay HUD
DXGI_QUERY_VIDEO_MEMORY_INFO memoryInfoLocal = {}, memoryInfoNonLocal = {};
m_DxgiAdapter->QueryVideoMemoryInfo( 0, DXGI_MEMORY_SEGMENT_GROUP_LOCAL, &memoryInfoLocal );
m_DxgiAdapter->QueryVideoMemoryInfo( 0, DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL, &memoryInfoNonLocal );
commonLocal.SetRendererGpuMemoryMB( int( ( memoryInfoLocal.CurrentUsage + memoryInfoNonLocal.CurrentUsage ) / 1024 / 1024 ) );
}
nvrhi::ITexture* DeviceManager_DX12::GetCurrentBackBuffer()

View file

@ -32,16 +32,27 @@
#include <unordered_set>
#include "renderer/RenderCommon.h"
#include "framework/Common_local.h"
#include <sys/DeviceManager.h>
#include <nvrhi/vulkan.h>
// SRS - optionally needed for MoltenVK runtime config visibility
#if defined(__APPLE__) && defined( USE_MoltenVK )
#include <MoltenVK/vk_mvk_moltenvk.h>
#if defined(__APPLE__)
#if defined( USE_MoltenVK )
#if 0
#include <MoltenVK/mvk_vulkan.h>
#include <MoltenVK/mvk_config.h> // SRS - will eventually move to these mvk include files for MoltenVK >= 1.2.7 / SDK >= 1.3.275.0
#else
#include <MoltenVK/vk_mvk_moltenvk.h> // SRS - now deprecated, but provides backwards compatibility for MoltenVK < 1.2.7 / SDK < 1.3.275.0
#endif
#endif
#if defined( VK_EXT_layer_settings ) || defined( USE_MoltenVK )
idCVar r_mvkSynchronousQueueSubmits( "r_mvkSynchronousQueueSubmits", "0", CVAR_BOOL | CVAR_INIT, "Use MoltenVK's synchronous queue submit option." );
idCVar r_mvkUseMetalArgumentBuffers( "r_mvkUseMetalArgumentBuffers", "2", CVAR_INTEGER | CVAR_INIT, "Use MoltenVK's Metal argument buffers option (0=Off, 1=Always On, 2=On when VK_EXT_descriptor_indexing enabled)", 0, 2 );
#endif
#endif
#include <nvrhi/validation.h>
#include <libs/optick/optick.h>
#if defined( USE_AMD_ALLOCATOR )
#define VMA_IMPLEMENTATION
@ -201,20 +212,21 @@ private:
{
// instance
{
#if defined(__APPLE__) && defined( VK_KHR_portability_enumeration )
#if defined(__APPLE__)
#if defined( VK_KHR_portability_enumeration )
// SRS - This is optional since it only became manadatory with Vulkan SDK 1.3.216.0 or later
VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME,
#endif
#if defined( VK_EXT_layer_settings )
// SRS - This is optional since implemented only for MoltenVK 1.2.7 / SDK 1.3.275.0 or later
VK_EXT_LAYER_SETTINGS_EXTENSION_NAME,
#endif
#endif
VK_EXT_SAMPLER_FILTER_MINMAX_EXTENSION_NAME,
VK_EXT_DEBUG_UTILS_EXTENSION_NAME
},
// layers
{
#if defined(__APPLE__)
// SRS - synchronization2 not supported natively on MoltenVK, use layer implementation instead
"VK_LAYER_KHRONOS_synchronization2"
#endif
},
{ },
// device
{
VK_EXT_DEBUG_MARKER_EXTENSION_NAME,
@ -225,7 +237,11 @@ private:
#if USE_OPTICK
VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME,
#endif
VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME
#if defined( VK_KHR_format_feature_flags2 )
VK_KHR_FORMAT_FEATURE_FLAGS_2_EXTENSION_NAME,
#endif
VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME,
VK_EXT_MEMORY_BUDGET_EXTENSION_NAME
},
};
@ -272,7 +288,7 @@ private:
nvrhi::vulkan::DeviceHandle m_NvrhiDevice;
nvrhi::DeviceHandle m_ValidationLayer;
nvrhi::CommandListHandle m_BarrierCommandList;
//nvrhi::CommandListHandle m_BarrierCommandList; // SRS - no longer needed
std::queue<vk::Semaphore> m_PresentSemaphoreQueue;
vk::Semaphore m_PresentSemaphore;
@ -286,6 +302,19 @@ private:
// SRS - flag indicating support for presentation timing via VK_GOOGLE_display_timing extension
bool displayTimingEnabled = false;
// SRS - slot for Vulkan device API version at runtime (initialize to Vulkan build version)
uint32_t m_DeviceApiVersion = VK_HEADER_VERSION_COMPLETE;
// SRS - function pointer for initing Vulkan DynamicLoader, VMA, Optick, and MoltenVK functions
PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = nullptr;
#if defined(__APPLE__) && defined( USE_MoltenVK )
#if MVK_VERSION >= MVK_MAKE_VERSION( 1, 2, 6 )
// SRS - function pointer for retrieving MoltenVK advanced performance statistics
PFN_vkGetPerformanceStatisticsMVK vkGetPerformanceStatisticsMVK = nullptr;
#endif
#endif
private:
static VKAPI_ATTR VkBool32 VKAPI_CALL vulkanDebugCallback(
VkDebugReportFlagsEXT flags,
@ -426,6 +455,7 @@ bool DeviceManager_VK::createInstance()
std::unordered_set<std::string> requiredLayers = enabledExtensions.layers;
auto instanceVersion = vk::enumerateInstanceVersion();
for( const auto& layer : vk::enumerateInstanceLayerProperties() )
{
const std::string name = layer.layerName;
@ -433,6 +463,13 @@ bool DeviceManager_VK::createInstance()
{
enabledExtensions.layers.insert( name );
}
#if defined(__APPLE__) && !defined( USE_MoltenVK )
// SRS - Vulkan SDK < 1.3.268.1 does not have native VK_KHR_synchronization2 support on macOS, add Khronos layer to emulate
else if( name == "VK_LAYER_KHRONOS_synchronization2" && instanceVersion < VK_MAKE_API_VERSION( 0, 1, 3, 268 ) )
{
enabledExtensions.layers.insert( name );
}
#endif
requiredLayers.erase( name );
}
@ -470,11 +507,68 @@ bool DeviceManager_VK::createInstance()
.setPpEnabledExtensionNames( instanceExtVec.data() )
.setPApplicationInfo( &applicationInfo );
#if defined(__APPLE__) && defined( VK_KHR_portability_enumeration )
#if defined(__APPLE__)
#if defined( VK_KHR_portability_enumeration )
if( enabledExtensions.instance.find( VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME ) != enabledExtensions.instance.end() )
{
info.setFlags( vk::InstanceCreateFlagBits( VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR ) );
}
#endif
#if defined( VK_EXT_layer_settings )
// SRS - set MoltenVK runtime configuration parameters on macOS via standardized VK_EXT_layer_settings extension
std::vector<vk::LayerSettingEXT> layerSettings;
vk::LayerSettingsCreateInfoEXT layerSettingsCreateInfo;
const vk::Bool32 valueTrue = vk::True, valueFalse = vk::False;
const int32_t useMetalArgumentBuffers = r_mvkUseMetalArgumentBuffers.GetInteger();
const float timestampPeriodLowPassAlpha = 1.0;
if( enabledExtensions.instance.find( VK_EXT_LAYER_SETTINGS_EXTENSION_NAME ) != enabledExtensions.instance.end() )
{
// SRS - use MoltenVK layer for configuration via VK_EXT_layer_settings extension
vk::LayerSettingEXT layerSetting = { "MoltenVK", "", vk::LayerSettingTypeEXT( 0 ), 1, nullptr };
// SRS - Set MoltenVK's synchronous queue submit option for vkQueueSubmit() & vkQueuePresentKHR()
layerSetting.pSettingName = "MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS";
layerSetting.type = vk::LayerSettingTypeEXT::eBool32;
layerSetting.pValues = r_mvkSynchronousQueueSubmits.GetBool() ? &valueTrue : &valueFalse;
layerSettings.push_back( layerSetting );
// SRS - Enable MoltenVK's image view swizzle feature in case we don't have native image view swizzle
layerSetting.pSettingName = "MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE";
layerSetting.type = vk::LayerSettingTypeEXT::eBool32;
layerSetting.pValues = &valueTrue;
layerSettings.push_back( layerSetting );
// SRS - Turn MoltenVK's Metal argument buffer feature on for descriptor indexing only
layerSetting.pSettingName = "MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS";
layerSetting.type = vk::LayerSettingTypeEXT::eInt32;
layerSetting.pValues = &useMetalArgumentBuffers;
layerSettings.push_back( layerSetting );
// SRS - Disable MoltenVK's timestampPeriod filter for HUD / Optick profiler timing calibration
layerSetting.pSettingName = "MVK_CONFIG_TIMESTAMP_PERIOD_LOWPASS_ALPHA";
layerSetting.type = vk::LayerSettingTypeEXT::eFloat32;
layerSetting.pValues = &timestampPeriodLowPassAlpha;
layerSettings.push_back( layerSetting );
// SRS - Only enable MoltenVK performance tracking if using API and available based on version
#if defined( USE_MoltenVK )
#if MVK_VERSION >= MVK_MAKE_VERSION( 1, 2, 6 )
// SRS - Enable MoltenVK's performance tracking for display of Metal encoding timer on macOS
layerSetting.pSettingName = "MVK_CONFIG_PERFORMANCE_TRACKING";
layerSetting.type = vk::LayerSettingTypeEXT::eBool32;
layerSetting.pValues = &valueTrue;
layerSettings.push_back( layerSetting );
#endif
#endif
layerSettingsCreateInfo.settingCount = uint32_t( layerSettings.size() );
layerSettingsCreateInfo.pSettings = layerSettings.data();
info.setPNext( &layerSettingsCreateInfo );
}
#endif
#endif
const vk::Result res = vk::createInstance( &info, nullptr, &m_VulkanInstance );
@ -820,10 +914,17 @@ bool DeviceManager_VK::createDevice()
auto meshletFeatures = vk::PhysicalDeviceMeshShaderFeaturesNV()
.setTaskShader( true )
.setMeshShader( true );
// SRS - get/set shading rate features which are detected individually by nvrhi (not just at extension level)
vk::PhysicalDeviceFeatures2 actualDeviceFeatures2;
vk::PhysicalDeviceFragmentShadingRateFeaturesKHR fragmentShadingRateFeatures;
actualDeviceFeatures2.pNext = &fragmentShadingRateFeatures;
m_VulkanPhysicalDevice.getFeatures2( &actualDeviceFeatures2 );
auto vrsFeatures = vk::PhysicalDeviceFragmentShadingRateFeaturesKHR()
.setPipelineFragmentShadingRate( true )
.setPrimitiveFragmentShadingRate( true )
.setAttachmentFragmentShadingRate( true );
.setPipelineFragmentShadingRate( fragmentShadingRateFeatures.pipelineFragmentShadingRate )
.setPrimitiveFragmentShadingRate( fragmentShadingRateFeatures.primitiveFragmentShadingRate )
.setAttachmentFragmentShadingRate( fragmentShadingRateFeatures.attachmentFragmentShadingRate );
auto sync2Features = vk::PhysicalDeviceSynchronization2FeaturesKHR()
.setSynchronization2( true );
@ -851,7 +952,7 @@ bool DeviceManager_VK::createDevice()
auto deviceFeatures = vk::PhysicalDeviceFeatures()
.setShaderImageGatherExtended( true )
.setShaderStorageImageReadWithoutFormat( true )
.setShaderStorageImageReadWithoutFormat( actualDeviceFeatures2.features.shaderStorageImageReadWithoutFormat )
.setSamplerAnisotropy( true )
.setTessellationShader( true )
.setTextureCompressionBC( true )
@ -924,15 +1025,16 @@ bool DeviceManager_VK::createDevice()
enablePModeImmediate = find( surfacePModes.begin(), surfacePModes.end(), vk::PresentModeKHR::eImmediate ) != surfacePModes.end();
enablePModeFifoRelaxed = find( surfacePModes.begin(), surfacePModes.end(), vk::PresentModeKHR::eFifoRelaxed ) != surfacePModes.end();
// stash the renderer string
// stash the device renderer string and api version
auto prop = m_VulkanPhysicalDevice.getProperties();
m_RendererString = std::string( prop.deviceName.data() );
m_DeviceApiVersion = prop.apiVersion;
#if defined( USE_AMD_ALLOCATOR )
// SRS - initialize the vma allocator
VmaVulkanFunctions vulkanFunctions = {};
vulkanFunctions.vkGetInstanceProcAddr = &vkGetInstanceProcAddr;
vulkanFunctions.vkGetDeviceProcAddr = &vkGetDeviceProcAddr;
vulkanFunctions.vkGetInstanceProcAddr = vkGetInstanceProcAddr;
vulkanFunctions.vkGetDeviceProcAddr = (PFN_vkGetDeviceProcAddr)vkGetInstanceProcAddr( m_VulkanInstance, "vkGetDeviceProcAddr" );
VmaAllocatorCreateInfo allocatorCreateInfo = {};
allocatorCreateInfo.vulkanApiVersion = VK_API_VERSION_1_2;
@ -1102,7 +1204,6 @@ bool DeviceManager_VK::CreateDeviceAndSwapChain()
{
enabledExtensions.instance.insert( VK_EXT_DEBUG_REPORT_EXTENSION_NAME );
#if defined(__APPLE__) && defined( USE_MoltenVK )
enabledExtensions.layers.insert( "MoltenVK" );
}
// SRS - when USE_MoltenVK defined, load libMoltenVK vs. the default libvulkan
@ -1114,8 +1215,7 @@ bool DeviceManager_VK::CreateDeviceAndSwapChain()
// SRS - make static so ~DynamicLoader() does not prematurely unload vulkan dynamic lib
static const vk::DynamicLoader dl;
#endif
const PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = // NOLINT(misc-misplaced-const)
dl.getProcAddress<PFN_vkGetInstanceProcAddr>( "vkGetInstanceProcAddr" );
vkGetInstanceProcAddr = dl.getProcAddress<PFN_vkGetInstanceProcAddr>( "vkGetInstanceProcAddr" );
VULKAN_HPP_DEFAULT_DISPATCHER.init( vkGetInstanceProcAddr );
#define CHECK(a) if (!(a)) { return false; }
@ -1150,37 +1250,70 @@ bool DeviceManager_VK::CreateDeviceAndSwapChain()
CHECK( pickPhysicalDevice() );
CHECK( findQueueFamilies( m_VulkanPhysicalDevice, m_WindowSurface ) );
// SRS - when USE_MoltenVK defined, set MoltenVK runtime configuration parameters on macOS
// SRS - when USE_MoltenVK defined, set MoltenVK runtime configuration parameters on macOS (deprecated version)
#if defined(__APPLE__) && defined( USE_MoltenVK )
vk::PhysicalDeviceFeatures2 deviceFeatures2;
vk::PhysicalDevicePortabilitySubsetFeaturesKHR portabilityFeatures;
deviceFeatures2.setPNext( &portabilityFeatures );
m_VulkanPhysicalDevice.getFeatures2( &deviceFeatures2 );
MVKConfiguration pConfig;
size_t pConfigSize = sizeof( pConfig );
vkGetMoltenVKConfigurationMVK( m_VulkanInstance, &pConfig, &pConfigSize );
// SRS - Set MoltenVK's synchronous queue submit option for vkQueueSubmit() & vkQueuePresentKHR()
pConfig.synchronousQueueSubmits = r_mvkSynchronousQueueSubmits.GetBool() ? VK_TRUE : VK_FALSE;
vkSetMoltenVKConfigurationMVK( m_VulkanInstance, &pConfig, &pConfigSize );
// SRS - If we don't have native image view swizzle, enable MoltenVK's image view swizzle feature
if( portabilityFeatures.imageViewFormatSwizzle == VK_FALSE )
#if defined( VK_EXT_layer_settings )
// SRS - for backwards compatibility at runtime: execute only if we can't find the VK_EXT_layer_settings extension
if( enabledExtensions.instance.find( VK_EXT_LAYER_SETTINGS_EXTENSION_NAME ) == enabledExtensions.instance.end() )
#endif
{
idLib::Printf( "Enabling MoltenVK's image view swizzle...\n" );
pConfig.fullImageViewSwizzle = VK_TRUE;
vkSetMoltenVKConfigurationMVK( m_VulkanInstance, &pConfig, &pConfigSize );
// SRS - vkSetMoltenVKConfigurationMVK() now deprecated, but retained for MoltenVK < 1.2.7 / SDK < 1.3.275.0
const PFN_vkGetMoltenVKConfigurationMVK vkGetMoltenVKConfigurationMVK = // NOLINT(misc-misplaced-const)
(PFN_vkGetMoltenVKConfigurationMVK)vkGetInstanceProcAddr( m_VulkanInstance, "vkGetMoltenVKConfigurationMVK" );
const PFN_vkSetMoltenVKConfigurationMVK vkSetMoltenVKConfigurationMVK = // NOLINT(misc-misplaced-const)
(PFN_vkSetMoltenVKConfigurationMVK)vkGetInstanceProcAddr( m_VulkanInstance, "vkSetMoltenVKConfigurationMVK" );
vk::PhysicalDeviceFeatures2 deviceFeatures2;
vk::PhysicalDevicePortabilitySubsetFeaturesKHR portabilityFeatures;
deviceFeatures2.setPNext( &portabilityFeatures );
m_VulkanPhysicalDevice.getFeatures2( &deviceFeatures2 );
MVKConfiguration mvkConfig;
size_t mvkConfigSize = sizeof( mvkConfig );
vkGetMoltenVKConfigurationMVK( m_VulkanInstance, &mvkConfig, &mvkConfigSize );
// SRS - Set MoltenVK's synchronous queue submit option for vkQueueSubmit() & vkQueuePresentKHR()
if( mvkConfig.synchronousQueueSubmits == VK_TRUE && !r_mvkSynchronousQueueSubmits.GetBool() )
{
idLib::Printf( "Disabled MoltenVK's synchronous queue submits...\n" );
mvkConfig.synchronousQueueSubmits = VK_FALSE;
}
// SRS - If we don't have native image view swizzle, enable MoltenVK's image view swizzle feature
if( portabilityFeatures.imageViewFormatSwizzle == VK_FALSE )
{
idLib::Printf( "Enabled MoltenVK's image view swizzle...\n" );
mvkConfig.fullImageViewSwizzle = VK_TRUE;
}
// SRS - Set MoltenVK's Metal argument buffer option for descriptor resource scaling
// - Also needed for Vulkan SDK 1.3.268.1 to work around SPIRV-Cross issue for Metal conversion.
// - See https://github.com/KhronosGroup/MoltenVK/issues/2016 and https://github.com/goki/vgpu/issues/9
// - Issue solved in Vulkan SDK >= 1.3.275.0, but config uses VK_EXT_layer_settings instead of this code.
if( mvkConfig.useMetalArgumentBuffers == MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS_NEVER && r_mvkUseMetalArgumentBuffers.GetInteger() )
{
idLib::Printf( "Enabled MoltenVK's Metal argument buffers...\n" );
mvkConfig.useMetalArgumentBuffers = MVKUseMetalArgumentBuffers( r_mvkUseMetalArgumentBuffers.GetInteger() );
}
#if MVK_VERSION >= MVK_MAKE_VERSION( 1, 2, 6 )
if( mvkConfig.apiVersionToAdvertise >= VK_MAKE_API_VERSION( 0, 1, 2, 268 ) )
{
// SRS - Disable MoltenVK's timestampPeriod filter for HUD / Optick profiler timing calibration
mvkConfig.timestampPeriodLowPassAlpha = 1.0;
// SRS - Enable MoltenVK's performance tracking for display of Metal encoding timer on macOS
mvkConfig.performanceTracking = VK_TRUE;
}
#endif
vkSetMoltenVKConfigurationMVK( m_VulkanInstance, &mvkConfig, &mvkConfigSize );
}
// SRS - Turn MoltenVK's Metal argument buffer feature on for descriptor indexing only
if( pConfig.useMetalArgumentBuffers == MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS_NEVER )
{
idLib::Printf( "Enabling MoltenVK's Metal argument buffers for descriptor indexing...\n" );
pConfig.useMetalArgumentBuffers = MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS_DESCRIPTOR_INDEXING;
vkSetMoltenVKConfigurationMVK( m_VulkanInstance, &pConfig, &pConfigSize );
}
#if MVK_VERSION >= MVK_MAKE_VERSION( 1, 2, 6 )
// SRS - Get function pointer for retrieving MoltenVK advanced performance statistics in DeviceManager_VK::BeginFrame()
vkGetPerformanceStatisticsMVK = (PFN_vkGetPerformanceStatisticsMVK)vkGetInstanceProcAddr( m_VulkanInstance, "vkGetPerformanceStatisticsMVK" );
#endif
#endif
CHECK( createDevice() );
@ -1220,7 +1353,7 @@ bool DeviceManager_VK::CreateDeviceAndSwapChain()
CHECK( createSwapChain() );
m_BarrierCommandList = m_NvrhiDevice->createCommandList();
//m_BarrierCommandList = m_NvrhiDevice->createCommandList(); // SRS - no longer needed
// SRS - Give each swapchain image its own semaphore in case of overlap (e.g. MoltenVK async queue submit)
for( int i = 0; i < m_SwapChainImages.size(); i++ )
@ -1234,7 +1367,11 @@ bool DeviceManager_VK::CreateDeviceAndSwapChain()
#undef CHECK
OPTICK_GPU_INIT_VULKAN( ( VkDevice* )&m_VulkanDevice, ( VkPhysicalDevice* )&m_VulkanPhysicalDevice, ( VkQueue* )&m_GraphicsQueue, ( uint32_t* )&m_GraphicsQueueFamily, 1, nullptr );
#if USE_OPTICK
const Optick::VulkanFunctions optickVulkanFunctions = { (PFN_vkGetInstanceProcAddr_)vkGetInstanceProcAddr };
#endif
OPTICK_GPU_INIT_VULKAN( ( VkInstance )m_VulkanInstance, ( VkDevice* )&m_VulkanDevice, ( VkPhysicalDevice* )&m_VulkanPhysicalDevice, ( VkQueue* )&m_GraphicsQueue, ( uint32_t* )&m_GraphicsQueueFamily, 1, &optickVulkanFunctions );
return true;
}
@ -1257,7 +1394,7 @@ void DeviceManager_VK::DestroyDeviceAndSwapChain()
}
m_PresentSemaphore = vk::Semaphore();
m_BarrierCommandList = nullptr;
//m_BarrierCommandList = nullptr; // SRS - no longer needed
destroySwapChain();
@ -1305,6 +1442,43 @@ void DeviceManager_VK::DestroyDeviceAndSwapChain()
void DeviceManager_VK::BeginFrame()
{
OPTICK_CATEGORY( "Vulkan_BeginFrame", Optick::Category::Wait );
#if defined(__APPLE__) && defined( USE_MoltenVK )
#if MVK_VERSION >= MVK_MAKE_VERSION( 1, 2, 6 )
if( vkGetPerformanceStatisticsMVK && m_DeviceApiVersion >= VK_MAKE_API_VERSION( 0, 1, 2, 268 ) )
{
// SRS - get MoltenVK's Metal encoding time and GPU memory usage for display in statistics overlay HUD
MVKPerformanceStatistics mvkPerfStats;
size_t mvkPerfStatsSize = sizeof( mvkPerfStats );
vkGetPerformanceStatisticsMVK( m_VulkanDevice, &mvkPerfStats, &mvkPerfStatsSize );
commonLocal.SetRendererMvkEncodeMicroseconds( uint64( Max( 0.0, mvkPerfStats.queue.submitCommandBuffers.latest - mvkPerfStats.queue.retrieveCAMetalDrawable.latest ) * 1000.0 ) );
commonLocal.SetRendererGpuMemoryMB( int( mvkPerfStats.device.gpuMemoryAllocated.latest / 1024.0 ) );
}
else
#endif
#endif
{
// SRS - get Vulkan GPU memory usage for display in statistics overlay HUD
vk::PhysicalDeviceMemoryProperties2 memoryProperties2;
vk::PhysicalDeviceMemoryBudgetPropertiesEXT memoryBudget;
memoryProperties2.pNext = &memoryBudget;
m_VulkanPhysicalDevice.getMemoryProperties2( &memoryProperties2 );
VkDeviceSize gpuMemoryAllocated = 0;
for( uint32_t i = 0; i < memoryProperties2.memoryProperties.memoryHeapCount; i++ )
{
gpuMemoryAllocated += memoryBudget.heapUsage[i];
#if defined(__APPLE__)
// SRS - macOS Vulkan API <= 1.2.268 has heap reporting defect, use heapUsage[0] only
if( m_DeviceApiVersion <= VK_MAKE_API_VERSION( 0, 1, 2, 268 ) )
break;
#endif
}
commonLocal.SetRendererGpuMemoryMB( int( gpuMemoryAllocated / 1024 / 1024 ) );
}
const vk::Result res = m_VulkanDevice.acquireNextImageKHR( m_SwapChain,
std::numeric_limits<uint64_t>::max(), // timeout
m_PresentSemaphore,
@ -1320,9 +1494,10 @@ void DeviceManager_VK::EndFrame()
{
m_NvrhiDevice->queueSignalSemaphore( nvrhi::CommandQueue::Graphics, m_PresentSemaphore, 0 );
m_BarrierCommandList->open(); // umm...
m_BarrierCommandList->close();
m_NvrhiDevice->executeCommandList( m_BarrierCommandList );
// SRS - Don't need barrier commandlist if EndFrame() is called before executeCommandList() in idRenderBackend::GL_EndFrame()
//m_BarrierCommandList->open(); // umm...
//m_BarrierCommandList->close();
//m_NvrhiDevice->executeCommandList( m_BarrierCommandList );
}
void DeviceManager_VK::Present()

View file

@ -452,6 +452,8 @@ main
*/
int main( int argc, const char** argv )
{
extern idCVar r_useGPUSkinning;
// DG: needed for Sys_ReLaunch()
cmdargc = argc;
cmdargv = argv;
@ -481,6 +483,21 @@ int main( int argc, const char** argv )
common->Init( 0, NULL, NULL );
}
// SRS - Determine the machine name, e.g. "x86_64" or "arm64"
// Might be cleaner in posix Sys_Init(), but only needed on
// macOS and all the required sys includes are located here.
size_t size;
sysctlbyname( "hw.machine", NULL, &size, NULL, 0 );
char* machineName = ( char* )Mem_Alloc( size, TAG_SYSTEM );
sysctlbyname( "hw.machine", machineName, &size, NULL, 0 );
// FIXME: On Apple Silicon disable GPU skinning to eliminate rendering artifacts
if( strcmp( machineName, "arm64" ) == 0 )
{
r_useGPUSkinning.SetInteger( 0 );
}
Mem_Free( machineName );
Posix_LateInit();
while( 1 )