Difference between revisions of "GPipe"

From HaskellWiki
Jump to: navigation, search
m
(Examples and tutorials)
 
(21 intermediate revisions by 4 users not shown)
Line 1: Line 1:
 
== What is GPipe? ==
 
== What is GPipe? ==
   
[http://hackage.haskell.org/package/GPipe GPipe] is a library for programming the GPU (graphics processing unit). It is an alternative to using OpenGl, and has the advantage that it is purely functional, statically typed and operates on immutable data as opposed to OpenGl's inherently imperative style. Another important difference with OpenGl is that with GPipe you don't need to write shaders in a second shader language such as GLSL or Cg, but instead apply normal Haskell functions directly on the GPU data types. GPipe uses the same conceptual model as OpenGl, and it is recommended that you have at least a basic understanding of how OpenGl works to be able to use GPipe.
+
[http://hackage.haskell.org/package/GPipe GPipe] is a library for programming the GPU (graphics processing unit). It is an alternative to using OpenGl, and has the advantage that it is functional and statically typed as opposed to OpenGl's inherently imperative style. Another important difference with OpenGl is that with GPipe you don't need to write shaders in a second shader language such as GLSL or Cg, but instead use regular Haskell functions on the GPU data types. GPipe uses the same conceptual model as OpenGl, so if you already know OpenGl, getting up to speed with GPipe is quick!
   
In GPipe, you'll primary work with these four types of data on the GPU:
 
  +
== Version 2 ==
* [http://hackage.haskell.org/packages/archive/GPipe/latest/doc/html/Graphics-GPipe-Stream-Primitive.html#t:PrimitiveStream <hask>PrimitiveStream</hask>]
 
* [http://hackage.haskell.org/packages/archive/GPipe/latest/doc/html/Graphics-GPipe-Stream-Fragment.html#t:FragmentStream <hask>FragmentStream</hask>]
 
* [http://hackage.haskell.org/packages/archive/GPipe/latest/doc/html/Graphics-GPipe-FrameBuffer.html#t:FrameBuffer <hask>FrameBuffer</hask>]
 
* [http://hackage.haskell.org/packages/archive/GPipe/1.1.3/doc/html/Graphics-GPipe-Texture.html <hask>Texture</hask>]
 
Let's walk our way through an simple example as I explain how you work with these types.
 
This page is formatted as a literate Haskell page, simply save it as &quot;<tt>box.lhs</tt>&quot; and then type
 
<pre>
 
ghc --make –O box.lhs
 
box
 
</pre>
 
at the prompt to see a spinning box. You’ll also need an image named &quot;<tt>myPicture.jpg</tt>&quot; in the same directory (I used a picture of some wooden planks).
 
   
<haskell>
 
  +
In 2015, a new major version of GPipe was realeased. Read the announcement [http://tobbebex.blogspot.se/2015/09/gpipe-is-dead-long-live-gpipe.html here on the GPipe blog]!
   
> module Main where
 
  +
== Examples and tutorials ==
 
> import Graphics.GPipe
 
> import Graphics.GPipe.Texture.Load
 
> import qualified Data.Vec as Vec
 
> import Data.Vec.Nat
 
> import Data.Vec.LinAlg.Transform3D
 
> import Data.Monoid
 
> import Data.IORef
 
> import Graphics.UI.GLUT
 
> (Window,
 
> mainLoop,
 
> postRedisplay,
 
> idleCallback,
 
> getArgsAndInitialize,
 
> ($=))
 
   
</haskell>
 
  +
A comprehensive tutorial in five parts is now available for GPipe 2:
   
Besides [http://hackage.haskell.org/package/GPipe GPipe], this example also uses the [http://hackage.haskell.org/package/Vec-Transform Vec-Transform package] for the transformation matrices, and the [http://hackage.haskell.org/package/GPipe-TextureLoad GPipe-TextureLoad package] for loading textures from disc. [http://hackage.haskell.org/package/GLUT GLUT] is used in GPipe for window management and the main loop.
 
  +
* [http://tobbebex.blogspot.se/2015/09/gpu-programming-in-haskell-using-gpipe.html Part 1 - Hello world]
  +
* [http://tobbebex.blogspot.se/2015/09/gpu-programming-in-haskell-using-gpipe_11.html Part 2 - Buffers and arrays]
  +
* [http://tobbebex.blogspot.se/2015/10/gpu-programming-in-haskell-using-gpipe.html Part 3 - Shaders and primitive streams]
  +
* [http://tobbebex.blogspot.se/2015/10/gpu-programming-in-haskell-using-gpipe_21.html Part 4 - Textures and samplers]
  +
* [http://tobbebex.blogspot.se/2015/11/gpu-programming-in-haskell-using-gpipe.html Part 5 - Drawing]
   
  +
* Quake 3 map viewer using GPipe 2, sources on [https://github.com/csabahruska/gpipe-quake3 GitHub].
   
== Creating a window ==
 
  +
Use at least version 2.1.3 for the examples in these.
   
We start by defining the <hask>main</hask> function.
 
  +
== GPipe 1 Examples and tutorials ==
   
<haskell>
 
  +
'' Note that these only applies to the older deprecated version of GPipe ''
   
> main :: IO ()
 
  +
* Wiki [[/Tutorial/]] that explains the basic principles of GPipe 1.
> main = do
 
  +
* [http://hackage.haskell.org/package/GPipe-Examples GPipe-Examples package], by Kree Cole-McLaughlin features a set of four examples with increasing complexity.
> getArgsAndInitialize
 
  +
* Csaba Hruska has made a Quake 3 map viewer using GPipe 1, sources on [https://github.com/csabahruska/GFXDemo GitHub].
> tex <- loadTexture RGB8 "myPicture.jpg"
 
> angleRef <- newIORef 0.0
 
> newWindow "Spinning box" (100:.100:.()) (800:.600:.()) (renderFrame tex angleRef) initWindow
 
> mainLoop
 
   
> renderFrame :: Texture2D RGBFormat -> IORef Float -> IO (FrameBuffer RGBFormat () ())
 
  +
== Sources ==
> renderFrame tex angleRef = do
 
> angle <- readIORef angleRef
 
> writeIORef angleRef ((angle + 0.01) `mod'` (2*pi))
 
> return $ cubeFrameBuffer tex angle
 
   
> initWindow :: Window -> IO ()
 
  +
All my GPipe related library sources are available on [http://github.com/tobbebex Github]. If you have something to contribute with, just send me a patch and I might merge it into the trunk.
> initWindow win = idleCallback $= Just (postRedisplay (Just win))
 
   
</haskell>
 
  +
== Other resources ==
   
First we set up GLUT, and load a texture from disc via the [http://hackage.haskell.org/package/GPipe-TextureLoad GPipe-TextureLoad package] function <hask>loadTexture</hask>. In this example we're going to animate a spinning box, and for that we put an angle in an <hask>IORef</hask> so that we can update it between frames. We then create a window with [http://hackage.haskell.org/packages/archive/GPipe/latest/doc/html/Graphics-GPipe-FrameBuffer.html#v:newWindow <hask>newWindow</hask>]. When the window is created, <hask>initWindow</hask> registers this window as being continously redisplayed in the idle loop. At each frame, the <hask>IO</hask> action <hask>renderFrame tex angleRef</hask> is run. In this function the angle is incremented with 0.01 (reseted each lap), and a <hask>FrameBuffer</hask> is created and returned to be displayed in the window. But before I explain <hask>FrameBuffer</hask>s, let's jump to the start of the graphics pipeline instead.
 
  +
=== GPipe 2 ===
   
  +
* [http://hackage.haskell.org/package/GPipe-GLFW GPipe-GLFW] is the first window management package for GPipe 2.
  +
* [http://hackage.haskell.org/package/linear linear] is the vector math package used by GPipe 2.
   
== PrimitiveStreams ==
 
  +
=== GPipe 1 ===
 
The graphics pipeline starts with creating primitives such as triangles on the GPU.Let's create a box with six sides, each made up of two triangles each.
 
 
<haskell>
 
 
> cube :: PrimitiveStream Triangle (Vec3 (Vertex Float), Vec3 (Vertex Float), Vec2 (Vertex Float))
 
> cube = mconcat [sidePosX, sideNegX, sidePosY, sideNegY, sidePosZ, sideNegZ]
 
 
> sidePosX = toGPUStream TriangleStrip $ zip3 [1:.0:.0:.(), 1:.1:.0:.(), 1:.0:.1:.(), 1:.1:.1:.()] (repeat (1:.0:.0:.())) uvCoords
 
> sideNegX = toGPUStream TriangleStrip $ zip3 [0:.0:.1:.(), 0:.1:.1:.(), 0:.0:.0:.(), 0:.1:.0:.()] (repeat ((-1):.0:.0:.())) uvCoords
 
> sidePosY = toGPUStream TriangleStrip $ zip3 [0:.1:.1:.(), 1:.1:.1:.(), 0:.1:.0:.(), 1:.1:.0:.()] (repeat (0:.1:.0:.())) uvCoords
 
> sideNegY = toGPUStream TriangleStrip $ zip3 [0:.0:.0:.(), 1:.0:.0:.(), 0:.0:.1:.(), 1:.0:.1:.()] (repeat (0:.(-1):.0:.())) uvCoords
 
> sidePosZ = toGPUStream TriangleStrip $ zip3 [1:.0:.1:.(), 1:.1:.1:.(), 0:.0:.1:.(), 0:.1:.1:.()] (repeat (0:.0:.1:.())) uvCoords
 
> sideNegZ = toGPUStream TriangleStrip $ zip3 [0:.0:.0:.(), 0:.1:.0:.(), 1:.0:.0:.(), 1:.1:.0:.()] (repeat (0:.0:.(-1):.())) uvCoords
 
 
> uvCoords = [0:.0:.(), 0:.1:.(), 1:.0:.(), 1:.1:.()]
 
 
</haskell>
 
 
Every side of the box is created from a normal list of four elements each, where each element is a tuple with three vectors: a position, a normal and an uv-coordinate. These lists of vertices are then turned into [http://hackage.haskell.org/packages/archive/GPipe/latest/doc/html/Graphics-GPipe-Stream-Primitive.html#t:PrimitiveStream <hask>PrimitiveStream</hask>]s on the GPU by [http://hackage.haskell.org/packages/archive/GPipe/latest/doc/html/Graphics-GPipe-Stream-Primitive.html#v:toGPUStream <hask>toGPUStream</hask>] that in our case creates triangle strips from the vertices, i.e 2 triangles from 4 vertices. Refer to the OpenGl specification on how triangle strips and the other topologies works.
 
 
All six sides are then concatenated together into a cube. We can see that the type of the cube is a <hask>PrimitiveStream</hask> of [http://hackage.haskell.org/packages/archive/GPipe/latest/doc/html/Graphics-GPipe-Stream-Primitive.html#t:Triangle <hask>Triangle</hask>]s where each vertex is a tuple of three vectors, just as the lists we started with. One big difference is that those vectors now are made up of <hask>Vertex Float</hask>s instead of <hask>Float</hask>s since they are now on the GPU.
 
 
The cube is defined in model-space, i.e where positions and normals are relative the cube. We now want to rotate that cube using a variable angle and project the whole thing with a perspective projection, as it is seen through a camera 2 units down the z-axis.
 
 
<haskell>
 
 
> transformedCube :: Float -> PrimitiveStream Triangle (Vec4 (Vertex Float), (Vec3 (Vertex Float), Vec2 (Vertex Float)))
 
> transformedCube angle = fmap (transform angle) cube
 
 
> transform angle (pos, norm, uv) = (transformedPos, (transformedNorm, uv))
 
> where
 
> modelMat = rotationVec (normalize (1:.0.5:.0.3:.())) angle `multmm` translation (-0.5)
 
> viewMat = translation (-(0:.0:.2:.()))
 
> projMat = perspective 1 100 (pi/3) (4/3)
 
> viewProjMat = projMat `multmm` viewMat
 
> transformedPos = toGPU (viewProjMat `multmm` modelMat) `multmv` homPoint pos
 
> transformedNorm = toGPU (Vec.map (Vec.take n3) $ Vec.take n3 $ modelMat) `multmv` norm
 
 
</haskell>
 
 
When applying a function on the <hask>PrimitiveStream</hask> using <hask>fmap</hask>, that function will be executed on the GPU using vertex shaders. The [http://hackage.haskell.org/packages/archive/GPipe/latest/doc/html/Graphics-GPipe-Stream.html#v:toGPU <hask>toGPU</hask>] function transforms normal values like <hask>Float</hask>s into GPU-values like <hask>Vertex Float</hask> so it can be used with the vertices of the <hask>PrimitiveStream</hask>.
 
 
 
== FragmentStreams ==
 
 
To render the primitives on the screen, we must first turn them into pixel fragments. This called rasterization and in our example done by the function [http://hackage.haskell.org/packages/archive/GPipe/latest/doc/html/Graphics-GPipe-Stream-Fragment.html#v:rasterizeFront <hask>rasterizeFront</hask>], which transforms <hask>PrimitiveStream</hask>s into [http://hackage.haskell.org/packages/archive/GPipe/latest/doc/html/Graphics-GPipe-Stream-Fragment.html#t:FragmentStream <hask>FragmentStream</hask>]s.
 
 
<haskell>
 
 
> rasterizedCube :: Float -> FragmentStream (Vec3 (Fragment Float), Vec2 (Fragment Float))
 
> rasterizedCube angle = rasterizeFront $ transformedCube angle
 
 
</haskell>
 
 
In the rasterization process, values of type <hask>Vertex Float</hask> are turned into values of type <hask>Fragment Float</hask>.
 
 
For each fragment, we now want to give it a color from the texture we initially loaded, as well as light it with a directional light coming from the camera.
 
 
<haskell>
 
 
> litCube :: Texture2D RGBFormat -> Float -> FragmentStream (Color RGBFormat (Fragment Float))
 
> litCube tex angle = fmap (enlight tex) $ rasterizedCube angle
 
 
> enlight tex (norm, uv) = RGB (c * Vec.vec (norm `dot` toGPU (0:.0:.1:.())))
 
> where RGB c = sample (Sampler Linear Wrap) tex uv
 
 
</haskell>
 
 
Using <hask>fmap</hask> on a <hask>FragmentStream</hask> will execute a function on the GPU using fragment shaders. The function [http://hackage.haskell.org/packages/archive/GPipe/1.1.3/doc/html/Graphics-GPipe-Texture.html#v:sample <hask>sample</hask>] is used for sampling the texture we have loaded, using the fragment's interpolated uv-coordinates and a sampler state.
 
 
Once we have a <hask>FragmentStream</hask> of <hask>Color</hask>s, we can paint those fragments onto a <hask>FrameBuffer</hask>.
 
 
 
== FrameBuffers ==
 
 
A [http://hackage.haskell.org/packages/archive/GPipe/latest/doc/html/Graphics-GPipe-FrameBuffer.html#t:FrameBuffer <hask>FrameBuffer</hask>] is a 2D image in which fragments from <hask>FragmentStream</hask>s are painted. A <hask>FrameBuffer</hask> may contain any combination of a color buffer, a depth buffer and a stencil buffer. Besides being shown in windows, <hask>FrameBuffer</hask>s may also be saved to memory or converted to textures, thus enabling multi pass rendering. A <hask>FrameBuffer</hask> has no defined size, but take the size of the window when shown, or are given a size when saved to memory or converted to a texture.
 
 
And so finally, we paint the fragments we have created onto a black <hask>FrameBuffer</hask>. By this we use [http://hackage.haskell.org/packages/archive/GPipe/latest/doc/html/Graphics-GPipe-FrameBuffer.html#v:paintColor <hask>paintColor</hask>] without any blending or color masking.
 
 
<haskell>
 
 
> cubeFrameBuffer :: Texture2D RGBFormat -> Float -> FrameBuffer RGBFormat () ()
 
> cubeFrameBuffer tex angle = paintSolid (litCube tex angle) emptyFrameBuffer
 
 
> paintSolid = paintColor NoBlending (RGB $ Vec.vec True)
 
> emptyFrameBuffer = newFrameBufferColor (RGB 0)
 
 
</haskell>
 
 
This <hask>FrameBuffer</hask> is the one we return from the <hask>renderFrame</hask> action we defined at the top.
 
 
 
== Screenshot ==
 
 
[[Image:box.jpg]]
 
   
  +
* [http://hackage.haskell.org/package/GLUT GLUT] is used in GPipe 1 for window management and the main loop.
  +
* [http://hackage.haskell.org/package/Vec Vec package] is the vector math package used by GPipe 1.
  +
* [http://hackage.haskell.org/package/GPipe-TextureLoad GPipe-TextureLoad package] helps loading textures from disc.
  +
* [http://hackage.haskell.org/package/GPipe-Collada GPipe-Collada package] makes it possible to use Collada files with GPipe.
   
 
== Questions and feedback ==
 
== Questions and feedback ==
   
 
If you have any questions or suggestions, feel free to [mailto:tobias_bexelius@hotmail.com mail] me. I'm also interested in seeing some use cases from the community, as complex or trivial they may be.
 
If you have any questions or suggestions, feel free to [mailto:tobias_bexelius@hotmail.com mail] me. I'm also interested in seeing some use cases from the community, as complex or trivial they may be.
  +
  +
[[Category:3D]]
  +
[[Category:Graphics]]
  +
[[Category:Libraries]]
  +
[[Category:Packages]]

Latest revision as of 15:27, 6 January 2016

What is GPipe?

GPipe is a library for programming the GPU (graphics processing unit). It is an alternative to using OpenGl, and has the advantage that it is functional and statically typed as opposed to OpenGl's inherently imperative style. Another important difference with OpenGl is that with GPipe you don't need to write shaders in a second shader language such as GLSL or Cg, but instead use regular Haskell functions on the GPU data types. GPipe uses the same conceptual model as OpenGl, so if you already know OpenGl, getting up to speed with GPipe is quick!

Version 2

In 2015, a new major version of GPipe was realeased. Read the announcement here on the GPipe blog!

Examples and tutorials

A comprehensive tutorial in five parts is now available for GPipe 2:

  • Quake 3 map viewer using GPipe 2, sources on GitHub.

Use at least version 2.1.3 for the examples in these.

GPipe 1 Examples and tutorials

Note that these only applies to the older deprecated version of GPipe

  • Wiki Tutorial that explains the basic principles of GPipe 1.
  • GPipe-Examples package, by Kree Cole-McLaughlin features a set of four examples with increasing complexity.
  • Csaba Hruska has made a Quake 3 map viewer using GPipe 1, sources on GitHub.

Sources

All my GPipe related library sources are available on Github. If you have something to contribute with, just send me a patch and I might merge it into the trunk.

Other resources

GPipe 2

  • GPipe-GLFW is the first window management package for GPipe 2.
  • linear is the vector math package used by GPipe 2.

GPipe 1

Questions and feedback

If you have any questions or suggestions, feel free to mail me. I'm also interested in seeing some use cases from the community, as complex or trivial they may be.