10.20.2016

How Disc Jam Reached 60 fps on Intel Processor Graphics using Unreal Engine 4

By Jay Mattis

Hi everyone! I’m Jay from High Horse Entertainment, a two-man team based out of Los Angeles. We started High Horse to build arcade games with modern graphics, control schemes, and an emphasis on robust online competitive play. Our first project, Disc Jam, is an arcade action sport in which timing and reflexes are critical to success. If you want to check it out, you can grab a free Steam key for our pre-alpha release at www.discjamgame.com!

Performance is a key focus for this project because maintaining 60 frames per second is an integral part of Disc Jam’s responsive and fluid gameplay style. As a result, we’ve learned a lot of lessons targeting this framerate using Unreal Engine 4. Below, I discuss our experience working with integrated graphics processing units from Intel and how we ultimately achieved our performance target without raising our minimum system requirements.

Why Target Integrated GPUs?

One thing that makes PC development tricky when compared with consoles is the lack of hardware standardization. A lot of people play games on their PCs and some of them have dedicated graphics chips, but a significant portion of PC owners rely on Integrated GPUs for gaming. It’s difficult to know exactly how large that market is, but the current hardware statistics made available by Unity Technologies show that around 40 percent of machines are playing using GPUs from Intel, which is higher than any other vendor. While many PC games can get away with high minimum system requirements, it’s critical that Disc Jam scale as low as possible for two major reasons:

Concurrency

Multiplayer games like Disc Jam live and die by their concurrency. If no one is playing, no one can find a match, and our player base will shrink until it disappears entirely. For this reason, it’s important that we support as many hardware configurations as possible.

Performance

Disc Jam is designed to be played at 60 frames per second (fps). If someone is playing on a system that’s unable to sustain that framerate, they’re not experiencing the game as we’ve intended. This may also impact their teammate’s and opponent’s experiences, because Disc Jam is an online game first and foremost.

Unreal Engine 4 Scalability and Performance

When deciding on an approach, we first looked at Unreal Engine 4’s performance “out of the box” on our target hardware. For this test, we used the binary release of Unreal Engine 4.12.5 and the Shooter Game example. All tests were run on a laptop equipped with an Intel® Core™ i7-4720 HQ processor and Intel® HD Graphics 4600 GPU.  Running Shooter Game in the Sanctuary map at 720p yields the following results:


Figure 1. Epic Quality Settings - ~20fps


Figure 2. Low Quality Settings: ~40fps

If we were making a game targeting 30 fps this would be great news. Unfortunately, we really want to hit that 60 fps mark, even on our minimum spec. Since it’s difficult to develop scenes that are more optimized than the Shooter Game example, we felt that the Unreal Engine 4 (UE4) desktop renderer had too high of a base performance cost for our hardware target. Luckily, if you’re willing to be creative and get your hands a little dirty, UE4 provides an alternative.

Unreal Engine 4’s Mobile Preview Renderer

Unreal Engine doesn’t just produce high-end desktop and console games. It produces high-end mobile games too! To do so, it features a few different rendering paths in order to support the myriad of mobile devices out there in the market. The highest-end path, the one designed for OpenGL for Embedded Systems (ES) 3.1 + Android Extension Pack (AEP), is the one we’re most interested in. From our tests, this rendering path boasts the best performance-to-quality ratio on integrated GPUs from Intel.

The key here is that UE4 has a feature called Mobile Preview. This feature is designed to reduce iteration time by previewing what a game will look like on mobile devices without having to deploy it. It effectively allows you to render the game on the desktop using the mobile rendering path instead of the full-blown deferred renderer that Unreal typically uses. Using this feature, we see the following results:


Figure 3. OpenGL ES 3.1 + AEP Mobile Preview: ~100fps

Running in Mobile Preview results in a ~2.5x speed up over the desktop renderer on its lowest settings. We are now well in range of hitting our target of 720p @ 60 fps on integrated GPUs! You’ll notice that there are some visual differences between the screenshots taken with the desktop renderer versus the mobile renderer. This is because the mobile renderer has some limitations especially in regards to lighting and shadowing. See the Epic documentation on Lighting for Mobile Platforms and Performance Guidelines for Mobile Devices for more information.

Multiple Lighting Rigs

In order to solve the problem above and light the court consistently in Disc Jam, we’ve opted to use multiple lighting rigs. We use one rig when rendering with the traditional renderer and another when rendering in Mobile Preview. Every game’s lighting needs will be different, but Disc Jam actually uses the same set of lights for both, with the only difference being the mobility of the primary shadow-casting light. In the high-end version, our primary light is a stationary spotlight. In Mobile Preview, we use a static spotlight so that all of the lighting is pre-baked, which helps us squeeze out even more performance.

UnrealEngine%2Fblog%2Fgoogle-daydream-sdk-1-0-released-supported-in-4-13-1-copy%2Fdisk-jam-high-end-lighting-610x343-78b0ec2082bf1c8f6364beb8f94a0b26542e9dc5
Figure 4. Disc Jam* High-End Renderer and Lighting


Figure 5. Disc Jam Low-End Renderer and Lighting

The first thing you’ll encounter when attempting to use multiple light rigs in UE4 is that the baked lighting is stored along with the geometry in the map rather than with the lights. Unfortunately, this means that you will need to duplicate all of your geometry into a second map in order to bake an alternate set of lights.

For Disc Jam, we set up a persistent level in which we placed all of the actors unaffected by lighting. These actors are shared across the high-end and low-end versions of the map and include things like spawn points and collision volumes. We then have both a high-end map and a low-end map that contain the same geometry and differ only in lighting. When the level loads we stream in the correct version:


Figure 6. Disc Jam’s Persistent Level Blueprint

The “Is in Mobile Preview” node used above is a custom C++ function defined as follows:

bool UDiscJamBlueprintFunctionLibrary::IsInMobilePreview()
{
	return GMaxRHIFeatureLevel <= ERHIFeatureLevel::ES3_1;
}

Packaging and Deployment

Please note: The following sections discuss packaging and deployment on Windows. The steps shown should be relatively similar for other operating systems.

After packaging the game and attempting to run with the command line argument “-FeatureLevelES31” it becomes immediately clear that the necessary shaders haven’t been included in the package. Under Project Settings → Platforms → Windows → Targeted RHIs, you can see that there are checkboxes for selecting which shader variants to package, but unfortunately the OpenGL ES 3.1 shaders are not among them. Adding this requires two simple code changes.

In GenericWindowsTargetPlatform.h, the GetAllPossibleShaderFormats function must be amended to include the OpenGL ES 3.1 shaders:

virtual void GetAllPossibleShaderFormats( TArray<FName>& OutFormats ) const override
{
	// no shaders needed for dedicated server target
	if (!IS_DEDICATED_SERVER)
	{
		static FName NAME_PCD3D_SM5(TEXT("PCD3D_SM5"));
		static FName NAME_PCD3D_SM4( TEXT( "PCD3D_SM4" ) );
		static FName NAME_PCD3D_ES3_1( TEXT( "PCD3D_ES31" ) );
		static FName NAME_GLSL_150(TEXT("GLSL_150"));
		static FName NAME_GLSL_430(TEXT("GLSL_430"));

		OutFormats.AddUnique(NAME_PCD3D_SM5);
		OutFormats.AddUnique(NAME_PCD3D_SM4);
		OutFormats.AddUnique(NAME_PCD3D_ES3_1);
		OutFormats.AddUnique(NAME_GLSL_150);
		OutFormats.AddUnique(NAME_GLSL_430);
	}
}

Then in WindowsTargetSettingsDetails.cpp, we add a friendly name to display in the UI by amending the GetFriendlyNameFromRHIName function:

FText GetFriendlyNameFromRHIName(const FString& InRHIName)
{
	FText FriendlyRHIName = LOCTEXT("UnknownRHI", "UnknownRHI");
	if (InRHIName == TEXT("PCD3D_SM5"))
	{
		FriendlyRHIName = LOCTEXT("DirectX11", "DirectX 11 (SM5)");
	}
	else if (InRHIName == TEXT("PCD3D_SM4"))
	{
		FriendlyRHIName = LOCTEXT("DirectX10", "DirectX 10 (SM4)");
	}
	else if (InRHIName == TEXT("PCD3D_ES31"))
	{
		FriendlyRHIName = LOCTEXT("DirectXES31", "DirectX Mobile Emulation (ES3.1)");
	}
	else if (InRHIName == TEXT("GLSL_150"))
	{
		FriendlyRHIName = LOCTEXT("OpenGL3", "OpenGL 3 (SM4)");
	}
	else if (InRHIName == TEXT("GLSL_430"))
	{
		FriendlyRHIName = LOCTEXT("OpenGL4", "OpenGL 4 (SM5, Experimental)");
	}
	else if (InRHIName == TEXT("SF_VKES31"))
	{
		FriendlyRHIName = LOCTEXT("Vulkan ES31", "Vulkan Mobile (ES3.1, Experimental)");
	}
	else if (InRHIName == TEXT("SF_VULKAN_SM4"))
	{
		FriendlyRHIName = LOCTEXT("VulkanSM4", "Vulkan (SM4)");
	}
	else if (InRHIName == TEXT("SF_VULKAN_SM5"))
	{
		FriendlyRHIName = LOCTEXT("VulkanSM5", "Vulkan (SM5)");
	}

	return FriendlyRHIName;
}

After making those changes and recompiling the engine, all that’s left to do is check the box under the Windows platform settings:


Figure 7. The newly created ‘DirectX Mobile Emulation (ES3.1)’ now appears

Bonus: Automatically Activate Mobile Preview on GPUs from Intel

Disc Jam allows players to choose which renderer to use on startup through Steam launch options. The low-end renderer choice simply launches the game with the “-FeatureLevelES31" command-line option.


Figure 8. Disc Jam Steam Launch Options

However, in the case of GPUs from Intel we have the game default to the Mobile Preview renderer. This is done with another simple code change. In WindowsD3D11Device.cpp, the function FD3D11DynamicRHI::InitD3DDevice() initializes the video adapter. About 100 lines down in that function it checks whether or not you’re using a GPU from Intel in order to correctly configure the video memory. Inside that block, we can set the renderer like so:

if ( IsRHIDeviceIntel() )
{
	// It's all system memory.
	FD3D11GlobalStats::GTotalGraphicsMemory = FD3D11GlobalStats::GDedicatedVideoMemory;
	FD3D11GlobalStats::GTotalGraphicsMemory += FD3D11GlobalStats::GDedicatedSystemMemory;
	FD3D11GlobalStats::GTotalGraphicsMemory += ConsideredSharedSystemMemory;

	GMaxRHIFeatureLevel = ERHIFeatureLevel::ES3_1;
	GMaxRHIShaderPlatform = SP_PCD3D_ES3_1;
}

And that’s all there is to it!

Let us know if this was helpful to you by dropping us a line @HighHorseGames on Twitter. To keep following Disc Jam and its development you can check out our blog at http://www.discjamgame.com.

Read the original article onj the Intel Developer Zone blog here.

Recent Posts

Holospark’s Earthfall Brings Innovation to the Co-op Shooter Genre

Seattle-based indie developer Holospark brings the Pacific Northwest to lif...

Unreal Engine Drives Monster Puppet for The Mill and Monster.com

Award-winning studio The Mill needed to produce several animation spots fea...

Drive Studio Uses Unreal to Score Big for FOX Sports’ 2018 FIFA World Cup Broadcast

Drive Studio leveraged the power of Unreal Engine to design the environment...