1 / 67

The Next Mainstream Programming Language: A Game Developer’s Perspective

The Next Mainstream Programming Language: A Game Developer’s Perspective. By: Tim Sweeney Presented by: Katie Coons. Outline. What’s in a game? Game simulation Numeric computation Shading Programming language requirements Modularity Concurrency Reliability Performance.

sonel
Download Presentation

The Next Mainstream Programming Language: A Game Developer’s Perspective

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. The Next Mainstream Programming Language: A Game Developer’s Perspective By: Tim Sweeney Presented by: Katie Coons

  2. Outline • What’s in a game? • Game simulation • Numeric computation • Shading • Programming language requirements • Modularity • Concurrency • Reliability • Performance

  3. What’s in a game? Productivity is very important. Think reliability. • Resources • ~10 programmers • ~20 artists • ~24 month development cycle • ~10M budget • Software Dependencies • 1 middleware game engine • ~20 middleware libraries • OS graphics APIs, sound, input, etc. Modularity is important.

  4. Software Dependencies Gears of War Gameplay Code~250,000 lines C++, script code Unreal Engine 3 Middleware Game Engine ~250,000 lines C++ code DirectX Graphics OpenAL Audio OggVorbis Music Codec Speex SpeechCodec wxWidgets Window Library ZLib Data Compr- ession …

  5. Outline • What’s in a game? • Game simulation • Numeric computation • Shading • Programming language requirements • Modularity • Concurrency • Reliability • Performance

  6. Gameplay Simulation

  7. Gameplay Simulation • Models state of the game world as interacting objects evolve over time • High-level, object-oriented code • C++ or scripting language • Imperative programming • Usually garbage-collected Mutable state - synchronization and concurrency are hard.

  8. Gameplay Simulation • 30-60 updates (frames) per second • ~1000 distinct gameplay classes • Contain imperative state • Contain member functions • Highly dynamic • ~10,000 active gameplay objects • Each time a gameplay object is updated, it typically touches 5-10 other objects This is a lot of mutable state to keep track of, and it changes very frequently.

  9. Numeric Computation

  10. Numeric Computation • Algorithms: • Scene graph traversal • Physics simulation • Collision Detection • Path Finding • Sound Propagation • Low-level, high-performance code • C++ with SIMD intrinsics • Essentially functional Potential concurrency to exploit

  11. Shading

  12. Shading • Generates pixel and vertex attributes • Written in HLSL/CG shading language • Runs on the GPU • Inherently data-parallel • Control flow is statically known • “Embarassingly Parallel” • Current GPUs are 16-wide to 48-wide! What lessons can we learn from shading languages? Do we want a language that exploits this data parallelism as well as fine and course grained thread/task parallelism?

  13. Shading • Game runs at 30 FPS @ 1280x720p • ~5,000 visible objects • ~10M pixels rendered per frame • Per-pixel lighting and shadowing requires multiple rendering passes per object and per light • Typical pixel shader is ~100 instructions long • Shader FPUs are 4-wide SIMD • ~500 GFLOPS compute power Great results, but with special hardware/software. How well do these abilities map to other problems? Is a hybrid approach worth trying?

  14. Three Kinds of Code

  15. Three Kinds of Code Maybe we can increase these with a little extra concurrency?

  16. Outline • What’s in a game? • Game simulation • Numeric computation • Shading • Programming language requirements • Modularity • Concurrency • Reliability • Performance

  17. The hard problems according to Tim Sweeney • Modularity • Very important with ~10-20 middleware libraries per game • Concurrency • Hardware supports 6-8 threads • C++ is ill-equipped for concurrency • Reliability • Error-prone language / type system leads to wasted effort finding trivial bugs • Significantly impacts productivity • Performance • When updating 10,000 objects at 60 FPS, everything is performance-sensitive These are all very strongly connected

  18. Modularity

  19. Game Class Hierarchy Generic Game Framework Actor Player Enemy InventoryItem Weapon Game-Specific Framework Extension Actor Player Enemy Dragon Troll InventoryItem Weapon Sword Crossbow

  20. Game Class Hierarchy Generic Game Framework Actor Player Enemy InventoryItem Weapon Game-Specific Framework Extension Actor Player Enemy Dragon Troll InventoryItem Weapon Sword Crossbow What if we want to extend the base class?

  21. Software Frameworks • The Problem: Users of a framework want to extend the functionality of the framework’s base classes! • The workarounds: • Modify the source…and modify it again with each new version • Add references to payload classes, and dynamically cast them at runtime to the appropriate types. Maybe the compiler could help us… Error prone! Ugly, and runtime casting can be error-prone

  22. A Better Solution… Base Framework Extended Framework package Engine; class Actor{ int Health; …}class Player extends Actor{ …}class Inventory extends Actor{ …} Package GearsOfWar extends Engine; class Actor extends Engine.Actor{ // Here we can add new members // to the base class. … }class Player extends Engine.Player{ // Thus virtually inherits from // GearsOfWar.Actor …}class Gun extends GearsOfWar.Inventory{ …} The basic goal:To extend an entire software framework’s class hierarchy in parallel, in an open-world system. Definitely applicable to non-graphics tasks, as well

  23. A Better Solution… Base Framework Extended Framework package Engine; class Actor{ int Health; …}class Player extends Actor{ …}class Inventory extends Actor{ …} Package GearsOfWar extends Engine; class Actor extends Engine.Actor{ // Here we can add new members // to the base class. … }class Player extends Engine.Player{ // Thus virtually inherits from // GearsOfWar.Actor …}class Gun extends GearsOfWar.Inventory{ …} Why don’t we do this already? Multiple inheritance? Type system implications?

  24. Concurrency

  25. The C++/Java/C# Model:“Shared state concurrency” • The Idea: • Any thread can modify any state at any time. • All synchronization is explicit, manual. • No compile-time verification of correctness properties: • Deadlock-free • Race-free With current constructs, the compiler can’t do this

  26. The C++/Java/C# Model:“Shared state concurrency” • This is hard! • Possible workarounds: • 1 main thread responsible for doing work that isn’t safe to multithread • 1 heavyweight rendering thread • A pool of 4-6 helper threads • Dynamically allocate them to simple tasks. • “Program Very Carefully!” • Huge productivity burden • Scales poorly to thread counts We could better exploit this concurrency There must be a better way!

  27. Three Kinds of Code: Revisited • Shading • Already implicitly data parallel • Numeric Computation • Computations are purely functional • But they use state locally during computations • Gameplay Simulation • Gratuitous use of mutable state • 10,000’s of objects must be updated • Typical object update touches 5-10 other objects

  28. Concurrency in Shading • Look at the solution of CG/HLSL: • New programming language aimed at “Embarrassingly Parallel” shader programming • Its constructs map naturally to a data-parallel implementation • Static control flow (conditionals supported via masking)

  29. Concurrency in Shading Conclusion: The problem of data-parallel concurrency is effectively solved(!) “Proof”: Xbox 360 games are running with 48-wide data shader programs utilizing half a Teraflop of compute power...

  30. Referential Transparency • Any subexpression can be substituted with its value at any time … without changing the meaning of the program. • Advantages • Static analysis is easier • Automatic code transformations easier (memoization, CSE) • Easier to prove correctness • Opportunities for concurrency   A = 1 + (3*4) A = 1 + 12 A = GetInput()

  31. Concurrency in Numeric Computation • These are essentially pure functional algorithms, but they operate locally on mutable state • Haskell ST, STRef solution enables encapsulating local heaps and mutability within referentially-transparent code • These are the building blocks for implicitly parallel programs • Estimate ~80% of CPU effort in Unreal can be parallelized this way Space implications? Impact on performance?

  32. Numeric Computation Example: Collision Detection A typical collision detection algorithm takes a line segment and determines when and where a point moving along that line will collide with a (constant) geometric dataset. struct vec3 { float x,y,z;}; struct hit{ bool DidCollide; float Time; vec3 Location;}; hit collide(vec3 start,vec3 end); Vec3 = data Vec3 float float float Hit = data Hit float Vec3 collide :: (vec3,vec3)->Maybe Hit

  33. Numeric Computation Example: Collision Detection A typical collision detection algorithm takes a line segment and determines when and where a point moving along that line will collide with a (constant) geometric dataset. struct vec3 { float x,y,z;}; struct hit{ bool DidCollide; float Time; vec3 Location;}; hit collide(vec3 start,vec3 end); Effects free Vec3 = data Vec3 float float float Hit = data Hit float Vec3 collide :: (vec3,vec3)->Maybe Hit

  34. Language Implications Effects Model • Purely Functional is the right default • Imperative constructs are vital features that must be exposed through explicit effects-typing constructs • Exceptions are an effect In a concurrent world, imperative is the wrong default

  35. Concurrency in Numeric Computation Referential transparency is promising… … but what are the side effects?

  36. Concurrency in Gameplay Simulation This is the hardest problem… • 10,000s of objects • Each one contains mutable state • Each one updated 30 times per second • Each update touches 5-10 other objects Manual synchronization (shared state concurrency) is hopelessly intractable here.

  37. Composable Memory Transactions • Composable memory transactions with concurrent Haskell • Modular, sequenced blocking with retry • Composable alternatives with orElse How composable are these, really? Performance? Underlying support?

  38. Concurrency in Gameplay Simulation:Software Transactional Memory The idea: • Update all objects concurrently in arbitrary order,with each update wrapped in an atomic {...} block • With 10,000’s of updates, and 5-10 objects touched per update, collisions will be low • ~2-4X STM performance overhead is acceptable:if it enables our state-intensive code to scale to many threads, it’s still a win (Really?)

  39. Concurrency in Gameplay Simulation Manual, explicit synchronization is too hard… Claim: Transactions are the only plausible solution to concurrent mutable state

  40. Three Kinds of Code

  41. Parallelism and Purity Physics, collision detection, scene traversal, path finding, .. Game World State Graphics shader programs Software Transactional Memory Purely functional core Data Parallel Subset

  42. Parallelism and Purity Physics, collision detection, scene traversal, path finding, .. Game World State Graphics shader programs Software Transactional Memory Purely functional core Data Parallel Subset Can we learn anything from these? Space? Performance? Composability? Performance?

  43. Reliability

  44. Dynamic failure in mainstream languages Example (C#):Given a vertex array and an index array, we read and transform the indexed vertices into a new array. What can possibly go wrong? Vertex[] Transform (Vertex[] Vertices, int[] Indices, Matrix m){ Vertex[] Result = new Vertex[Indices.length]; for(int i=0; i<Indices.length; i++) Result[i] = Transform(m,Vertices[Indices[i]]); return Result; };

  45. Dynamic failure in mainstream languages May contain indices outside of the range of the Vertex array Our code is littered with runtime failure cases, Yet the compiler remains silent! Dynamic failure is a big deal - awkward/error prone to prevent May be NULL May be NULL May be NULL Vertex[] Transform (Vertex[] Vertices, int[] Indices, Matrix m){ Vertex[] Result = new Vertex[Indices.length]; for(int i=0; i<Indices.length; i++) Result[i] = Transform(m,Vertices[Indices[i]]); return Result; }; Array access might be out of bounds Could dereference a NULL pointer Will the compiler realize this can’t fail?

  46. Dynamic failure in mainstream languages • Solved problems: • Random memory overwrites • Memory leaks (?) • Solvable • Accessing arrays out of bounds • Dereferencing null pointers • Integer overflow • Accessing uninitialized variables 50% of the bugs in Unreal can be traced to these problems!

  47. A better solution…? An index buffer containing natural numbers less than n Universally quantify over all natural numbers An array of exactly known size Transform{n:nat}(Vertices:[n]Vertex, Indices:[]nat<n, m:Matrix):[]Vertex= for each(i in Indices) Transform(m,Vertices[i]) The only possible failure mode:divergence, if the call to Transform diverges. Haskell-style array comprehension

  48. How might this work? • Dependent types • Dependent functions • Universal quantification The Integers int nat nat<n The Natural Numbers The Natural Numbers less than n,where n may be a variable! Sum(n:nat,xs:[n]int)=..a=Sum(3,[7,8,9]) Explicit type/value dependency between function parameters Sum{n:nat}(xs:[n]int)=..a=Sum([7,8,9]) Good ideas, though they make the compiler’s job significantly harder…

  49. How might this work? • Separating the “pointer to t” conceptfrom the “optional value of t” concept • Advantages: • You can’t dereference a NULL pointer • The compiler can force you to do the appropriate error checks A pointer to an integer xp:^int xo:?intxpo:?^int An optional integer An optional pointer to an integer! ?int x = lookup(t, k) FOUND(x) -> { use x … } NOTHING(x) -> { display error message of your choice }

  50. How might this work? • Comprehensions (a la Haskell),for safely traversing and generating collections Successors(xs:[]int):[]int= foreach(x in xs) x+1 Now we can’t have an index out of bounds error! But this ties the Collections object and Iterator interface directly to the language - sacrifice abstraction

More Related