122 lines
3.2 KiB
Markdown
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
|