Files
nook/posts/20251218-glsl-edsl-p1.md
2025-12-18 22:35:32 +01:00

122 lines
3.2 KiB
Markdown

---
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