{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreiedftxaznog6fueuujimfamwkohluoxxv5t45g5b77i4winahrfum",
"uri": "at://did:plc:pi6woz4d47bkuws673w2il2r/app.bsky.feed.post/3mivsb3bpvzi2"
},
"path": "/t/shader-pipeline-primshader-and-random-face-colours/13904#post_1",
"publishedAt": "2026-04-07T10:03:40.000Z",
"site": "https://discourse.haskell.org",
"textContent": "While building my toy rasterizer I was thinking that I could implement various stages like so:\n\n\n type VertexShader = ViewSpace -> ViewSpace\n type PrimShader = forall f. Functor f => f ViewSpace -> f ViewSpace\n type MeshShader = forall t f. (Traversable t, Functor f) => t (f ViewSpace) -> t (f ViewSpace)\n type FragmentShader = ProjectionSpace -> ProjectionSpace\n\n data Shader = Shader {\n getVertexShader :: VertexShader,\n getPrimShader :: PrimShader,\n getMeshShader :: MeshShader,\n getFragmentShader :: FragmentShader\n }\n\n noShaders :: Shader\n noShaders = Shader id id id id\n\n addShader :: Shader -> Shader -> Shader\n addShader (Shader v0 p0 m0 f0) (Shader v1 p1 m1 f1) =\n Shader (v0 . v1) (p0 . p1) (m0 . m1) (f0 . f1)\n\n\nAnd use them in a pipeline like so:\n\n\n getObjFaces :: Dimensions -> ModelSpaceTransform -> Projection a -> CameraTransform -> Shader -> ObjFile -> [[ScreenSpace]]\n getObjFaces dims modelTransform projection camera shader obj =\n let allFaces = snd <$> Map.elems (objFaces obj)\n allVerts = fmap someFunc allFaces\n someFunc faceVec = concatMap (objShaderPipeline shader modelTransform projection camera obj) (V.toList faceVec)\n\n in [toScreenSpace dims <$> verts | verts <- allVerts, all pointIsVisible verts]\n\n objShaderPipeline :: Shader -> ModelSpaceTransform -> Projection a -> CameraTransform -> ObjFile -> Face ObjFaceInfo -> [ProjectionSpace]\n objShaderPipeline (Shader vertexShader primShader _ fragmentShader) modelTransform projection camera obj (Face p1 p2 p3 pn) =\n let faceInfoList = p1 : p2 : p3 : pn\n vs = objVertices obj\n points =\n fmap\n (fragmentShader . toProjection projection)\n faceVertices\n faceVertices =\n Debug.traceShowWith (fmap (getVertexColour . getViewSpace)) . primShader . fmap\n ( vertexShader\n . toViewSpace camera\n . toModelView modelTransform\n . (vs V.!)\n . subtract 1\n . objFaceVertexIndex\n ) $\n faceInfoList\n in points\n\n\n\n\nThe problem I’m having is implementing a `PrimShader` that randomly sets all the vertices on one face to one specfic `VertexColour `using `unsafePerformIO`. This colour would ideally be generated every time the shader is called:\n\n\n genCol :: IO VertexColour\n genCol = VertexColour\n <$> randomRIO (0, 255)\n <*> randomRIO (0, 255)\n <*> randomRIO (0, 255)\n <*> pure 255\n\n randomFaceColourShader :: IO VertexColour -> Shader\n randomFaceColourShader colGenerator = Shader id colourFaces id id\n where colourFaces vs = fmap (toGeneratedColour colGenerator) vs\n toGeneratedColour col (ViewSpace (Vertex pos _ vnorm)) = ViewSpace (Vertex pos (Just (unsafePerformIO col)) vnorm)\n\n\nI later execute the whole pipeline as part of getting the `pixelsToRender` in the `canvasLoop` function.\n\n\n canvasLoop = do\n SDL.initializeAll\n -- model <- testParse\n let -- Initialisation stuff\n shader = randomFaceColourShader genCol -- The shader of interest.\n -- shader = noShaders\n -- projection = projectFrustrum (-right * aspect ) (right * aspect) (-top) (top) near far\n -- projection = projectPerspective 53 aspect 1 20\n -- depthBufferPix = getObjFaceDepth dims modelOpts projection camera model\n pixelsToRender = getObjFaces dims modelOpts projection camera shader model -- Shaders executed in here via getObjFaces\n -- ...\n (newZ, newCanv) = addPointsToBuffer (fromIntegral w) pixelsToRender 1 zBuf canv\n -- rest of program\n\n\nIn `cabal repl`and `cabal run` I get all the verts being correctly set according to my `Debug.Trace` output but a dithered and incorrectly coloured output face colours.\n\nEDIT: Or maybe not: this output is inconsistent. Sometimes all three verts are same colour and other times, I get this lol. Even still, the dithering should not be happening as far as I can tell!\n\n\n ghci> canvasLoop\n [Just (VertexColour {r = 131, g = 191, b = 83, a = 255}),Just (VertexColour {r = 204, g = 226, b = 140, a = 255}),Just (VertexColour {r = 114, g = 205, b = 169, a = 255})]\n [Just (VertexColour {r = 223, g = 67, b = 147, a = 255}),Just (VertexColour {r = 49, g = 228, b = 23, a = 255}),Just (VertexColour {r = 9, g = 62, b = 174, a = 255})]\n [Just (VertexColour {r = 161, g = 94, b = 184, a = 255}),Just (VertexColour {r = 227, g = 206, b = 146, a = 255}),Just (VertexColour {r = 138, g = 6, b = 29, a = 255})]\n [Just (VertexColour {r = 255, g = 187, b = 217, a = 255}),Just (VertexColour {r = 140, g = 55, b = 221, a = 255}),Just (VertexColour {r = 182, g = 113, b = 167, a = 255})]\n [Just (VertexColour {r = 44, g = 213, b = 17, a = 255}),Just (VertexColour {r = 180, g = 135, b = 37, a = 255}),Just (VertexColour {r = 25, g = 189, b = 69, a = 255})]\n [Just (VertexColour {r = 39, g = 6, b = 180, a = 255}),Just (VertexColour {r = 11, g = 1, b = 24, a = 255}),Just (VertexColour {r = 69, g = 88, b = 6, a = 255})]\n [Just (VertexColour {r = 92, g = 64, b = 121, a = 255}),Just (VertexColour {r = 64, g = 160, b = 76, a = 255}),Just (VertexColour {r = 239, g = 17, b = 85, a = 255})]\n [Just (VertexColour {r = 28, g = 156, b = 188, a = 255}),Just (VertexColour {r = 28, g = 200, b = 218, a = 255}),Just (VertexColour {r = 30, g = 232, b = 153, a = 255})]\n\n\nIf I `cabal install` followed by executing the binary, all vertices on every face get set to the same colour:\n\n\n $ game-engine +RTS -s -N8\n [Just (VertexColour {r = 136, g = 243, b = 159, a = 255}),Just (VertexColour {r = 136, g = 243, b = 159, a = 255}),Just (VertexColour {r = 136, g = 243, b = 159, a = 255})]\n [Just (VertexColour {r = 136, g = 243, b = 159, a = 255}),Just (VertexColour {r = 136, g = 243, b = 159, a = 255}),Just (VertexColour {r = 136, g = 243, b = 159, a = 255})]\n [Just (VertexColour {r = 136, g = 243, b = 159, a = 255}),Just (VertexColour {r = 136, g = 243, b = 159, a = 255}),Just (VertexColour {r = 136, g = 243, b = 159, a = 255})]\n [Just (VertexColour {r = 136, g = 243, b = 159, a = 255}),Just (VertexColour {r = 136, g = 243, b = 159, a = 255}),Just (VertexColour {r = 136, g = 243, b = 159, a = 255})]\n [Just (VertexColour {r = 136, g = 243, b = 159, a = 255}),Just (VertexColour {r = 136, g = 243, b = 159, a = 255}),Just (VertexColour {r = 136, g = 243, b = 159, a = 255})]\n [Just (VertexColour {r = 136, g = 243, b = 159, a = 255}),Just (VertexColour {r = 136, g = 243, b = 159, a = 255}),Just (VertexColour {r = 136, g = 243, b = 159, a = 255})]\n [Just (VertexColour {r = 136, g = 243, b = 159, a = 255}),Just (VertexColour {r = 136, g = 243, b = 159, a = 255}),Just (VertexColour {r = 136, g = 243, b = 159, a = 255})]\n [Just (VertexColour {r = 136, g = 243, b = 159, a = 255}),Just (VertexColour {r = 136, g = 243, b = 159, a = 255}),Just (VertexColour {r = 136, g = 243, b = 159, a = 255})]\n\n\n\nThe only thing I can think of doing at this point is lifting all of the `Shader` types to be in `IO` and everything that interacts with the shader pipeline to also be in `IO`but I’m wondering if there is any other approach that could be worth trying.",
"title": "Shader Pipeline: PrimShader and Random Face Colours"
}