# GPipe/Tutorial

### From HaskellWiki

(Removing Vec-Transform references) |
Yuvallanger (Talk | contribs) m ("you'll primary work with these" -> "you'll primarily work with these") |
||

(3 intermediate revisions by one user not shown) | |||

Line 1: | Line 1: | ||

== Introduction == |
== Introduction == |
||

− | In GPipe, you'll primary work with these four types of data on the GPU: |
+ | In GPipe, you'll primarily work with these four types of data on the GPU: |

* [http://hackage.haskell.org/packages/archive/GPipe/latest/doc/html/Graphics-GPipe-Stream-Primitive.html#t:PrimitiveStream <code>PrimitiveStream</code>] |
* [http://hackage.haskell.org/packages/archive/GPipe/latest/doc/html/Graphics-GPipe-Stream-Primitive.html#t:PrimitiveStream <code>PrimitiveStream</code>] |
||

* [http://hackage.haskell.org/packages/archive/GPipe/latest/doc/html/Graphics-GPipe-Stream-Fragment.html#t:FragmentStream <code>FragmentStream</code>] |
* [http://hackage.haskell.org/packages/archive/GPipe/latest/doc/html/Graphics-GPipe-Stream-Fragment.html#t:FragmentStream <code>FragmentStream</code>] |
||

Line 7: | Line 7: | ||

* [http://hackage.haskell.org/packages/archive/GPipe/latest/doc/html/Graphics-GPipe-Texture.html <code>Texture</code>] |
* [http://hackage.haskell.org/packages/archive/GPipe/latest/doc/html/Graphics-GPipe-Texture.html <code>Texture</code>] |
||

− | Let's walk our way through an simple example as I explain how you work with these types. This example requires GPipe version 1.4 or later. |
+ | Let's walk our way through an simple example as I explain how you work with these types. This example requires GPipe version 1.4.1 or later. |

This page is formatted as a literate Haskell page, simply save it as "<tt>box.lhs</tt>" and then type |
This page is formatted as a literate Haskell page, simply save it as "<tt>box.lhs</tt>" and then type |
||

<pre> |
<pre> |
||

Line 23: | Line 23: | ||

> import qualified Data.Vec as Vec |
> import qualified Data.Vec as Vec |
||

> import Data.Vec.Nat |
> import Data.Vec.Nat |
||

− | > import Data.Vec.LinAlg |
||

> import Data.Monoid |
> import Data.Monoid |
||

> import Data.IORef |
> import Data.IORef |
||

Line 37: | Line 36: | ||

Besides [http://hackage.haskell.org/package/GPipe GPipe], this example also uses 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. |
Besides [http://hackage.haskell.org/package/GPipe GPipe], this example also uses 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. |
||

− | |||

== Creating a window == |
== Creating a window == |
||

Line 105: | Line 103: | ||

> viewProjMat = projMat `multmm` viewMat |
> viewProjMat = projMat `multmm` viewMat |
||

> transformedPos = toGPU (viewProjMat `multmm` modelMat) `multmv` (homPoint pos :: Vec4 (Vertex Float)) |
> transformedPos = toGPU (viewProjMat `multmm` modelMat) `multmv` (homPoint pos :: Vec4 (Vertex Float)) |
||

− | > transformedNorm = toGPU (Vec.map (Vec.take n3) $ Vec.take n3 $ modelMat) `multmv` norm |
+ | > transformedNorm = toGPU (Vec.map (Vec.take n3) $ Vec.take n3 modelMat) `multmv` norm |

</haskell> |
</haskell> |
||

Line 114: | Line 112: | ||

== FragmentStreams == |
== 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 <code>rasterizeFront</code>], which transforms <hask>PrimitiveStream</hask>s into [http://hackage.haskell.org/packages/archive/GPipe/latest/doc/html/Graphics-GPipe-Stream-Fragment.html#t:FragmentStream <code>FragmentStream</code>]s. |
+ | To render the primitives on the screen, we must first turn them into pixel fragments. This is 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 <code>rasterizeFront</code>], which transforms <hask>PrimitiveStream</hask>s into [http://hackage.haskell.org/packages/archive/GPipe/latest/doc/html/Graphics-GPipe-Stream-Fragment.html#t:FragmentStream <code>FragmentStream</code>]s. |

<haskell> |
<haskell> |
||

Line 140: | Line 138: | ||

Once we have a <hask>FragmentStream</hask> of <hask>Color</hask>s, we can paint those fragments onto a <hask>FrameBuffer</hask>. |
Once we have a <hask>FragmentStream</hask> of <hask>Color</hask>s, we can paint those fragments onto a <hask>FrameBuffer</hask>. |
||

− | |||

== FrameBuffers == |
== FrameBuffers == |

## Latest revision as of 08:50, 19 April 2014

## Contents |

## [edit] 1 Introduction

In GPipe, you'll primarily work with these four types of data on the GPU:

Let's walk our way through an simple example as I explain how you work with these types. This example requires GPipe version 1.4.1 or later.
This page is formatted as a literate Haskell page, simply save it as "`box.lhs`" and then type

ghc --make –O box.lhs box

at the prompt to see a spinning box. You’ll also need an image named "`myPicture.jpg`" in the same directory (I used a picture of some wooden planks).

> module Main where > import Graphics.GPipe > import Graphics.GPipe.Texture.Load > import qualified Data.Vec as Vec > import Data.Vec.Nat > import Data.Monoid > import Data.IORef > import Graphics.UI.GLUT > (Window, > mainLoop, > postRedisplay, > idleCallback, > getArgsAndInitialize, > ($=))

Besides GPipe, this example also uses the GPipe-TextureLoad package for loading textures from disc. GLUT is used in GPipe for window management and the main loop.

## [edit] 2 Creating a window

We start by defining the> main :: IO () > main = do > getArgsAndInitialize > 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 -> Vec2 Int -> IO (FrameBuffer RGBFormat () ()) > renderFrame tex angleRef size = do > angle <- readIORef angleRef > writeIORef angleRef ((angle + 0.005) `mod'` (2*pi)) > return $ cubeFrameBuffer tex angle size > initWindow :: Window -> IO () > initWindow win = idleCallback $= Just (postRedisplay (Just win))

`newWindow`

. When the window is created,

## [edit] 3 PrimitiveStreams

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.

> 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:.()]

Every side of the box is created from a regular 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 `PrimitiveStream`

s on the GPU by `toGPUStream`

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.

`Triangle`

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 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.

> transformedCube :: Float -> Vec2 Int -> PrimitiveStream Triangle (Vec4 (Vertex Float), (Vec3 (Vertex Float), Vec2 (Vertex Float))) > transformedCube angle size = fmap (transform angle size) cube > transform angle (width:.height:.()) (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) (fromIntegral width / fromIntegral height) > viewProjMat = projMat `multmm` viewMat > transformedPos = toGPU (viewProjMat `multmm` modelMat) `multmv` (homPoint pos :: Vec4 (Vertex Float)) > transformedNorm = toGPU (Vec.map (Vec.take n3) $ Vec.take n3 modelMat) `multmv` norm

`toGPU`

function transforms normal values like

## [edit] 4 FragmentStreams

To render the primitives on the screen, we must first turn them into pixel fragments. This is called rasterization and in our example done by the function`rasterizeFront`

, which transforms `FragmentStream`

s.
> rasterizedCube :: Float -> Vec2 Int -> FragmentStream (Vec3 (Fragment Float), Vec2 (Fragment Float)) > rasterizedCube angle size = rasterizeFront $ transformedCube angle size

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.

> litCube :: Texture2D RGBFormat -> Float -> Vec2 Int -> FragmentStream (Color RGBFormat (Fragment Float)) > litCube tex angle size = fmap (enlight tex) $ rasterizedCube angle size > enlight tex (norm, uv) = RGB (c * Vec.vec (norm `dot` toGPU (0:.0:.1:.()))) > where RGB c = sample (Sampler Linear Wrap) tex uv

`sample`

is used for sampling the texture we have loaded, using the fragment's interpolated uv-coordinates and a sampler state.
Once we have a ## [edit] 5 FrameBuffers

A`FrameBuffer`

is a 2D image in which fragments from `paintColor`

without any blending or color masking.
> cubeFrameBuffer :: Texture2D RGBFormat -> Float -> Vec2 Int -> FrameBuffer RGBFormat () () > cubeFrameBuffer tex angle size = paintSolid (litCube tex angle size) emptyFrameBuffer > paintSolid = paintColor NoBlending (RGB $ Vec.vec True) > emptyFrameBuffer = newFrameBufferColor (RGB 0)