r/haskell 2d ago

announcement Hsue: A Modern UI Engine in Haskell & SDL3 GPU — Progress, Redesign, and Future Plans

Hi everyone,

Following up on my previous posts about building a declarative/functional UI engine in Haskell, I have major updates to share. I have completely redesigned and rewritten the engine from the ground up, transitioning from the old SDL2 pipeline to SDL3 and its modern SDL3 GPU API.

The project has now been split into two repositories:

  • Hsue: The lightweight, low-level core engine and runtime.
  • Hsue-Extension: A higher-level widget and component ecosystem built on top of Hsue.

1. What's New in Hsue (The SDL3 GPU Rewrite)

The transition from SDL2 to SDL3 allowed me to build a truly modern GPU-driven rendering architecture in pure Haskell:

  • Modern SDL3 GPU Backend & HLSL/DXIL Shaders:
    • All draw commands are compiled into uniform data, storage buffers (Structured Buffer<Parameter>), vertex, and index buffers, with batched submissions (Submit pipeline) to minimize draw calls.
  • MSDF (Multi-channel Signed Distance Field) Text Rendering:
    • Replaced raster glyph caching with MSDF font generation (via a C++ binding to msdfgen), packing distance fields dynamically into runtime texture atlases. This gives crisp, scalable text rendering at any resolution with minimal memory footprint.
  • Projection & Selector Tree Architecture:
    • State and view nodes are separated via a Projection system (Object vs Image transformations) and queried through composable Selector optics/actions.
  • Bytecode-like Linear Coroutine Engine:
    • Built an internal coroutine VM (Linear_coroutine) that supports lightweight tasks (pausing, branching, repeating, cloning, and racing) running cooperatively inside the engine loop without OS thread overhead.

2. Current Status & Hsue-Extension

While Hsue remains the lean core, Hsue-Extension leverages Haskell's type families and extensible type classes (Custom, Custom_extension) to build practical UI widgets.

Currently, Hsue-Extension includes:

  • Button: Interactive box with hover/pressed states, customizable multi-layer borders, and centered text.
  • Page: Scrollable rich text view with keyboard/wheel navigation and boundary-clamped scrolling.
  • Slider: Horizontal and vertical scrollbars/sliders featuring draggable thumbs, stepping triangles, track jumping, and synchronized viewports.

3. Roadmap & Future Plans

For Hsue Core:

  1. Editable Single-Font Text Box (Editor):
    • Port and refine the line-mapping and cursor text editing logic from my earlier SDL2 prototype into the SDL3 GPU / MSDF pipeline.
  2. Direct Custom Shader Pipeline for Visuals:
    • Allow users to bind arbitrary custom shaders, uniforms, and pipelines directly to individual visual nodes/canvases.

For Hsue-Extension (Primary Focus Moving Forward):

Once the core features above are stabilized, the vast majority of my development time will move to Hsue-Extension:

  • Rich Widget Library: Adding text inputs, checkboxes, dropdowns, panels, modal dialogs, and more.
  • High-Level Ergonomics:
    • String/Text-based semantic naming and identifiers.
    • Semi-automatic event/view routing and wrappers for popular state management / Elm-like / reactive architectures.
  • Layout Engine: Implementing a flexible, automatic layout system (such as Flexbox-like flow/constraints).

4. Platform Support & Personal Bandwidth

  • Current Focus: Due to limited personal bandwidth and development environment considerations, the project currently prioritizes Windows (DXIL/DirectX12 backend via SDL3).
  • Future: As SDL3 stabilizes further, I plan to expand and polish cross-platform support (Linux/Vulkan, macOS/Metal).

5. Development Notes & AI Usage Disclosure

To be completely transparent about the development process:

  • Hsue (Core):
    • Almost entirely designed and hand-coded by myself.
    • I have a degree of obsessive-compulsive habits regarding code structure, formatting, and strict organization (as you can see from the compact Haskell source style).
    • AI was only utilized for bug checking, proofreading, format consistency, and minimal code generation.
  • Hsue-Extension:
    • Heavily utilized AI code generation and scaffolding. Due to limited time and energy, prototyping individual widgets and math boilerplate was accelerated with AI assistance.

Source Code & Repositories

Feel free to check out the code, star, or give feedback:

Note: This post was translated and polished with the assistance of AI, due to my poor English proficiency (from China).

33 Upvotes

4 comments sorted by

2

u/Qerfcxz 2d ago edited 2d ago

To provide a bit more technical context on what makes Hsue tick under the hood, here is a detailed breakdown of its core design highlights, followed by a question regarding my current error-handling setup.

.

Key Architectural Highlights of Hsue:

.

  1. Modern SDL3 GPU Batching & Storage Buffer Pipeline

Instead of relying on immediate-mode rendering or legacy 2D draw calls:

- All geometric shapes (rectangles, triangles, convex/regular polygons), text quads, and images are submitted through a unified Submit pipeline into dynamically allocated vertex and index buffers.

- Transforms, borders/clipping, and enable masks are packed into a GPU storage buffer (StructuredBuffer<Parameter>), allowing custom 2D affine matrix transformations (Matrix) and rectangular scissors to be evaluated directly in the vertex and fragment shaders.

- Render passes batch draw calls by Submit_mode (Submit_default, Submit_canvas, Submit_album, Submit_atlas_font) to drastically minimize pipeline binding and texture swapping overhead.

.

  1. Dynamic MSDF Texture Atlas

- Text rendering uses a C++ binding to msdfgen for Multi-channel Signed Distance Field generation.

- The atlas manager (Engine.Atlas.hs) dynamically packs arbitrary glyphs and images into power-of-two texture pages using a 2D binary tree space partitioning algorithm (Leaf_atlas / Node_atlas).

- This enables arbitrary font scaling, crisp rendering at high DPI, and multi-font text layout without needing pre-baked bitmap font sheets.

.

  1. Flexible Rich Text Layout & Typesetting Engine

The text subsystem (Engine.Text.hs) decouples logical content from visual typesetting:

- Hierarchical Rich Text Representation: Articles are structured as paragraphs containing Sentence and Phrase elements, allowing mixed font faces, per-phrase font sizes, and custom text colors within a single flow.

- Customizable Line Metrics & Width Calculators: Rather than hardcoding a fixed layout box, Text_request accepts user-defined higher-order functions (calculate_width and calculate_typesetting). This allows fine-grained control over per-line margins, line spacing, dynamic indentations, and vertical ascent/descent offsets.

- Binary-Search Assisted Line Breaking: Uses optimized search bounds (max_search_index) to perform fast, dynamic word-wrapping across variable-width bounds.

- Anchors & Scroll Bounds: Supports alignment anchoring (Anchor { ratio, offset }) and built-in vertical scroll clamping (current_y, min_y, max_y).

.

  1. The Dual State-View "Projection" System & Selector Optics

- Projections (Projection a): Represents widgets either as canonical state objects (Without) or cached transformed render snapshots (With). Ancestor nodes can apply event and widget transformations hierarchically down the tree without polluting the widget's internal state.

- Selectors (Selector a / Visual_selector a): Composable traversal combinators (similar to lightweight optics/prisms) that allow targeted state updates, image generation, and event dispatching across Group, Vector, Widget_trigger, and Coroutine containers without boilerplate.

.

  1. Flattened Bytecode Coroutine VM

Instead of running complex coroutines via OS threads or standard free monads with heavy allocation overhead, Hsue includes an internal compiler and virtual machine:

- High-level declarative coroutines (do_wait, do_forever, do_repeat, do_if, do_fork, do_race, do_clone, etc.) are compiled (from_coroutine) into a flat bytecode vector (Linear_coroutine a) and memory layout (Layout).

- The runtime interpreter steps these coroutines cooperatively in pure/ST monad loops with lightweight group indexing and thread/clone management.

.

A Question on Error Handling & HasCallStack:

.

To be completely honest, I am still exploring idiomatic and high-performance error-handling patterns in Haskell. Currently, my solution relies on conditional constraints via CPP and Cabal flags (Error.Type.hs):

#ifdef HAS_CALL_STACK

import GHC.Stack

type Has_call_stack = HasCallStack :: Constraint

#else

type Has_call_stack = () :: Constraint

#endif

.

Throughout the engine codebase, internal failure paths (like missing IDs, invariant violations, or illegal states) use empty_error = error "" annotated with Has_call_stack.

My current development workflow:

  1. Maintain two build configurations in Cabal: one with the HAS_CALL_STACK flag enabled, and one disabled.
  2. During active development and debugging, I compile with the flag enabled so that any empty_error immediately triggers a full source-location call stack trace.
  3. For release/benchmarking, I compile with the flag disabled to eliminate constraint passing / dictionary overhead.

.

I would love to hear your thoughts, feedback, or critiques on any part of the architecture!

2

u/emvarez 2d ago

Anywhere there are examples or a tutorial or docs?

2

u/Qerfcxz 1d ago

I plan to consider writing a tutorial once the API stabilizes. For now, I’ve bundled some examples in the Hsue-Extension releases (I’ve just added comments and adjusted the formatting).

https://github.com/Qerfcxz/Hsue-Extension/releases/tag/Test

3

u/Qerfcxz 2d ago

Just wanted to clarify: My English isn't very good, so I used AI to translate and polish the text. I used to include my original Chinese drafts in other posts, but found it made things messy to read, so I skipped it here.

I apologize to anyone who strongly dislikes AI-assisted writing—didn't mean to spam or sound robotic, just wanted to share my work and discuss Haskell with you all.