----------------------------------------------------------------------------- -- | -- Copyright : (C) 2015 Dimitri Sabadie -- License : BSD3 -- -- Maintainer : Dimitri Sabadie <dimitri.sabadie@gmail.com> -- Stability : experimental -- Portability : portable -- -- = What is luminance? -- -- __luminance__ is a small yet powerful graphics API. It was designed so that people can quickly -- get their feet wet and start having fun with graphics in /Haskell/. The main idea is to unleash -- the graphical and visual properties of /GPUs/ in a stateless and type-safe way. -- -- This library doesn’t expose any architectural patterns or designs. It’s up to you to design your -- program as you want and following your own plans. Because it’s a graphics and rendering API, you -- won’t find several common things you find in animations, games or simulations. If you need those -- you’ll have to look for dedicated libraries instead. -- -- One of the most important thing you have to keep in mind is the fact that luminance won’t -- provide you with anything else than working with the /GPU/. That is, it won’t even provide -- functions to open windows. That’s actually a good thing, because then you’ll be able to use it -- with /any kind of windowing and system library you want to/! -- -- The drawback is about safety. If you screw up setting up the OpenGL context, there’s no way -- luminance will work. If users feel the need, a few dedicated packages will be uploaded, like -- __luminance-glfw__ to add "GLFW-b" support for instance. -- -- = Getting started -- -- == Setting up the window and OpenGL context -- -- The first thing to do is to create a window. Here’s a typical "GLFW-b" snippet to create such -- a window. -- -- @ -- initialized <- init -- when initialized $ do -- @ -- -- In the first place, we initialize the "GLFW-b" library and make sure everything ran smoothly. -- -- @ -- windowHint (WindowHint'Resizable False) -- windowHint (WindowHint'ContextVersionMajor 4) -- windowHint (WindowHint'ContextVersionMinor 5) -- windowHint (WindowHint'OpenGLForwardCompat False) -- windowHint (WindowHint'OpenGLProfile OpenGLProfile'Core) -- window <- createWindow 800 600 "luminance application" Nothing Nothing -- @ -- -- That part just setup the window’s OpenGL hints so that we create a compatible context for -- luminance. luminance will work with __OpenGL 4.5__ only, don’t even try to make it work with a -- lower implementation. We also disable the /forward compatibility/ because we don’t need it and -- ask to stick to a /core/ profile. -- -- @ -- case window of -- Just window' -> do -- makeContextCurrent window -- swapInterval 1 -- -- we’re good to go! -- destroyWindow window' -- Nothing -> hPutStrLn stderr "unable to create window; please check your hardware support OpenGL4.5" -- @ -- -- We then test 'window'. If we have successfully opened the window, we go on by making the OpenGL -- context of the window the current one for the current thread, set the swap interval (that’s not -- important for the purpose of that tutorial) and we’re good to go. Otherwise, we just display an -- error message and quit. -- -- @ -- terminate -- @ -- -- We finally close the "GLFW-b" context to cleanup everything. -- -- == Preparing the environment for luminance -- -- A lot of the functions you’ll use work in special types. For instance, a lot of @create*@ -- functions will require @('MonadIO' m,'MonadResource' m,'MonadError' e m) => m@ or so. For that -- reason, we’ll be using a type of our own and will unwrap it so that we end up in 'IO' in the end. -- -- Here’s the type: -- -- @ -- type App = ExceptT AppError (ResourceT IO) -- -- newtype AppError = AppError String deriving (Eq,Show) -- @ -- -- And we’ll unwrap from our type to 'IO' with: -- -- @ -- runResourceT . runExcepT -- @ -- -- = Getting something to the screen -- -- == About the screen -- -- luminance generalizes OpenGL concepts so that they’re made safer. In order to render something -- onto the screen, you have to understand what the screen truly is. It’s actually… a back buffer – -- assuming we have double buffering enabled, which the case with "GLFW-b" by default. So rendering -- to the screen is the same thing than rendering to the back buffer and ask "GLFW-b" to swap the -- back buffer with the front buffer. -- -- And guess what. luminance wraps the back buffer into a 'Framebuffer' object. You can access it -- through 'defaultFramebuffer'. That value will always represent the back buffer. -- -- == About batched rendering -- -- In most graphics frameworks, rendering is the act of taking an object and getting it rendered. -- luminance follows a different path. Because of real world needs and, well, real applications, you -- cannot do that in luminance. Because, what serious application will render only __one__ object? -- None. If so, then it’s an exception. We shouldn’t design our libraries and interface for the -- exceptions. We should build them for the most used case, which is, having a lot of objects in a -- scene. -- -- That’s why luminance exposes the concept of /batched rendering/. The idea is that you have to -- gather you objects in /batches/ and render them all at once. That enables a correct sharing of -- resources – for instance, framebuffers or textures – and is very straight-forward to reason -- about. -- -- luminance has several types of batches, each for the type of shared information. You can – up to -- now – shared two information between the rendered objects: -- -- * /framebuffer/: that means you can create a 'FBBatch' that will gather several values under the -- same 'Framebuffer'; -- * or /shaders/: that means you can create a 'SPBatch' that will gather several values under the -- same shader 'Program'. -- -- The idea is that the 'SPBatch'es are stored in 'FBBatch'es. That creates a structure similar to -- an AST luminance knows how to dispatch to the GPU. -- -- == About shader stages -- -- luminance supports five kinds of shader stage: -- -- * /tessellation evaluation shader/ -- * /tessellation control shader/ -- * /vertex shader/ -- * /geometry shader/ -- * /fragment shader/ -- -- Additionnaly, you can create /compute shaders/ but they’re not usable up to now. -- -- When creating a new shader, you have to pass a 'String' representing the source code. This will -- change in the end. An EDSL is planned to make things easier and safer, but in the waiting, you -- are stuck with 'String', I’m sorry. -- -- You have to write /GLSL450/-conformant code. -- -- == About uniforms -- -- Shaders are customized through uniforms. Those are very handy and very simple to use in -- luminance. You have the possibility to get them when creating shader 'Program's. The -- 'createProgram' function expects two parameters: a list of shader 'Stage's and a uniform -- interface builder function. That function takes another function as parameter you can use to -- retrieve a uniform 'U' by passing 'Either' a 'String' for the name of the uniform or a 'Natural' -- for its explicit semantic. Be careful when using explicit semantics though; they’re not tested. -- -- Here’s an exemple of such a use: -- -- @ -- (program,uniformInterface) \<- 'createProgram' shaderStages $ \\uni -\> do -- resolutionU <- uni $ 'Left' "resolution" -- timeU <- uni $ 'Left' "time" -- 'pure' $ 'divided' resolutionU timeU -- @ -- -- In that example, @uniformInterface@ has type @U ((Float,Float),Float)@, @(Float,Float@ being the -- type of the @resolutionU@ part and @Float@ being the part for @timeU@. 'divided' is a method of -- 'Divisible' – the typeclass of divisible contravariant functors – which is defined in the -- "contravariant" package. -- -- If you don’t need uniform interface, you can build a dummy object, like @()@, or simply use the -- appropriate 'createProgram_' function. -- -- == About 'RenderCmd' and 'Geometry' -- -- 'RenderCmd' is a very simple type yet powerful one. It’s a way to add stateless support to -- OpenGL render commands – draw commands, actually. It gathers several information you can set -- when performing a draw command. A 'RenderCmd' can hold any type of object, but the most useful -- version of it holds 'Geometry'. -- -- A 'Geometry' is a /GPU/ version of a mesh. It’s composed of /vertices/, /indices/ and a primitive -- mode used to know how to link vertices between each others. Sometimes, 'Geometry' doesn’t have -- /indices/. That’s called __direct geometry__, because the /vertices/ are supposed to be directly -- used when creating primitives. If you use /indices/, then you have an __indexed geometry__ and -- the /vertices/ can linked by looking at the /indices/ you’ve fed in. -- -- A 'Geometry' is created with the 'createGeometry' function and a 'RenderCmd' is created with -- 'renderCmd'. You’re supposed to create a 'Geometry' once – while loading your resources for -- example – and the 'RenderCmd' can be created on the fly – it doesn’t require 'IO'. -- -- == Putting all together -- -- Let’s draw a triangle on the screen! First, we need the vertices! -- -- @ -- vertices :: ['V' 2 'Float'] -- vertices = -- [ -- 'vec2' (-0.5) (-0.5) -- , 'vec2' 0 0.5 -- , 'vec2' 0.5 (-0.5) -- ] -- @ -- -- @'V' 2@ is a cool type used to represent /vertex attributes/. You’ll need `DataKinds` to be able -- to use it. -- -- Then, we don’t need /indices/ because we can directly issue a draw. Let’s then have the /GPU/ -- version of those vertices: -- -- @ -- triangle <- 'createGeometry' vertices 'Nothing' 'Triangle' -- @ -- -- Then, we need a shader! Let’s write the vertex shader first: -- -- @ -- in vec2 co; -- out vec4 vertexColor; -- -- vec4 color[3] = vec4[]( -- vec4(1., 0., 0., 1.) -- , vec4(0., 1., 0., 1.) -- , vec4(0., 0., 1., 1.) -- ); -- -- void main() { -- gl_Position = vec4(co, 0., 1.); -- vertexColor = color[gl_VertexID]; -- } -- @ -- -- Nothing fancy, except that we pass @vertexColor@ to the next stage so that we can blend between -- vertices. -- -- Now, a fragment shader: -- -- @ -- in vec4 vertexColor; -- out vec4 frag; -- -- void main() { -- frag = vertexColor; -- } -- @ -- -- Now, let’s create the shader 'Stage's and the shader 'Program': -- -- @ -- program \<- 'sequenceA' ['createVertexShader' vsSrc,'createFragmentShader' fsSrc] \>\>= createProgram_ -- @ -- -- Once again, that’s pretty straight-forward. -- -- Finally, we need the batches. We’ll need one 'FBBatch' and one 'SPBatch'. -- -- @ -- let spb = 'shaderProgramBatch_' program ['stdRenderCmd_' triangle] -- fbb = 'framebufferBatch' 'defaultFramebuffer' ['anySPBatch' spb] -- @ -- -- Ok, so let’s explain all of this. 'shaderProgramBatch_' is a shorter version of -- 'shaderProgramBatch' you can use to build 'SPBatch'. The extra underscore means you don’t want no -- uniform interface. We pass our @program@ and a singleton list containing a 'RenderCmd' we create -- with the 'stdRenderCmd_'. Once again, the extra underscore stands for no uniform interface. We -- then just pass our @triangle@. Notice that both 'stdRenderCmd' and 'stdRenderCmd_' disable color -- blending and enable depth test so that you don’t have to pass those information around. -- -- Then, we create the 'FBBatch'. That is done via the 'framebufferBatch' function. It takes the -- 'Framebuffer' to render into – in our case, the 'defaultFramebuffer', which is the /back buffer/. -- We also pass a singleton list of the universally quantified 'SPBatch' with the 'anySPBatch' -- function. -- -- We just need to issue a command to the /GPU/ to render our triangle. That is done with a -- constrained type, 'Cmd'. -- -- @ -- void . runCmd $ draw fbb -- @ -- -- We don’t need the result of 'runCmd' in our case so we discard it with 'void'. 'runCmd' runs in -- 'MonadIO'. -- -- We just need to swap the buffers with @swapBuffers window@ – see "GLFW-b" for further details – -- and we’re good! -- -- = Dealing with 'Texture2D' -- -- Up to now, luminance only supports 2D-textures. More texture types will be added as luminance -- gets mature. The interface might change a lot, because it might be very inefficient, especially -- when converting from containers to others. ----------------------------------------------------------------------- module Graphics.Luminance ( module Graphics.Luminance.Batch , module Graphics.Luminance.Blending , module Graphics.Luminance.Buffer , module Graphics.Luminance.Cmd , module Graphics.Luminance.Core.Tuple , module Graphics.Luminance.Framebuffer , module Graphics.Luminance.Geometry , module Graphics.Luminance.Pixel , module Graphics.Luminance.Query , module Graphics.Luminance.RenderCmd , module Graphics.Luminance.RW , module Graphics.Luminance.Shader , module Graphics.Luminance.Texture , module Graphics.Luminance.Vertex ) where import Graphics.Luminance.Batch import Graphics.Luminance.Blending import Graphics.Luminance.Buffer import Graphics.Luminance.Cmd import Graphics.Luminance.Core.Tuple import Graphics.Luminance.Framebuffer import Graphics.Luminance.Geometry import Graphics.Luminance.Pixel import Graphics.Luminance.Query import Graphics.Luminance.RenderCmd import Graphics.Luminance.RW import Graphics.Luminance.Shader import Graphics.Luminance.Texture import Graphics.Luminance.Vertex