I'm trying to get a basic "draw a triangle on the screen" example working, but in Haskell with the gl and GLFW-b packages.
My code is here:
-- https://learnopengl.com/#!Getting-started/Hello-Triangle
-- stuff from base
import Control.Monad (when, mapM)
import Foreign -- includes Ptr and Marshal, among other things.
-- we qualify these names so we can tell what's from GLFW
import qualified Graphics.UI.GLFW as GLFW
-- gl funcs all already have "gl" in their name
import Graphics.GL
width = 800 :: Int
height = 600 :: Int
vertexShaderSource = [
"#version 330 core\n",
"layout (location = 0) in vec3 position;\n",
"void main()\n",
"{\n",
"gl_Position = vec4(position.x, position.y, position.z, 1.0);\n",
"}\n"]
fragmentShaderSource = [
"#version 330 core\n",
"out vec4 color;\n",
"void main()\n",
"{\n",
"color = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n",
"}\n"]
getSrcPointer :: [String] -> IO (Ptr (Ptr GLchar), Ptr GLint)
getSrcPointer sourceLines = do
let glcharLines = map (map (fromIntegral.fromEnum)) sourceLines
let linesLengths = map (fromIntegral.length) glcharLines
linesPtrs <- mapM newArray glcharLines
linesPtrsPtr <- newArray linesPtrs
lengthsPtr <- newArray linesLengths
return (linesPtrsPtr,lengthsPtr)
-- type KeyCallback = Window -> Key -> Int -> KeyState -> ModifierKeys -> IO ()
callback :: GLFW.KeyCallback
callback window key scanCode keyState modKeys = do
putStrLn (show key)
when (key == GLFW.Key'Escape && keyState == GLFW.KeyState'Pressed)
(GLFW.setWindowShouldClose window True)
main :: IO ()
main = do
-- init GLFW and set the appropriate options
_ <- GLFW.init
GLFW.windowHint (GLFW.WindowHint'ContextVersionMajor 3)
GLFW.windowHint (GLFW.WindowHint'ContextVersionMinor 3)
GLFW.windowHint (GLFW.WindowHint'OpenGLProfile GLFW.OpenGLProfile'Core)
GLFW.windowHint (GLFW.WindowHint'Resizable False)
-- create our window
maybeWindow <- GLFW.createWindow width height "Lesson 02" Nothing Nothing
case maybeWindow of
Nothing -> do
-- somehow we failed. Nothing to do but report that and quit.
putStrLn "Failed to create a GLFW window!"
GLFW.terminate
Just window -> do
-- set our context and callback
GLFW.makeContextCurrent (Just window)
GLFW.setKeyCallback window (Just callback)
-- define the viewport dimensions
(frameWidth,frameHeight) <- GLFW.getFramebufferSize window
glViewport 0 0 (fromIntegral frameWidth) (fromIntegral frameHeight)
-- build and compile our shader program.
successP <- malloc
-- vertex shader
vertexShader <- glCreateShader GL_VERTEX_SHADER
(linesPtrsPtr,lengthsPtr) <- getSrcPointer vertexShaderSource
glShaderSource vertexShader 1 linesPtrsPtr lengthsPtr
glCompileShader vertexShader
-- check for compilation errors
glGetShaderiv vertexShader GL_COMPILE_STATUS successP
success <- peek successP
when (success == 0) $ do
putStrLn "Vertex Shader Compile Error:"
let infoLength = 512
resultP <- malloc
infoLog <- mallocArray (fromIntegral infoLength)
glGetShaderInfoLog vertexShader (fromIntegral infoLength) resultP infoLog
result <- fromIntegral <$> peek resultP
logBytes <- peekArray result infoLog
putStrLn (map (toEnum.fromEnum) logBytes)
-- fragment shader
fragmentShader <- glCreateShader GL_FRAGMENT_SHADER
(linesPtrsPtr,lengthsPtr) <- getSrcPointer fragmentShaderSource
glShaderSource fragmentShader 1 linesPtrsPtr lengthsPtr
glCompileShader fragmentShader
-- check for compilation errors
glGetShaderiv fragmentShader GL_COMPILE_STATUS successP
success <- peek successP
when (success == 0) $ do
putStrLn "Fragment Shader Compile Error:"
let infoLength = 512
resultP <- malloc
infoLog <- mallocArray (fromIntegral infoLength)
glGetShaderInfoLog fragmentShader (fromIntegral infoLength) resultP infoLog
result <- fromIntegral <$> peek resultP
logBytes <- peekArray result infoLog
putStrLn (map (toEnum.fromEnum) logBytes)
-- link up the shaders
shaderProgram <- glCreateProgram
glAttachShader shaderProgram vertexShader
glAttachShader shaderProgram fragmentShader
glLinkProgram shaderProgram
-- check for linking errors
glGetProgramiv shaderProgram GL_LINK_STATUS successP
success <- peek successP
when (success == 0) $ do
putStrLn "Program Linking Error:"
let infoLength = 512
resultP <- malloc
infoLog <- mallocArray (fromIntegral infoLength)
glGetProgramInfoLog shaderProgram (fromIntegral infoLength) resultP infoLog
result <- fromIntegral <$> peek resultP
logBytes <- peekArray result infoLog
putStrLn (map (toEnum.fromEnum) logBytes)
-- cleanup the sub-programs now that our complete shader program is ready
glDeleteShader vertexShader
glDeleteShader fragmentShader
-- setup vertex data and attribute pointers
verticesP <- newArray ([
-0.5, -0.5, 0.0, -- Left
0.5, -0.5, 0.0, -- Right
0.0, 0.5, 0.0 -- Top
] :: [GLfloat])
let verticesSize = fromIntegral $ sizeOf (0.0 :: GLfloat) * 9
vboP <- malloc :: IO (Ptr GLuint)
vaoP <- malloc :: IO (Ptr GLuint)
glGenVertexArrays 1 vaoP
glGenBuffers 1 vboP
-- Bind the Vertex Array Object first, then bind and set vertex buffer(s) and attribute pointer(s).
vao <- peek vaoP
glBindVertexArray vao
vbo <- peek vboP
glBindBuffer GL_ARRAY_BUFFER vbo
glBufferData GL_ARRAY_BUFFER verticesSize (castPtr verticesP) GL_STATIC_DRAW
let threeFloats = fromIntegral $ sizeOf (0.0::GLfloat) * 3
glVertexAttribPointer 0 3 GL_FLOAT GL_FALSE threeFloats nullPtr
glEnableVertexAttribArray 0
-- Note that this is allowed, the call to glVertexAttribPointer
-- registered VBO as the currently bound vertex buffer object so
-- afterwards we can safely unbind
glBindBuffer GL_ARRAY_BUFFER 0
-- Unbind VAO (it's always a good thing to unbind any buffer/array
-- to prevent strange bugs)
glBindVertexArray 0
-- "game loop"
let loop :: IO ()
loop = do
shouldClose <- GLFW.windowShouldClose window
if shouldClose
then return ()
else do
-- event poll
GLFW.pollEvents
-- clear the screen
glClearColor 0.2 0.3 0.3 1.0
glClear GL_COLOR_BUFFER_BIT
-- draw a triangle
glUseProgram shaderProgram
glBindVertexArray vao
glDrawArrays GL_TRIANGLES 0 3
glBindVertexArray 0
-- swap buffers and go again
GLFW.swapBuffers window
loop
loop
-- clean up the gl resources
glDeleteVertexArrays 1 vaoP
glDeleteBuffers 1 vboP
-- clean up the GLFW resources
GLFW.terminate
It compiles and runs without any reported errors, but it only shows the clear color; the triangle isn't drawn at all.
Note that when I compile and run your program, I get shader compile and program link errors. Specifically, I get errors:
Vertex Shader Compile Error:
0:1(18): error: syntax error, unexpected $end
Fragment Shader Compile Error:
0:1(18): error: syntax error, unexpected $end
and there are link errors too, of course.
On closer inspection, your glShaderSource calls are flawed. Though they are passing arrays of line pointers and line lengths for six lines per shader, the line count argument is 1. If I modify the last line of getSrcPointer to return the line count:
return (linesPtrsPtr,lengthsPtr, (fromIntegral.length) linesLengths)
and then pass this count to the glShaderSource calls in each case:
(linesPtrsPtr,lengthsPtr,count) <- getSrcPointer vertexShaderSource
glShaderSource vertexShader count linesPtrsPtr lengthsPtr
...
(linesPtrsPtr,lengthsPtr,count) <- getSrcPointer fragmentShaderSource
glShaderSource fragmentShader count linesPtrsPtr lengthsPtr
then your program works fine.
Happy triangling!
Related
I'm using two textures on two different objects that each have their own shader program. However, the second object renders with both textures applied and with some strange distortion.
I will focus on the second object's code as the first object and texture renders correctly.
The first texture is loaded like this:
-- Icosahedron texture
dice_textureP <- malloc
glGenTextures 1 dice_textureP
diceText <- peek dice_textureP
glBindTexture GL_TEXTURE_2D diceText
glEnable GL_BLEND
glBlendFunc GL_SRC_ALPHA GL_ONE_MINUS_SRC_ALPHA
glGenerateMipmap GL_TEXTURE_2D
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MAG_FILTER GL_LINEAR
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MIN_FILTER GL_LINEAR
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_S GL_REPEAT
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_T GL_REPEAT
eErrDI0 <- readImage "app/numbers.png"
dyImage0 <- case eErrDI0 of
Left e -> do
putStrLn e
return $ ImageRGBA8 $ generateImage (\x y ->
let x' = fromIntegral x in (PixelRGBA8 x' x' x' x')) 500 400
Right di -> return di
let ipixelrgba80 = convertRGBA8 dyImage0
iWidth0 = fromIntegral $ imageWidth ipixelrgba80
iHeight0 = fromIntegral $ imageHeight ipixelrgba80
iData0 = imageData ipixelrgba80
VS.unsafeWith iData0 $ \dataP ->
glTexImage2D GL_TEXTURE_2D 0 GL_RGBA iWidth0 iHeight0 0 GL_RGBA GL_UNSIGNED_BYTE (castPtr dataP)
glGenerateMipmap GL_TEXTURE_2D
glBindTexture GL_TEXTURE_2D 0
The second texture is loaded like this:
card_oneP <- malloc
glGenTextures 1 card_oneP
card_oneTexture <- peek card_oneP
glBindTexture GL_TEXTURE_2D card_oneTexture
glDisable GL_BLEND
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MAG_FILTER GL_NEAREST_MIPMAP_NEAREST
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MIN_FILTER GL_NEAREST_MIPMAP_NEAREST
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_S GL_CLAMP_TO_EDGE
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_T GL_CLAMP_TO_EDGE
eErrDI1 <- readImage "app/card_one.jpg"
dyImage1 <- case eErrDI1 of
Left e -> do
putStrLn e
return $ ImageRGBA8 $ generateImage (\x y ->
let x' = fromIntegral x in (PixelRGBA8 x' x' x' x')) 312 445
Right di -> return di
let ipixelrgb81 = convertRGBA8 dyImage1
iWidth1 = fromIntegral $ imageWidth ipixelrgb81
iHeight1 = fromIntegral $ imageHeight ipixelrgb81
iData1 = imageData ipixelrgb81
VS.unsafeWith iData1 $ \dataP ->
glTexImage2D GL_TEXTURE_2D 0 GL_RGBA iWidth0 iHeight0 0 GL_RGBA GL_UNSIGNED_BYTE (castPtr dataP)
glGenerateMipmap GL_TEXTURE_2D
glBindTexture GL_TEXTURE_2D 0
I bind the first texture before drawing the first object like this:
-- first vao and shader program
glUseProgram ico_shaderProgram
glBindVertexArray ico_vao
-- bind first texture
glActiveTexture GL_TEXTURE0
glBindTexture GL_TEXTURE_2D diceText
diceTextureLocation <- glGetUniformLocation ico_shaderProgram diceTexture
glUniform1i diceTextureLocation 0
After calling glDrawArrays on the first object, I unbind the first texture using glBindTexture_2D 0.
The second texture is bound in a similar fashion:
-- second vao and shader
glUseProgram card_shaderProgram
glBindVertexArray card_vao
-- bind second texture
glActiveTexture GL_TEXTURE0
glBindTexture GL_TEXTURE_2D card_oneTexture
cardTextureLocation <- glGetUniformLocation card_shaderProgram cardTexture
glUniform1i cardTextureLocation 0
I call glBindTexture_2D 0 after the draw before re-entering the render loop.
The result looks like this:
As you can see, the object on the right has a few artifacts... The second texture is rendering on top of the first texture (which should only be on the first object). Moreover, the second texture is all distorted.
The verticies and uv coords for the second object can be found here (gist).
The shader used for this object is here (gist).
The attributes for the second object are set up like so:
-- second object attribs
glVertexAttribPointer 0 3 GL_FLOAT GL_FALSE (5*floatSize) nullPtr
glEnableVertexAttribArray 0
let threeFloatOffset = castPtr $ plusPtr nullPtr (fromIntegral $ 3*floatSize)
glVertexAttribPointer 1 2 GL_FLOAT GL_FALSE (5*floatSize) threeFloatOffset
glEnableVertexAttribArray 1
glBindVertexArray 0
I've tried to include the most relevant code. I would be very grateful if someone can spot my error. I am 100% willing to post more code if needed.
UPDATE:
Someone in the comments kindly pointed out that my code was using incorrect width and height values for the second texture. I am using this function to load in images now:
loadImageTexture :: [Char] -> Int -> Int -> IO (Image PixelRGBA8)
loadImageTexture filePath w h = do
errorOrDyImage <- readImage filePath
dyImage <- case errorOrDyImage of
Left e -> do
putStrLn e
return $ ImageRGBA8 $ generateImage (\x y ->
let x' = fromIntegral x in (PixelRGBA8 x' x' x' x')) w h
Right di -> return di
return $ convertRGBA8 dyImage
As someone pointed out in the comments, iWidth0 and iHeight0 (width and height for the first texture) are incorrectly called for the second texture:
glTexImage2D GL_TEXTURE_2D 0 GL_RGBA iWidth0 iHeight0 0 GL_RGBA GL_UNSIGNED_BYTE (castPtr dataP)
It should be:
glTexImage2D GL_TEXTURE_2D 0 GL_RGBA iWidth1 iHeight1 0 GL_RGBA GL_UNSIGNED_BYTE (castPtr dataP)
Creating a function to handle loading textures would have avoided this problem from the start.
I create simple haskell-opengl project. It is build successful using commands:
cabal configure
cabal build
When I use stack:
stack build
I get error:
GLUT-2.7.0.1: configure
Progress
Configuring GLUT-2.7.0.1...
Setup.hs: Missing dependency on a foreign library:
* Missing C library: glut32
This problem can usually be solved by installing the system package that
provides this library (you may need the "-dev" version). If the library is
already installed but in a non-standard location then you can use the flags
--extra-include-dirs= and --extra-lib-dirs= to specify where it is.
How to make use stack glut.dll?
module Main where
import Graphics.UI.GLUT
import Foreign.Marshal.Array
import Foreign.Ptr
import Foreign.Storable()
import Foreign.C.Types()
import qualified Data.ByteString as BS
import Control.Monad
data State = State
{
vertexBuffer :: BufferObject,
gpuProgram :: Program
}
triangleVertexes :: [GLfloat]
triangleVertexes = [
0.0, 0.5, 0.0, 1.0,
0.5, -0.366, 0.0, 1.0,
-0.5, -0.366, 0.0, 1.0,
1.0, 0.0, 0.0, 1.0,
0.0, 1.0, 0.0, 1.0,
0.0, 0.0, 1.0, 1.0
]
main :: IO ()
main = do
(progName, _) <- getArgsAndInitialize
initialDisplayMode $= [ DoubleBuffered, RGBAMode, WithAlphaComponent, WithDepthBuffer ]
_ <- createWindow progName
state <- initializeState
displayCallback $= display state
reshapeCallback $= Just (reshape state)
mainLoop
fragmentShaderFilePath :: FilePath
fragmentShaderFilePath = "shader.frag"
vertexShaderFilePath :: FilePath
vertexShaderFilePath = "shader.vert"
createVertexBuffer :: [GLfloat] -> IO BufferObject
createVertexBuffer vertexes = do
bufferObject <- genObjectName
bindBuffer ArrayBuffer $= Just bufferObject
withArrayLen vertexes $ \count arr ->
bufferData ArrayBuffer $= (fromIntegral count * 4, arr, StaticDraw)
enableAttribLocations [0, 1]
setAttribPointers
return bufferObject
vertexNumComponents :: NumComponents
vertexNumComponents = 4
colorNumComponents :: NumComponents
colorNumComponents = 4
initializeState :: IO State
initializeState = do
bufferObject <- createVertexBuffer triangleVertexes
program <- initGPUProgram
return State
{
vertexBuffer = bufferObject,
gpuProgram = program
}
loadShader :: ShaderType -> FilePath -> IO Shader
loadShader t path = do
shader <- createShader t
source <- BS.readFile path
shaderSourceBS shader $= source
compileShader shader
status <- get (compileStatus shader)
unless status $ putStrLn . (("message" ++ " log: ") ++) =<< get (shaderInfoLog shader)
return shader
initGPUProgram :: IO Program
initGPUProgram = do
vertexShader <- loadShader VertexShader vertexShaderFilePath
fragmentShader <- loadShader FragmentShader fragmentShaderFilePath
let shaders = [vertexShader, fragmentShader]
program <- createProgram
mapM_ (attachShader program) shaders
linkProgram program
mapM_ (detachShader program) shaders
return program
display :: State -> DisplayCallback
display state = do
clearColor $= Color4 1.0 0.0 1.0 1.0
clear [ ColorBuffer ]
bindBuffer ArrayBuffer $= Just (vertexBuffer state)
enableAttribLocations [0, 1]
setAttribPointers
currentProgram $= Just (gpuProgram state)
drawArrays Triangles 0 3
disableAttribLocations [0, 1]
swapBuffers
checkError "display"
setCapabilityForAttribLocations :: Capability -> [GLuint] -> IO ()
setCapabilityForAttribLocations capability =
mapM_ (\location -> vertexAttribArray (AttribLocation location) $= capability)
disableAttribLocations :: [GLuint] -> IO ()
disableAttribLocations = setCapabilityForAttribLocations Disabled
enableAttribLocations :: [GLuint] -> IO ()
enableAttribLocations = setCapabilityForAttribLocations Enabled
setAttribPointers :: IO ()
setAttribPointers = do
vertexAttribPointer (AttribLocation 0) $= (ToFloat, VertexArrayDescriptor vertexNumComponents Float 0 nullPtr)
vertexAttribPointer (AttribLocation 1) $= (ToFloat, VertexArrayDescriptor colorNumComponents Float 0 (plusPtr nullPtr 48))
reshape :: State -> ReshapeCallback
reshape _ size =
viewport $= (Position 0 0, size)
checkError :: String -> IO ()
checkError functionName = get errors >>= mapM_ reportError
where reportError e = putStrLn (showError e ++ " detected in " ++ functionName)
showError (Error category message) = "GL error " ++ show category ++ " (" ++ message ++ ")"
-- fragment shader
#version 330
smooth in vec4 theColor;
out vec4 outputColor;
void main()
{
outputColor = theColor;
}
-- vertex shader
#version 330
layout (location = 0) in vec4 position;
layout (location = 1) in vec4 color;
smooth out vec4 theColor;
void main()
{
gl_Position = position + vec4(0.5, 0.5, 0.0, 1.0);
theColor = color;
}
After watching a video of a talk by Bret Victor, I was inspired to write a quick hack that was somewhat similar to a development environment he demonstrated in the talk.
Basically the idea is, one has the app running in one window and whenever one saves a change in a source file the program changes.
This works great for small changes except that I can't change the type of the state in my code without shutting down the app and recompiling.
How can I solve the expression problem and have the data type of my state be able to change
without causing a recompile?
P.S.
Here's the code.
I originally didn't want to post because it was really messy and quickly hacked together, but people wanted it so they can get it.
First the display and the idle module, (this was a quick hack so I didn't figure out how to do them as real modules).
Idle.hs
\state -> do
counter <- readIORef state
writeIORef state ((counter + 1)`mod`3)
postRedisplay Nothing
Display.hs
\state -> let
cube w = do
renderPrimitive Quads $ do
vertex $ Vertex3 w w w
vertex $ Vertex3 w w (-w)
vertex $ Vertex3 w (-w) (-w)
vertex $ Vertex3 w (-w) w
vertex $ Vertex3 w w w
vertex $ Vertex3 w w (-w)
vertex $ Vertex3 (-w) w (-w)
vertex $ Vertex3 (-w) w w
vertex $ Vertex3 w w w
vertex $ Vertex3 w (-w) w
vertex $ Vertex3 (-w) (-w) w
vertex $ Vertex3 (-w) w w
vertex $ Vertex3 (-w) w w
vertex $ Vertex3 (-w) w (-w)
vertex $ Vertex3 (-w) (-w) (-w)
vertex $ Vertex3 (-w) (-w) w
vertex $ Vertex3 w (-w) w
vertex $ Vertex3 w (-w) (-w)
vertex $ Vertex3 (-w) (-w) (-w)
vertex $ Vertex3 (-w) (-w) w
vertex $ Vertex3 w w (-w)
vertex $ Vertex3 w (-w) (-w)
vertex $ Vertex3 (-w) (-w) (-w)
vertex $ Vertex3 (-w) w (-w)
points :: Integer -> [(GLfloat,GLfloat,GLfloat)]
points n' = let n = fromIntegral n' in map (\k -> let t = 2*pi*k/n in (sin(t),cos(t),0.0)) [1..n]
in do
clear [ ColorBuffer ]
counter <- readIORef state
mapM_ (\(x,y,z) -> preservingMatrix $ do
color $ Color3 ((x+1.0)/2.0) ((y+1.0)/2.0) ((z+1.0)/2.0)
translate $ Vector3 x y z
cube (0.3::GLfloat)
) $ points (9 + counter)
flush
The main module
module Main where
import Control.Monad
import Data.Typeable as Typeable
import System.IO
import Data.IORef
import Graphics.Rendering.OpenGL
import Graphics.UI.GLUT
import Language.Haskell.Interpreter
main :: IO ()
main = do
(_, _) <- getArgsAndInitialize
createWindow "Hello World"
action <- newIORef $ do
clear [ ColorBuffer ]
flush
let imports = ["Prelude", "Data.IORef", "Graphics.Rendering.OpenGL", "Graphics.UI.GLUT"]
let modules = ["State"]
runFile (undefined :: IORef Integer -> IO ()) "Display.hs" imports $ \displayCode ->
runFile (undefined :: IORef Integer -> IO ()) "Idle.hs" imports $ \idleCode -> do
state <- newIORef 12
displayCallback $= display displayCode state
idleCallback $= Just (idle displayCode idleCode state)
mainLoop
display displayCode state = do
f <- execute displayCode
f state
idle displayCode idleCode state = do
update displayCode
update idleCode
f <- execute idleCode
f state
instance Eq GhcError where
GhcError s == GhcError t = s == t
instance Eq InterpreterError where
UnknownError s == UnknownError t = s == t
WontCompile s == WontCompile t = s == t
NotAllowed s == NotAllowed t = s == t
GhcException s == GhcException t = s == t
data V a = V {
update :: IO (),
execute :: IO a
}
runFile :: Typeable a => a -> String -> [String] -> (V a -> IO ()) -> IO ()
runFile theType file imports f = do
currentError <- newIORef Nothing
currentAction <- newIORef Nothing
let v = V {
update = do
fileContents <- readFile file
result <- runInterpreter $ do
setImports imports
interpret fileContents theType
oldError <- readIORef currentError
case result of
Right newAction -> do
when (oldError /= Nothing) $ do
writeIORef currentError Nothing
putStrLn (file ++ " Ok!")
writeIORef currentAction (Just newAction)
Left newError -> do
when ((Just newError) /= oldError) $ do
writeIORef currentError (Just newError)
print newError
, execute = do
action <- readIORef currentAction
case action of
Nothing -> do
err <- readIORef currentError
return (error (show err))
Just act -> return act
}
update v
f v
I'm pretty sure it is impossible in GHC. When Haskell is compiled, the higher level language is desugared into Core, which is also typed. GHC will not initiate the transformation into Core until the program has been typed checked. There's a reason for this, too: as the program type checks it simultaneously proves itself. As jberryman noted, the only work around would be to have a flexible type for State which would allow polymorphism, so a type change might not register as one.
When I create two windows and redraw them in two different threads (one per window), it seems like all drawing goes to first created window. It constantly switches between what should be displayed in both windows. And second one remains mostly black.
The code was working well with only one window, and then I updated it - inserted currentWindow $= Just glWindow in the beginning of the functions which set callbacks and call rendering methods.
What do you think is the cause of the problems?
EDIT:
Code skeleton:
module Chart.Window where
import Graphics.UI.GLUT hiding (Window, postRedisplay, <etc>)
import qualified Graphics.UI.GLUT as GLUT
import qualified Graphics.Rendering.OpenGL as GL
data Window = Window
{ glWindow :: GLUT.Window
, viewListRef :: IORef [Line]
}
main = do
forkOS start <params1>
forkOS start <params2>
start <params> = do
win <- new <params>
run win
mainLoop
new :: Strict -> (Int, Int) -> (Int, Int) -> IO Window
new name (posx, posy) (w, h) = do
initGLUT
glWindow <- createWindow name
currentWindow $= Just glWindow
windowSize $= Size (fromIntegral w) (fromIntegral h)
windowPosition $= Position (fromIntegral posx) (fromIntegral posy)
return Window {..}
initGLUT :: IO ()
initGLUT = do
beenInit <- get initState
unless beenInit $ void getArgsAndInitialize
initialDisplayMode $= [WithDepthBuffer, DoubleBuffered, RGBAMode]
initialWindowSize $= Size 100 100
initialWindowPosition $= Position 100 100
actionOnWindowClose $= ContinueExectuion
run :: Window -> IO ()
run win#Window{..} = do
-- this will fork (with forkIO) threads
-- which will call "repaint" when chart needs to be updated
initListeners repaint
initCallbacks win
where
repaint :: [Line] -> IO ()
repaint viewList = do
writeIORef viewListRef viewList
postRedisplay win
postRedisplay Window{..} = GLUT.postRedisplay $ Just glWindow
initCallbacks win#Window{..} = do
currentWindow $= Just glWindow
GLUT.displayCallback $= display win
GLUT.idleCallback $= Just (postRedisplay win)
display Window{..} = do
currentWindow $= Just glWindow
Size w h <- get windowSize
viewList <- readIORef viewListRef
drawChart viewList otherOptions
reshapeCallback :: Window -> GLUT.ReshapeCallback
reshapeCallback win#Window{..} size#(Size w h) = do
currentWindow $= Just glWindow
GL.viewport $= (Position 0 0, size)
GL.matrixMode $= GL.Projection
GL.loadIdentity
GL.ortho2D 0 (fromIntegral w) 0 (fromIntegral h)
GL.matrixMode $= GL.Modelview 0
GL.loadIdentity
... -- send command for listener thread to change internal state and postRedisplay
drawChart viewList otherOptions = do
...
-- chart consists of several horizontal panels. Call this for each panel:
glViewport 0 panelYPosition width winHeight
glScissor 0 panelYPosition (fromIntegral width) (fromIntegral panelHeight)
GL.clear [GL.ColorBuffer]
...
-- and then for each line=(Vertex2 v1 v2) from viewList
GL.renderPrimitive GL.Lines $ do
GL.vertex v1
GL.vertex v2
...
BTW, when I commented the line which sets reshapeCallback (and window is reshaped at the beginning) and launched charting with only one window, I got exactly the same effect as in multi-window launch. I mean, the (only) window was mostly empty as if it was secondly created.
I had a similar problem. I work with a thread that calculates the iterations of a genetic algorithm and in each iteration I call to "GL.postRedisplay (Just window)" but it didn't draw anything.
I solved my problem by calling "GL.postRedisplay (Just window)" from the idle function:
idle window = CC.threadDelay (1000*500) >> GL.postRedisplay (Just window)
Don't forget to setup your idle callback function like this:
GL.idleCallback GL.$= Just (idle window) >>
CC and GL mean:
import qualified Control.Concurrent as CC
import qualified Graphics.UI.GLUT as GL
I have a procedure that (a) does some IO, (b) constructs a lookup table, and (c) returns an IO action that uses the lookup table. But when compiled with -O, GHC (version 6.12.1) inlines the construction the lookup table, so that it is reevaluated for every call of the IO action.
Example:
module Main where
import Data.Array
import Data.IORef
import Control.Monad
makeAction getX getY sumRef = do
x <- getX
let a = listArray (0, 1000) [x ..]
return $ do
y <- getY
modifyIORef sumRef (\sum -> sum + a ! y)
main = do
sumRef <- newIORef 0
action <- makeAction getX getY sumRef
replicateM_ 100000 action
n <- readIORef sumRef
putStrLn (show n)
where
getX = return (1 :: Int)
getY = return 0
Is this issue well-known enough to have a standard GHC-foolproof workaround - or how would you adjust the program so that a isn't repeatedly being allocated?
The easiest workaround is to force evaluation by using strictness annotations.
{-# LANGUAGE BangPatterns #-}
Then force allocation by simply making a strict using a ! ("bang").
let !a = listArray (0, 1000) [x ..]
Alternatively, if you are working in the IO monad, strictness annotations may not always help. To force evaluation of an expression before some IO action is run, you can use evaluate. For example:
let a = listArray (0, 1000) [x ..]
evaluate a
Try forcing a when constructing the monadic value to return:
makeAction getX getY sumRef = do
x <- getX
let a = listArray (0, 1000) [x ..]
return $ a `seq` do
y <- getY
modifyIORef sumRef (\sum -> sum + a ! y)