diff --git a/README.md b/README.md index 5b7bc821..539ef005 100644 --- a/README.md +++ b/README.md @@ -494,7 +494,7 @@ Recommended in this case is `cmake-vs2019-64bit.bat` or `cmake-vs2019-64bit-no-f You don't need FFmpeg to be installed. You can turn it off by adding -DFFMPEG=OFF and -DBINKDEC=ON to the CMake options. For debug builds FFmpeg is enabled by default because the bundled libbinkdec is slow during development if compiled for Debug mode. For release, retail and universal builds FFmpeg is disabled and libbinkdec is enabled by default. - The Vulkan SDK must be installed and can be obtained from https://vulkan.lunarg.com/sdk/home#mac + The Vulkan SDK 1.3.231.1 or later must be installed and can be obtained from https://vulkan.lunarg.com/sdk/home#mac 3. Generate the Makefiles using CMake: diff --git a/neo/cmake-xcode-debug.sh b/neo/cmake-xcode-debug.sh index 66d77134..a2ee3f06 100755 --- a/neo/cmake-xcode-debug.sh +++ b/neo/cmake-xcode-debug.sh @@ -5,6 +5,7 @@ cd xcode-debug # 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_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_USE_METAL_ARGUMENT_BUFFERS=2" -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) +# note 4: env variable MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS=1 enforces synchronous queue submits which is required 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=1;MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=2" -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 diff --git a/neo/compileshaders.cmake b/neo/compileshaders.cmake index 69f405f1..87f8ee74 100644 --- a/neo/compileshaders.cmake +++ b/neo/compileshaders.cmake @@ -106,12 +106,7 @@ function(compile_shaders) endif() if (NOT params_CFLAGS) - if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - # SRS - MoltenVK currently supports vulkan 1.1 (1.2 coming in next SDK release) - set(CFLAGS "$,-Zi,> -fspv-target-env=vulkan1.1 -O3 -WX -Zpr") - else() - set(CFLAGS "$,-Zi,> -fspv-target-env=vulkan1.2 -O3 -WX -Zpr") - endif() + set(CFLAGS "$,-Zi,> -fspv-target-env=vulkan1.2 -O3 -WX -Zpr") else() set(CFLAGS ${params_CFLAGS}) endif() diff --git a/neo/idlib/precompiled.h b/neo/idlib/precompiled.h index 91bd2fa3..759fe278 100644 --- a/neo/idlib/precompiled.h +++ b/neo/idlib/precompiled.h @@ -90,8 +90,9 @@ const int MAX_EXPRESSION_REGISTERS = 4096; // everything that is needed by the backend needs // to be double buffered to allow it to run in // parallel on a dual cpu machine -#if defined(__APPLE__) && ( defined( USE_VULKAN ) || defined( USE_NVRHI ) ) +#if ( defined(__APPLE__) && defined( USE_VULKAN ) ) || defined( USE_NVRHI ) // SRS - macOS MoltenVK/Metal needs triple buffering for full screen to work properly + // SRS - use triple buffering for NVRHI with command queue event query sync method const uint32 NUM_FRAME_DATA = 3; #else const uint32 NUM_FRAME_DATA = 2; diff --git a/neo/renderer/NVRHI/RenderBackend_NVRHI.cpp b/neo/renderer/NVRHI/RenderBackend_NVRHI.cpp index 192de6be..8f8e4265 100644 --- a/neo/renderer/NVRHI/RenderBackend_NVRHI.cpp +++ b/neo/renderer/NVRHI/RenderBackend_NVRHI.cpp @@ -1603,14 +1603,16 @@ void idRenderBackend::GL_BlockingSwapBuffers() OPTICK_EVENT( "BlockingSwapBuffers" ); // Make sure that all frames have finished rendering - deviceManager->GetDevice()->waitForIdle(); - - // Release all in-flight references to the render targets - deviceManager->GetDevice()->runGarbageCollection(); + // SRS - device-level sync kills perf by serializing command queue processing (CPU) and rendering (GPU) + // - instead, use alternative sync method (based on command queue event queries) inside Present() + //deviceManager->GetDevice()->waitForIdle(); // Present to the swap chain. deviceManager->Present(); + // Release all in-flight references to the render targets + deviceManager->GetDevice()->runGarbageCollection(); + renderLog.EndFrame(); if( deviceManager->GetGraphicsAPI() == nvrhi::GraphicsAPI::VULKAN ) diff --git a/neo/renderer/RenderSystem_init.cpp b/neo/renderer/RenderSystem_init.cpp index 01a5d7e7..b730dfc0 100644 --- a/neo/renderer/RenderSystem_init.cpp +++ b/neo/renderer/RenderSystem_init.cpp @@ -890,7 +890,12 @@ bool R_ReadPixelsRGB8( nvrhi::IDevice* device, CommonRenderPasses* pPasses, nvrh data[ i * 4 + 3 ] = 0xff; } + // SRS - Save screen shots to fs_savepath on macOS (i.e. don't save into an app bundle's basepath) +#if defined(__APPLE__) + R_WritePNG( fullname, static_cast( pData ), 4, desc.width, desc.height, true, "fs_savepath" ); +#else R_WritePNG( fullname, static_cast( pData ), 4, desc.width, desc.height, true, "fs_basepath" ); +#endif if( newData ) { diff --git a/neo/sys/DeviceManager.h b/neo/sys/DeviceManager.h index 348ff37c..862a2ed9 100644 --- a/neo/sys/DeviceManager.h +++ b/neo/sys/DeviceManager.h @@ -54,7 +54,6 @@ struct DeviceCreationParameters nvrhi::Format swapChainFormat = nvrhi::Format::RGBA8_UNORM; // RB: don't do the sRGB gamma ramp with the swapchain uint32_t swapChainSampleCount = 1; uint32_t swapChainSampleQuality = 0; - uint32_t maxFramesInFlight = 2; bool enableDebugRuntime = false; bool enableNvrhiValidationLayer = false; bool vsyncEnabled = false; diff --git a/neo/sys/DeviceManager_DX12.cpp b/neo/sys/DeviceManager_DX12.cpp index 062d8f4c..338bb2bf 100644 --- a/neo/sys/DeviceManager_DX12.cpp +++ b/neo/sys/DeviceManager_DX12.cpp @@ -61,6 +61,7 @@ class DeviceManager_DX12 : public DeviceManager std::vector m_RhiSwapChainBuffers; RefCountPtr m_FrameFence; std::vector m_FrameFenceEvents; + nvrhi::EventQueryHandle m_FrameWaitQuery; UINT64 m_FrameCount = 1; @@ -447,6 +448,9 @@ bool DeviceManager_DX12::CreateDeviceAndSwapChain() m_FrameFenceEvents.push_back( CreateEvent( nullptr, false, true, NULL ) ); } + m_FrameWaitQuery = nvrhiDevice->createEventQuery(); + nvrhiDevice->setEventQuery( m_FrameWaitQuery, nvrhi::CommandQueue::Graphics ); + return true; } @@ -459,6 +463,8 @@ void DeviceManager_DX12::DestroyDeviceAndSwapChain() m_NvrhiDevice = nullptr; + m_FrameWaitQuery = nullptr; + for( auto fenceEvent : m_FrameFenceEvents ) { WaitForSingleObject( fenceEvent, INFINITE ); @@ -630,6 +636,11 @@ void DeviceManager_DX12::EndFrame() void DeviceManager_DX12::Present() { + // SRS - Sync on previous frame's command queue completion vs. waitForIdle() on whole device + m_NvrhiDevice->waitEventQuery( m_FrameWaitQuery ); + m_NvrhiDevice->resetEventQuery( m_FrameWaitQuery ); + m_NvrhiDevice->setEventQuery( m_FrameWaitQuery, nvrhi::CommandQueue::Graphics ); + if( !m_windowVisible ) { return; diff --git a/neo/sys/DeviceManager_VK.cpp b/neo/sys/DeviceManager_VK.cpp index 77c10bae..b39025b3 100644 --- a/neo/sys/DeviceManager_VK.cpp +++ b/neo/sys/DeviceManager_VK.cpp @@ -272,8 +272,7 @@ private: nvrhi::CommandListHandle m_BarrierCommandList; vk::Semaphore m_PresentSemaphore; - std::queue m_FramesInFlight; - std::vector m_QueryPool; + nvrhi::EventQueryHandle m_FrameWaitQuery; // SRS - flag indicating support for eFifoRelaxed surface presentation (r_swapInterval = 1) mode bool enablePModeFifoRelaxed = false; @@ -1124,6 +1123,10 @@ bool DeviceManager_VK::CreateDeviceAndSwapChain() vkGetMoltenVKConfigurationMVK( m_VulkanInstance, &pConfig, &pConfigSize ); + // SRS - Enforce synchronous queue submission for vkQueueSubmit() & vkQueuePresentKHR() + pConfig.synchronousQueueSubmits = VK_TRUE; + 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 ) { @@ -1182,6 +1185,9 @@ bool DeviceManager_VK::CreateDeviceAndSwapChain() m_PresentSemaphore = m_VulkanDevice.createSemaphore( vk::SemaphoreCreateInfo() ); + m_FrameWaitQuery = m_NvrhiDevice->createEventQuery(); + m_NvrhiDevice->setEventQuery( m_FrameWaitQuery, nvrhi::CommandQueue::Graphics ); + #undef CHECK return true; @@ -1191,25 +1197,13 @@ void DeviceManager_VK::DestroyDeviceAndSwapChain() { destroySwapChain(); + m_FrameWaitQuery = nullptr; + m_VulkanDevice.destroySemaphore( m_PresentSemaphore ); m_PresentSemaphore = vk::Semaphore(); m_BarrierCommandList = nullptr; - while( m_FramesInFlight.size() > 0 ) - { - auto query = m_FramesInFlight.front(); - m_FramesInFlight.pop(); - query = nullptr; - } - - if( !m_QueryPool.empty() ) - { - auto query = m_QueryPool.back(); - m_QueryPool.pop_back(); - query = nullptr; - } - m_NvrhiDevice = nullptr; m_ValidationLayer = nullptr; m_RendererString.clear(); @@ -1276,6 +1270,11 @@ void DeviceManager_VK::EndFrame() void DeviceManager_VK::Present() { + // SRS - Sync on previous frame's command queue completion vs. waitForIdle() on whole device + m_NvrhiDevice->waitEventQuery( m_FrameWaitQuery ); + m_NvrhiDevice->resetEventQuery( m_FrameWaitQuery ); + m_NvrhiDevice->setEventQuery( m_FrameWaitQuery, nvrhi::CommandQueue::Graphics ); + vk::PresentInfoKHR info = vk::PresentInfoKHR() .setWaitSemaphoreCount( 1 ) .setPWaitSemaphores( &m_PresentSemaphore ) @@ -1286,46 +1285,12 @@ void DeviceManager_VK::Present() const vk::Result res = m_PresentQueue.presentKHR( &info ); assert( res == vk::Result::eSuccess || res == vk::Result::eErrorOutOfDateKHR || res == vk::Result::eSuboptimalKHR ); - if( m_DeviceParams.enableDebugRuntime ) + if( m_DeviceParms.enableDebugRuntime || deviceParms.vsyncEnabled ) { // according to vulkan-tutorial.com, "the validation layer implementation expects // the application to explicitly synchronize with the GPU" m_PresentQueue.waitIdle(); } - else - { -#ifndef _WIN32 - if( m_DeviceParams.vsyncEnabled ) - { - m_PresentQueue.waitIdle(); - } -#endif - - while( m_FramesInFlight.size() > m_DeviceParams.maxFramesInFlight ) - { - auto query = m_FramesInFlight.front(); - m_FramesInFlight.pop(); - - m_NvrhiDevice->waitEventQuery( query ); - - m_QueryPool.push_back( query ); - } - - nvrhi::EventQueryHandle query; - if( !m_QueryPool.empty() ) - { - query = m_QueryPool.back(); - m_QueryPool.pop_back(); - } - else - { - query = m_NvrhiDevice->createEventQuery(); - } - - m_NvrhiDevice->resetEventQuery( query ); - m_NvrhiDevice->setEventQuery( query, nvrhi::CommandQueue::Graphics ); - m_FramesInFlight.push( query ); - } } DeviceManager* DeviceManager::CreateVK() diff --git a/neo/sys/sdl/sdl_vkimp.cpp b/neo/sys/sdl/sdl_vkimp.cpp index 35bc8856..03030f69 100644 --- a/neo/sys/sdl/sdl_vkimp.cpp +++ b/neo/sys/sdl/sdl_vkimp.cpp @@ -470,15 +470,24 @@ static bool SetScreenParmsFullscreen( glimpParms_t parms ) // change settings in that display mode according to parms // FIXME: check if refreshrate, width and height are supported? - m.refresh_rate = parms.displayHz; - m.w = parms.width; - m.h = parms.height; - - // set that displaymode - if( SDL_SetWindowDisplayMode( window, &m ) < 0 ) + if( m.w != parms.width || m.h != parms.height || m.refresh_rate != parms.displayHz ) { - common->Warning( "Couldn't set window mode for fullscreen, reason: %s", SDL_GetError() ); - return false; + m.w = parms.width; + m.h = parms.height; + m.refresh_rate = parms.displayHz; + + // if we're already in fullscreen mode, disable it first so resizing works properly + if( glConfig.isFullscreen ) + { + SDL_SetWindowFullscreen( window, SDL_FALSE ); + } + + // set the new displaymode + if( SDL_SetWindowDisplayMode( window, &m ) < 0 ) + { + common->Warning( "Couldn't set window mode for fullscreen, reason: %s", SDL_GetError() ); + return false; + } } // if we're currently not in fullscreen mode, we need to switch to fullscreen