--- layout: default.liquid title: 2025-12-18 GLSL EDSL in Haskell part 1 description: first part of GLSL EDSL development series. We initialize the project and do some planning. is_draft: true --- all code for this project is available [here](https://git.mtgmonkey.net/Andromeda/hs-glsl). Please contact me if you want an account for contribution or to file issues. #### goals of this project I want to generate [GLSL]() from the type-safe environment of Haskell with an [EDSL](). Specifically, I want to functionally declare the glsl shader code I use in my [game](https://git.mtgmonkey.net/Andromeda/hs-game). At the moment, that's quite a lot. Let's start with a practically bare Haskell/OpenGL project. Let's build our development environment. ``` bash mkdir hs-glsl cd hs-glsl # get initial dependencies nix-shell -p cabal-install ghc cabal init # all defaults are ok for our needs EXCEPT the following: # Application directory: I chose 4 then put `src` # Version: I chose 0.1.0, breaking with PVP in # favor of Semantic Versioning # Category: Graphics # I also entered my contact, username, and homepage ``` Great, now we have a `hs-glsl.cabal`, the file that defines our project. Cabal also created a number of other files and directories, namely `src/Main.hs` and `LICENSE`. Running the command `cabal run` will run the project, but it isn't reproducable; how can we do it with Nix? We can create a nix flake as follows: ``` nix # flake.nix { inputs = { nixpkgs.url = "nixpkgs/nixpkgs-unstable"; }; outputs = { nixpkgs, self, ... }: let system = "x86_64-linux"; pkgs = nixpkgs.legacyPackages.${system}; in { packages.${system} = { default = pkgs.haskellPackages.callCabal2nix "hs-glsl" {}; }; devShells.${system} = { default = pkgs.mkShell { packages = [ pkgs.cabal-install ]; inputsFrom = [ self.packages.${system}.default ]; }; }; }; } ``` now `nix develop` puts us in a shell with all of out dependencies, and `nix run` should run the program. Let's use [GLFW-b]() to create a window: ``` yaml # hs-glsl.cabal ... executable hs-glsl ... build-depends: base ^>=4.20.2.0 , GLFW-b <1000 ... ``` --- ``` hs -- | src/Main.hs module Main (main) where -- | windowing library import qualified Graphics.UI.GLFW as GLFW -- | main main :: IO () main = do -- initialize GLFW _ <- GLFW.init GLFW.defaultWindowHints -- we use GLSL version 3.3 Core here GLFW.windowHint $ GLFW.WindowHint'ContextVersionMajor 3 GLFW.windowHint $ GLFW.WindowHint'ContextVersionMinor 3 GLFW.windowHint $ GLFW.WindowHint'OpenGLProfile GLFW.OpenGLProfile'Core -- monitor required to fullscreen at start monitor <- GLFW.getPrimaryMonitor -- actually create the window Just window <- GLFW.createWindow 256 256 "hs-glsl" monitor Nothing GLFW.makeContextCurrent $ Just window -- enter loop loop window -- | loop that runs every frame loop :: GLFW.Window -> IO () loop window = do -- swap to next framebuffer GLFW.swapBuffers window -- call next frame loop window ``` Great, a fullscreen window that never closes! I'll go ahead and add a quick key callback so that pressing `esc` closes the program. ``` -- | src/Main.hs