660 likes | 726 Views
C++ and DirectX for Metro style games. Chas Boyd, Matt Sandy Windows Graphics Microsoft Corporation. Why Write a Metro-Style Game?. Metro-style apps have features games need Store makes monetization simple New customers: Windows tablet users
E N D
C++ and DirectX for Metro style games Chas Boyd, Matt Sandy Windows Graphics Microsoft Corporation
Why Write a Metro-Style Game? Metro-style apps have features games need Store makes monetization simple New customers: Windows tablet users Single API set spans tablet PCs and conventional PCs Streamlined API surface is quick to learn Key game APIs are already familiar New platform means opportunity for visibility
Agenda Quick summary of the platform Step through the developing a C++/DirectX App such as a real-time animated game: App Initialization Opening a Window Rendering Graphics Loading and Saving Adding Input Controls Adding Sound Effects
Windows 8 Metro style apps Desktop apps View XAML DX Model Controller HTML JavaScript C# VB HTML / CSS C C++ JavaScript C C++ C# VB System Services WinRT APIs Application Model Devices & Printing Communication & Data Graphics & Media Windows Kernel Services Kernel .NET SL Internet Explorer Win32
App Structure Options C++ App JS/HTML App C++ or C# App WWA Host XAML Framework Your App Framework CoreWindow Kernel: Input and DirectX
FrameworkViewand CoreWindow • IFrameworkView • Effectively the new App Object class you implement • Enables OS to initialize app, deliver core OS resources • CoreWindow • Replacement for Win32 hWnd • Manages screen layout • Provides app input • Using event handler model
App Model – Activation • refclassMyApp : publicIFrameworkView • { • public: • MyApp(); • // IFrameworkView Methods • virtualvoid Initialize(CoreApplicationView^ applicationView); • virtualvoidSetWindow(CoreWindow^ window); • virtualvoid Load(String^ entryPoint); • virtualvoid Run(); • virtualvoidUninitialize();
App Activation Timeline Splash Screen Loading Screen Menu Screen Gameplay - OS Loads your App App Loads Loading Screen App is running Load() Run()
Games Need Real-Time Animation • voidMyApp::Run() • { • auto dispatcher = CoreWindow::GetForCurrentThread()->Dispatcher; • while (!m_windowClosed) • { • dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent); • m_renderer->Update(); • m_renderer->Render(); • m_renderer->Present(); • } • }
ViewStates: OnWindowSizeChanged • voidMyApp::SetWindow(CoreWindow^ window) • { • window->SizeChanged+= • refnewTypedEventHandler<CoreWindow^,WindowSizeChangedEventArgs^>( • this, &MyApp::OnWindowSizeChanged); • }
Resize Handler • voidDirectXApp::OnWindowSizeChanged( • _In_CoreWindow^ sender, • _In_WindowSizeChangedEventArgs^ args • ) • { • if (m_window->Bounds.Width != m_windowBounds.Width || • m_window->Bounds.Height != m_windowBounds.Height) • { • m_d2dContext->SetTarget(nullptr); • m_d2dTargetBitmap = nullptr; • m_renderTargetView = nullptr; • m_depthStencilView = nullptr; • CreateWindowSizeDependentResources(); • } • }
Rotation Preferences • { • DisplayOrientations::None; // Enable rotation by OS/Accelerometer • DisplayOrientations::Landscape; // Lock rotation by OS/Accelerometer • DisplayOrientations::LandscapeFlipped; // and enable this orientation • DisplayOrientations::Portrait; • DisplayOrientations::PortraitFlipped; • } • usingnamespace Windows::Graphics::Display; • DisplayProperties::AutoRotationPreferences = DisplayOrientations::Landscape • | DisplayOrientations::LandscapeFlipped;
Rotation Best Practices: Support All Orientations Event-based games Solitaire, Mahjong Full-real time games For example, Arcade games Pause gameplay during animation Restart via 3 – 2 – 1 countdown When game is paused E.g. at a menu Non-game apps Accelerometer/gyro games For example, Marble Maze Augmented Reality Apps Lock Rotation
Window Activation – Focus Changed • voidMyApp::OnWindowActivationChanged( • CoreWindow^ sender, • WindowActivatedEventArgs^ args) • { • auto state = args->WindowActivationState; • if(state == CoreWindowActivationState::Deactivated) • OutputDebugString("Focus Lost"); • if(state == CoreWindowActivationState::CodeActivated|| • state == CoreWindowActivationState::PointerActivated) • OutputDebugString("Focus Regained"); • }
Windows 8 Game Platform Technologies Your Killer Game Graphics Movies & Cut Scenes Game Input Audio Local Services Connected Services Tools Visual Studio Direct3D DirectX Video Pointer Point Windows Live WASAPI PLM Sensor API Windows Store Direct2D Asset Viewers Media Foundation AppData XAudio2 XInput Xbox LIVE Contracts Asset Processors HTML XAML
Direct3D is the Core Graphics API for Metro Style Apps • C++/DirectX is a very common game technology • Direct3D 11 is the API version for metro-style apps: • Existing Direct3D11 code should ‘just work’ • Direct3D10 code is very close to D3D11 syntax • Direct3D9 code is more work to port to D3D11, but relatively simple if you still have your DX9/10 shaders
DirectX Versions • The only DirectX API version supported is DirectX11 • But the DirectX11 API operates in different “modes” called FeatureLevels • These correspond to DirectX9 and Direct10 graphics hardware generations • In each mode, the API restricts features to those supported on that class of hardware
DirectX 11 Hardware Feature Level Geometry shaders Stream out 128 Textures per shader 8 Render Targets Integers in shaders Vertex textures Shader sampling Constant buffers Alpha-to-coverage Basic DirectCompute Async resource creation Full DirectCompute Random access writes Tessellation shaders New compression formats Shader linkage • Vertex shaders • Pixel shaders • 8 Textures • 4 Render Targets • Cube maps • Volume textures • Anisotropic filtering • Antialiasing • HDR rendering • Texture compression
DirectX 10 Hardware Feature Level • Vertex shaders • Pixel shaders • 8 Textures • 4 Render Targets • Cube maps • Volume textures • Anisotropic filtering • Antialiasing • HDR rendering • Texture compression Geometry shaders Stream out 128 Textures per shader 8 Render Targets Integers in shaders Vertex textures Shader sampling Constant buffers Alpha-to-coverage Basic DirectCompute Async resource creation
DirectX9 Hardware Feature Level • Vertex shaders • Pixel shaders • 8 Textures • 4 Render Targets • Cube maps • Volume textures • Anisotropic filtering • Antialiasing • HDR rendering • Texture compression
Select Feature Levels to Support D3D_FEATURE_LEVEL featureLevels[] = { D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3, D3D_FEATURE_LEVEL_9_1 }; UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
Create the Device and Context ComPtr<ID3D11Device> device; ComPtr<ID3D11DeviceContext> context; D3D11CreateDevice( nullptr, // use the default adapter D3D_DRIVER_TYPE_HARDWARE, 0, // use 0 unless a software device creationFlags, // defined above featureLevels, // what app will support ARRAYSIZE(featureLevels), D3D11_SDK_VERSION, // should always be D3D11_SDK_VERSION &device, // created device &m_featureLevel, // feature level of the device &context // corresponding immediate context );
Creating a swap chain • Windows::UI::Core::CoreWindow^ coreWindow; // app’s core window • Microsoft::WRL::ComPtr<ID3D11Device1> d3dDevice;// renderer • Microsoft::WRL::ComPtr<IDXGISwapChain1> dxgiSwapChain;// front/back buffers of RT • // Obtain the final swap chain for this window from the DXGI factory. • dxgiFactory->CreateSwapChainForCoreWindow( • d3dDevice.Get(), // the Direct3D device that will render to it • DX::GetIUnknown(coreWindow), // IUnknown interface on our core window • &swapChainDesc, // double or triple buffered, stereo, etc. • nullptr, // allow on all displays • &dxgiSwapChain// the resulting swap chain object • );
voidmyApp::Render() • { • m_d3dContext->OMSetRenderTargets( // rebind every frame! • 1, • m_renderTargetView.GetAddressOf(), • m_depthStencilView.Get() ); • if(!m_loadingComplete) // only draw the cube once it's loaded • return; //(this is async) • m_d3dContext->IASetVertexBuffers( • 0, 1, • m_vertexBuffer.GetAddressOf(), • &stride, • &offset ); • m_d3dContext->IASetIndexBuffer( • m_indexBuffer.Get(), • DXGI_FORMAT_R16_UINT, 0 );
m_d3dContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); • m_d3dContext->IASetInputLayout(m_inputLayout.Get()); • m_d3dContext->VSSetConstantBuffers( • 0, 1, • m_constantBuffer.GetAddressOf() ); • m_d3dContext->VSSetShader( m_vertexShader.Get(), • nullptr, 0 ); • m_d3dContext->PSSetShader( m_pixelShader.Get(), • nullptr, 0 ); • m_d3dContext->DrawIndexed( m_indexCount, 0, 0 ); • }
Graphics Optimization Techniques • Tune Anisotropic filter quality • Simple scalar value • MultiSamplingAntiAliasing (MSAA) • Reduce sample count to maintain frame rate • Render to a lower resolution and scale up for final image • For best image quality, do not scale 2D text • Geometry • Feature_Level_11 – use tessellation for more polygon count control • Consider lower-resolution (lower vertex count) meshes
Texture Quality Control • Balance visual quality with performance • Scale back on size via mipmap levels • Use block-compressed • texture formats • Loader code skips mip levels 1024 x 1024 512 x 512 256 x 256
Differentiation • Increase Visual Quality • Higher resolution textures • Use bump maps
Minimum PrecisionHLSL Code Sample static constfloat brightThreshold = 0.5f; Texture2DsourceTexture : register(t0); float4 DownScale3x3BrightPass(QuadVertexShaderOutput input) : SV_TARGET { float3 brightColor = 0; // Gather 16 adjacent pixels (each bilinear sample reads a 2x2 region) brightColor = sourceTexture.Sample(linearSampler, input.tex, int2(-1,-1)).rgb; brightColor += sourceTexture.Sample(linearSampler, input.tex, int2( 1,-1)).rgb; brightColor += sourceTexture.Sample(linearSampler, input.tex, int2(-1, 1)).rgb; brightColor += sourceTexture.Sample(linearSampler, input.tex, int2( 1, 1)).rgb; brightColor /= 4.0f; // Brightness thresholding brightColor = max(0, brightColor - brightThreshold); return float4(brightColor, 1.0f); }
Minimum Precision OptimizationHLSL Code Sample static const min16float brightThreshold = (min16float)0.5; Texture2D<min16float4> sourceTexture : register(t0); float4 DownScale3x3BrightPass(QuadVertexShaderOutput input) : SV_TARGET { min16float3 brightColor = 0; // Gather 16 adjacent pixels (each bilinear sample reads a 2x2 region) brightColor = sourceTexture.Sample(linearSampler, input.tex, int2(-1,-1)).rgb; brightColor += sourceTexture.Sample(linearSampler, input.tex, int2( 1,-1)).rgb; brightColor += sourceTexture.Sample(linearSampler, input.tex, int2(-1, 1)).rgb; brightColor += sourceTexture.Sample(linearSampler, input.tex, int2( 1, 1)).rgb; brightColor /= (min16float)4.0; // Brightness thresholding brightColor = max(0, brightColor - brightThreshold); return float4(brightColor, 1.0f); }
Development Strategy • Develop on DirectX 11 hardware • Target Feature_Level_9 and scale up • Include calibration code in game to adjust performance for current hardware • Adjust to maintain performance • Be aware of Feature Level differences • Test by restricting Feature Level or via WARP • Test on multiple PCs
Process Lifetime Management • Suspend/Resume Apps are not notified before they are terminated App gets 5 seconds to work after suspend message suspending Running App Suspended App Terminated App User Launches App Low Memory resuming Apps are notified when they have been resumed
Best Practice for Games • Save (parts of) state as often as possible • End of level, • Mid-level checkpoints, • When user pauses, • On Suspend event • Incremental data possibly every frame • If resuming mid-level, use a countdown
Mouse and Touch Input • win->PointerPressed += refnewTypedEventHandler<CoreWindow^,PointerEventArgs^> • (this, &LonLatController::OnPointerPressed); • voidLonLatController::OnPointerPressed( • _In_CoreWindow^ sender, • _In_PointerEventArgs^ args • ) • { • float2position = float2( // position of contact • args->CurrentPoint->Position.X, • args->CurrentPoint->Position.Y • ); • m_lonLatLastPoint= position; // save for use in controller • m_lonLatPointerID= args->CurrentPoint->PointerId; • }
Polling for Keyboard Input • // Arrow keys or WASD example • auto upKeyState = window->GetKeyAsyncState(VirtualKey::Up); • auto wKeyState = window->GetAsyncKeyState(VirtualKey::W); • if (upKeyState & CoreVirtualKeyStates::Down || • wKeyState & CoreVirtualKeyStates::Down) • { • m_playerPosition.y += 1.0f; • }
Sensor fusion inputs and outputs Accelerometer 3D Accelerometer Pass-Through 3D Gyro Gyro 3D Magnetometer Sensor Fusion Compass Inclinometer Device Orientation
Reading orientation sensor data • using Windows::Devices::Sensors; • // Get current reading from sensor • OrientationSensorReading^ orientationReading = m_orientationsensor->GetCurrentReading(); • SensorQuaternion^ quat = orientationReading->Quaternion; • // Transform quaternion from device orientation space to world space • // Orientation space is Z-up, right-handed coordinate system • // World space is Y-up, left-handed coordinate system • XMVECTOR orientationQuat = XMVectorSet(-quat->X, quat->Z, quat->Y, -quat->W); • // Create a rotation matrix from the quaternion • // This matrix can be used to rotate an object inside the scene to match • // the rotation of the device • XMMATRIX rotXMMatrix = XMMatrixRotationQuaternion(orientationQuat);
Game Controller Input • Windows 8 supports Xbox360 -compatible controllers • Check out new ControllerSketch sample • Demonstrates game controller usage from JavaScript app
Game Controller D-pad Input • SHORT thumbLeftX = inputState.Gamepad.sThumbLX; • if (abs(thumbLeftX) < XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) • thumbLeftX = 0; • SHORT thumbLeftY = inputState.Gamepad.sThumbLY; • if (abs(thumbLeftY) < XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) • thumbLeftY = 0; • combinedTiltX += (float)thumbLeftX / 32768.0f; • combinedTiltY += (float)thumbLeftY / 32768.0f;
Sound Effects // Create the XAudio2 engine and mastering voice on the default audio device XAudio2Create(&m_audioEngine); m_audioEngine->CreateMasteringVoice(&m_masteringVoice);// Load all audio data for the sound effect into a single in-memory buffer MediaLoadersoundFile(ref new Platform::String(SOUND_FILE));m_soundEffectBuffer = soundFile.ReadAll();// Create a single source voice for a sound effect m_audioEngine->CreateSourceVoice(&m_sourceVoice, &(soundFile.GetOutputWaveFormatEx())); // Trigger sound effect: queue in-memory buffer for playback and start the voiceXAUDIO2_BUFFER buf = {0};buf.AudioBytes = m_soundEffectBuffer.size();buf.pAudioData = &m_soundEffectBuffer[0];buf.Flags = XAUDIO2_END_OF_STREAM;m_sourceVoice->SubmitSourceBuffer(&buf);m_sourceVoice->Start();