diff options
Diffstat (limited to 'Spear/GLSL/Management.hs')
-rw-r--r-- | Spear/GLSL/Management.hs | 297 |
1 files changed, 297 insertions, 0 deletions
diff --git a/Spear/GLSL/Management.hs b/Spear/GLSL/Management.hs new file mode 100644 index 0000000..81cf45f --- /dev/null +++ b/Spear/GLSL/Management.hs | |||
@@ -0,0 +1,297 @@ | |||
1 | module Spear.GLSL.Management | ||
2 | ( | ||
3 | -- * Data types | ||
4 | GLSLShader | ||
5 | , GLSLProgram | ||
6 | , ShaderType(..) | ||
7 | -- * Program manipulation | ||
8 | , newProgram | ||
9 | , releaseProgram | ||
10 | , linkProgram | ||
11 | , useProgram | ||
12 | , withGLSLProgram | ||
13 | -- * Shader manipulation | ||
14 | , attachShader | ||
15 | , detachShader | ||
16 | , loadShader | ||
17 | , newShader | ||
18 | , releaseShader | ||
19 | -- ** Source loading | ||
20 | , loadSource | ||
21 | , shaderSource | ||
22 | , readSource | ||
23 | , compile | ||
24 | -- * Location | ||
25 | , attribLocation | ||
26 | , fragLocation | ||
27 | , uniformLocation | ||
28 | -- * Helper functions | ||
29 | , ($=) | ||
30 | , Data.StateVar.get | ||
31 | ) | ||
32 | where | ||
33 | |||
34 | |||
35 | import Spear.Setup | ||
36 | |||
37 | import Control.Monad ((<=<), forM) | ||
38 | import Control.Monad.Trans.State as State | ||
39 | import Control.Monad.Trans.Error | ||
40 | import Control.Monad.Trans.Class | ||
41 | import Control.Monad (mapM_, when) | ||
42 | import qualified Data.ByteString.Char8 as B | ||
43 | import Data.StateVar | ||
44 | import Foreign.Ptr | ||
45 | import Foreign.Storable | ||
46 | import Foreign.C.String | ||
47 | import Foreign.Marshal.Alloc (alloca) | ||
48 | import Foreign.Marshal.Array (withArray) | ||
49 | import Graphics.Rendering.OpenGL.Raw.Core31 | ||
50 | import System.Directory (doesFileExist, getCurrentDirectory, setCurrentDirectory) | ||
51 | import Unsafe.Coerce | ||
52 | |||
53 | |||
54 | -- | Represents a GLSL shader handle. | ||
55 | data GLSLShader = GLSLShader | ||
56 | { getShader :: GLuint | ||
57 | , getShaderKey :: Resource | ||
58 | } | ||
59 | |||
60 | |||
61 | -- | Represents a GLSL program handle. | ||
62 | data GLSLProgram = GLSLProgram | ||
63 | { getProgram :: GLuint | ||
64 | , getProgramKey :: Resource | ||
65 | } | ||
66 | |||
67 | |||
68 | -- | Encodes several shader types. | ||
69 | data ShaderType = VertexShader | FragmentShader deriving (Eq, Show) | ||
70 | |||
71 | |||
72 | toGLShader :: ShaderType -> GLenum | ||
73 | toGLShader VertexShader = gl_VERTEX_SHADER | ||
74 | toGLShader FragmentShader = gl_FRAGMENT_SHADER | ||
75 | |||
76 | |||
77 | -- | Apply the given function to the GLSLProgram's id. | ||
78 | withGLSLProgram :: GLSLProgram -> (GLuint -> a) -> a | ||
79 | withGLSLProgram prog f = f $ getProgram prog | ||
80 | |||
81 | |||
82 | -- | Get the location of the given uniform variable within the given program. | ||
83 | uniformLocation :: GLSLProgram -> String -> GettableStateVar GLint | ||
84 | uniformLocation prog var = makeGettableStateVar get | ||
85 | where | ||
86 | get = withCString var $ \str -> glGetUniformLocation (getProgram prog) (unsafeCoerce str) | ||
87 | |||
88 | |||
89 | -- | Get or set the location of the given variable to a fragment shader colour number. | ||
90 | fragLocation :: GLSLProgram -> String -> StateVar GLint | ||
91 | fragLocation prog var = makeStateVar get set | ||
92 | where | ||
93 | get = withCString var $ \str -> glGetFragDataLocation (getProgram prog) (unsafeCoerce str) | ||
94 | set idx = withCString var $ \str -> | ||
95 | glBindFragDataLocation (getProgram prog) (unsafeCoerce idx) (unsafeCoerce str) | ||
96 | |||
97 | |||
98 | -- | Get or set the location of the given attribute within the given program. | ||
99 | attribLocation :: GLSLProgram -> String -> StateVar GLint | ||
100 | attribLocation prog var = makeStateVar get set | ||
101 | where | ||
102 | get = withCString var $ \str -> glGetAttribLocation (getProgram prog) (unsafeCoerce str) | ||
103 | set idx = withCString var $ \str -> | ||
104 | glBindAttribLocation (getProgram prog) (unsafeCoerce idx) (unsafeCoerce str) | ||
105 | |||
106 | |||
107 | -- | Create a 'GLSLProgram'. | ||
108 | newProgram :: [GLSLShader] -> Setup GLSLProgram | ||
109 | newProgram shaders = do | ||
110 | h <- setupIO glCreateProgram | ||
111 | when (h == 0) $ setupError "glCreateProgram failed" | ||
112 | rkey <- register $ deleteProgram h | ||
113 | let program = GLSLProgram h rkey | ||
114 | |||
115 | mapM_ (setupIO . attachShader program) shaders | ||
116 | linkProgram program | ||
117 | |||
118 | return program | ||
119 | |||
120 | |||
121 | -- | Release the given 'GLSLProgram'. | ||
122 | releaseProgram :: GLSLProgram -> Setup () | ||
123 | releaseProgram = release . getProgramKey | ||
124 | |||
125 | |||
126 | -- | Delete the given 'GLSLProgram'. | ||
127 | deleteProgram :: GLuint -> IO () | ||
128 | --deleteProgram = glDeleteProgram | ||
129 | deleteProgram prog = do | ||
130 | putStrLn $ "Deleting shader program " ++ show prog | ||
131 | glDeleteProgram prog | ||
132 | |||
133 | |||
134 | -- | Link the given GLSL program. | ||
135 | linkProgram :: GLSLProgram -> Setup () | ||
136 | linkProgram prog = do | ||
137 | let h = getProgram prog | ||
138 | err <- setupIO $ do | ||
139 | glLinkProgram h | ||
140 | alloca $ \statptr -> do | ||
141 | glGetProgramiv h gl_LINK_STATUS statptr | ||
142 | status <- peek statptr | ||
143 | case status of | ||
144 | 0 -> getStatus glGetProgramiv glGetProgramInfoLog h | ||
145 | _ -> return "" | ||
146 | |||
147 | case length err of | ||
148 | 0 -> return () | ||
149 | _ -> setupError err | ||
150 | |||
151 | |||
152 | -- | Use the given GLSL program. | ||
153 | useProgram :: GLSLProgram -> IO () | ||
154 | useProgram prog = glUseProgram $ getProgram prog | ||
155 | |||
156 | |||
157 | -- | Attach the given GLSL shader to the given GLSL program. | ||
158 | attachShader :: GLSLProgram -> GLSLShader -> IO () | ||
159 | attachShader prog shader = glAttachShader (getProgram prog) (getShader shader) | ||
160 | |||
161 | |||
162 | -- | Detach the given GLSL shader from the given GLSL program. | ||
163 | detachShader :: GLSLProgram -> GLSLShader -> IO () | ||
164 | detachShader prog shader = glDetachShader (getProgram prog) (getShader shader) | ||
165 | |||
166 | |||
167 | -- | Load a shader from the file specified by the given string. | ||
168 | -- | ||
169 | -- This function creates a new shader. To load source code into an existing shader, | ||
170 | -- see 'loadSource', 'shaderSource' and 'readSource'. | ||
171 | loadShader :: FilePath -> ShaderType -> Setup GLSLShader | ||
172 | loadShader file shaderType = do | ||
173 | shader <- newShader shaderType | ||
174 | loadSource file shader | ||
175 | compile file shader | ||
176 | return shader | ||
177 | |||
178 | |||
179 | -- | Create a new shader. | ||
180 | newShader :: ShaderType -> Setup GLSLShader | ||
181 | newShader shaderType = do | ||
182 | h <- setupIO $ glCreateShader (toGLShader shaderType) | ||
183 | case h of | ||
184 | 0 -> setupError "glCreateShader failed" | ||
185 | _ -> do | ||
186 | rkey <- register $ deleteShader h | ||
187 | return $ GLSLShader h rkey | ||
188 | |||
189 | |||
190 | -- | Release the given 'GLSLShader'. | ||
191 | releaseShader :: GLSLShader -> Setup () | ||
192 | releaseShader = release . getShaderKey | ||
193 | |||
194 | |||
195 | -- | Free the given shader. | ||
196 | deleteShader :: GLuint -> IO () | ||
197 | --deleteShader = glDeleteShader | ||
198 | deleteShader shader = do | ||
199 | putStrLn $ "Deleting shader " ++ show shader | ||
200 | glDeleteShader shader | ||
201 | |||
202 | |||
203 | -- | Load a shader source from the file specified by the given string into the given shader. | ||
204 | loadSource :: FilePath -> GLSLShader -> Setup () | ||
205 | loadSource file h = do | ||
206 | exists <- setupIO $ doesFileExist file | ||
207 | case exists of | ||
208 | False -> setupError "the specified shader file does not exist" | ||
209 | True -> setupIO $ do | ||
210 | code <- readSource file | ||
211 | withCString code $ shaderSource h | ||
212 | |||
213 | |||
214 | -- | Load the given shader source into the given shader. | ||
215 | shaderSource :: GLSLShader -> CString -> IO () | ||
216 | shaderSource shader str = | ||
217 | let ptr = unsafeCoerce str | ||
218 | in withArray [ptr] $ flip (glShaderSource (getShader shader) 1) nullPtr | ||
219 | |||
220 | |||
221 | -- | Compile the given shader. | ||
222 | compile :: FilePath -> GLSLShader -> Setup () | ||
223 | compile file shader = do | ||
224 | let h = getShader shader | ||
225 | |||
226 | -- Compile | ||
227 | setupIO $ glCompileShader h | ||
228 | |||
229 | -- Verify status | ||
230 | err <- setupIO $ alloca $ \statusPtr -> do | ||
231 | glGetShaderiv h gl_COMPILE_STATUS statusPtr | ||
232 | result <- peek statusPtr | ||
233 | case result of | ||
234 | 0 -> getStatus glGetShaderiv glGetShaderInfoLog h | ||
235 | _ -> return "" | ||
236 | |||
237 | case length err of | ||
238 | 0 -> return () | ||
239 | _ -> setupError $ "Unable to compile shader " ++ file ++ ":\n" ++ err | ||
240 | |||
241 | |||
242 | type StatusCall = GLuint -> GLenum -> Ptr GLint -> IO () | ||
243 | type LogCall = GLuint -> GLsizei -> Ptr GLsizei -> Ptr GLchar -> IO () | ||
244 | |||
245 | |||
246 | getStatus :: StatusCall -> LogCall -> GLuint -> IO String | ||
247 | getStatus getStatus getLog h = do | ||
248 | alloca $ \lenPtr -> do | ||
249 | getStatus h gl_INFO_LOG_LENGTH lenPtr | ||
250 | len <- peek lenPtr | ||
251 | case len of | ||
252 | 0 -> return "" | ||
253 | _ -> withCString (replicate (unsafeCoerce len) '\0') $ getErrorString getLog h (unsafeCoerce len) | ||
254 | |||
255 | |||
256 | getErrorString :: LogCall -> GLuint -> GLsizei -> CString -> IO String | ||
257 | getErrorString getLog h len str = do | ||
258 | let ptr = unsafeCoerce str | ||
259 | getLog h len nullPtr ptr | ||
260 | peekCString str | ||
261 | |||
262 | |||
263 | -- | Load the shader source specified by the given file. | ||
264 | -- | ||
265 | -- This function implements an #include mechanism, so the given file can | ||
266 | -- refer to other files. | ||
267 | readSource :: FilePath -> IO String | ||
268 | readSource = fmap B.unpack . readSource' | ||
269 | |||
270 | |||
271 | readSource' :: FilePath -> IO B.ByteString | ||
272 | readSource' file = do | ||
273 | let includeB = B.pack "#include" | ||
274 | newLineB = B.pack "\n" | ||
275 | isInclude = ((==) includeB) . B.take 8 | ||
276 | clean = B.dropWhile (\c -> c == ' ') | ||
277 | cleanInclude = B.filter (\c -> c /= '"') . B.dropWhile (\c -> c /= ' ') | ||
278 | toLines = B.splitWith (\c -> c == '\n' || c == '\r') | ||
279 | addNewLine s = if (B.last s /= '\n') then B.append s newLineB else s | ||
280 | parse = fmap B.concat . (fmap . fmap $ flip B.append newLineB) . sequence . | ||
281 | fmap (processLine . clean) . toLines | ||
282 | processLine l = | ||
283 | if isInclude l | ||
284 | then readSource' $ B.unpack . clean . cleanInclude $ l | ||
285 | else return l | ||
286 | |||
287 | contents <- B.readFile file | ||
288 | |||
289 | dir <- getCurrentDirectory | ||
290 | let dir' = dir ++ "/" ++ (reverse . dropWhile (\c -> c /= '/') . reverse) file | ||
291 | |||
292 | setCurrentDirectory dir' | ||
293 | code <- parse contents | ||
294 | setCurrentDirectory dir | ||
295 | |||
296 | return code | ||
297 | |||