heph-aligned-storable: Generically derive Storable instances suitable for CPU-GPU transfer

This is a package candidate release! Here you can preview how this package release will appear once published to the main package index (which can be accomplished via the 'maintain' link below). Please note that once a package has been published to the main package index it cannot be undone! Please consult the package uploading documentation for more information.

[maintain] [Publish]

Please see the README on GitHub at https://github.com/jtnuttall/heph/tree/main/heph-aligned-storable#readme


[Skip to Readme]

Properties

Versions 0.1.0.0, 0.1.0.0
Change log CHANGELOG.md
Dependencies base (>=4.7 && <5), half (>=0.3 && <0.5), hashable (>=1.3 && <1.6), linear (>=1.20 && <1.24), unliftio (>=0.2 && <0.3), vector (>=0.12.3.0 && <0.14), vector-sized (>=1.4 && <1.7) [details]
License BSD-3-Clause
Copyright 2025 Jeremy Nuttall
Author Jeremy Nuttall
Maintainer jeremy@jeremy-nuttall.com
Category Graphics
Home page https://github.com/jtnuttall/heph/tree/main/heph-aligned-storable#readme
Bug tracker https://github.com/jtnuttall/heph/issues
Source repo head: git clone https://github.com/jtnuttall/heph
Uploaded by jtnuttall at 2026-02-02T07:05:45Z

Modules

[Index] [Quick Jump]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees


Readme for heph-aligned-storable-0.1.0.0

[back to package description]

heph-aligned-storable

Generically derive Storable instances for GPU memory layouts (std140, std430, scalar).

CI

Quick Start

IMPORTANT: Be sure to use layout(row_major) if you are using linear with this library.

GLSL:

layout(std140, row_major, binding = 0) uniform myuniforms {
  mat4 modelViewProjection;
  vec3 cameraPosition;
  float time;
};

Haskell:

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeApplications #-}

import Foreign.GPU.Storable.Aligned
import Foreign.GPU.Marshal.Aligned
import GHC.Generics (Generic)
import Linear (M44, V3, V4(..))

data Uniforms = Uniforms
  { modelViewProjection :: M44 Float
  , cameraPosition      :: V3 Float
  , time                :: Float
  } deriving (Generic, Show, Eq)

instance AlignedStorable Std140 Uniforms

main :: IO ()
main = do
  let uniforms = Uniforms
        { modelViewProjection = V4 (V4 1 0 0 0) (V4 0 1 0 0) (V4 0 0 1 0) (V4 0 0 0 1)
        , cameraPosition = V3 0 0 5
        , time = 0
        }
  withPacked @Std140 uniforms $ \ptr -> do
    -- ptr is ready for vkCmdPushConstants, memcpy to mapped buffer, etc.
    pure ()

Features

The Contract

alignedPoke writes member data only. Padding bytes are untouched.

Use the helpers in Foreign.GPU.Marshal.Aligned (withPacked, allocaPacked, etc.) for guaranteed zero-initialized padding. If you allocate memory yourself, use calloc or zero the buffer before poking.

Arrays

By default, arrays are poked element-by-element. For a single memcpy, wrap in AlignedArray:

data MyStruct (layout :: MemoryLayout) = MyStruct
  { meta   :: Float
  , pixels :: AlignedArray layout 64 (V4 Float)  -- memcpy'd as a block
  } deriving Generic

instance AlignedStorable Std140 (MyStruct Std140)

Gotchas

Matrix naming conventions

linear uses Mnm for n rows × m columns. GLSL uses matNxM for N columns of M-vectors.

row_major

GLSL's layout(row_major) affects memory layout, not matrix semantics. Matrices are still column-major for arithmetic. This library implements the memory layout correctly. You don't need to transpose before upload.

vec3 and mat3 are cursed

Driver handling of the round-up rules for these types has historically been inconsistent. Consider padding to vec4/mat4 and pretending the 3-element variants don't exist.

Why not derive-storable?

derive-storable produces FFI-compatible layouts (C struct ABI), not GPU layouts. GPU alignment rules differ: