Skip to content

sizif-22/Noir

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

image-processor

A high-performance image processing server that combines Go (HTTP/concurrency) with Rust (GPU-accelerated pixel processing) via CGo FFI.

Receives an image via HTTP, runs a WGPU compute shader on the GPU to convert it to grayscale, and returns the result — all with near-zero overhead between the two languages.


Why Go + Rust?

Layer What it does Why
Go HTTP server, routing, request handling Go routines make it trivial to handle thousands of concurrent requests with minimal memory overhead. The standard library's net/http is production-grade out of the box.
Rust Image decoding/encoding, GPU compute No garbage collector means predictable, low-latency performance for heavy pixel workloads. The GPU compute shader (via wgpu) processes every pixel in parallel — orders of magnitude faster than a CPU-bound loop.
FFI Bridge between Go and Rust Zero-copy extern "C" interface: Go passes a pointer to the raw image bytes, Rust processes it on the GPU, and returns a pointer to the result. No serialization, no JSON marshalling — just raw memory.

Go handles the I/O, Rust handles the math — each does what it does best.


How it works

Client  ──POST /gray──►  Go HTTP server
                                │
                          bridge.go (CGo)
                                │
                          ┌─────▼──────┐
                          │  Rust DLL  │
                          │  (wgpu)    │
                          │            │
                          │  GPU       │
                          │  compute   │
                          │  shader    │
                          └─────┬──────┘
                                │
                          Go writes response
                                │
Client  ◄── PNG bytes───  image/png

The Rust library uses wgpu to run a compute shader on the GPU that converts each pixel to grayscale using luminance weights (0.299R + 0.587G + 0.114B), then re-encodes the result as PNG.


Project Structure

image-processor/
├── rust-lib/                    # Rust library (cdylib)
│   ├── src/lib.rs               # Extern "C" FFI + GPU compute pipeline
│   ├── Cargo.toml
│   └── Cargo.lock
│
├── go-server/                   # Go HTTP server
│   ├── handlers/
│   │   ├── image.go             # POST /gray handler
│   │   └── health.go            # GET /health handler
│   ├── libs/                    # Compiled rust_lib.dll goes here
│   ├── bridge.go                # CGo bindings to Rust
│   ├── main.go                  # Entry point
│   └── go.mod
│
├── scripts/
│   ├── build.ps1                # Windows build script
│   ├── run.ps1                  # Windows run script
│   ├── build.sh                 # Linux/macOS build script
│   └── run.sh                   # Linux/macOS run script
│
├── bin/                         # Built server binary + DLL
├── Makefile
└── README.md

Build & Run

Prerequisites

  • Rust (stable)
  • Go 1.21+
  • GCC / MinGW (required by CGo)

Windows (PowerShell)

# Build Rust lib + Go binary
.\scripts\build.ps1

# Start the server
.\scripts\run.ps1

Linux / macOS

# Build Rust lib + Go binary
make build

# Start the server
make run

The server listens on http://localhost:8080.

Send an image

Use -F (multipart) so curl transmits the original filename — the server derives the output name from it.

curl -O -J -X POST http://localhost:8080/gray -F "image=@photo.jpg"

This saves the grayscale result as photo-o.jpg automatically.

With explicit output file:

curl -X POST http://localhost:8080/gray -F "image=@photo.jpg" -o result.png

API

Endpoint Method Body Response
/gray POST Raw bytes or multipart/form-data (image field) Grayscale PNG + Content-Disposition: filename="<input>-o.<ext>"
/health GET {"status":"ok"}

Performance Characteristics

  • Go spawns a goroutine per request — thousands of concurrent connections are cheap.
  • GPU compute shader processes every pixel in parallel (workgroup size 16×16). A 4K image (8.3M pixels) is handled in a single dispatch.
  • Zero-copy FFI — Go passes a pointer, Rust writes directly to GPU and reads back the result. No memory copying between languages.
  • No GC pauses during image processing — Rust's allocation is deterministic and dropped as soon as the response is written.

About

High-performance image processing server — Go handles HTTP concurrency, Rust does the heavy lifting via CGo FFI

Topics

Resources

Stars

Watchers

Forks

Contributors