hajimehoshi/ebiten
A dead simple 2D game engine for Go
Healthy across all four use cases
weakest axisPermissive license, no critical CVEs, actively maintained — safe to depend on.
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓Last commit 1d ago
- ✓5 active contributors
- ✓Apache-2.0 licensed
Show all 6 evidence items →Show less
- ✓CI configured
- ✓Tests present
- ⚠Single-maintainer risk — top contributor 95% of recent commits
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Healthy" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/hajimehoshi/ebiten)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/hajimehoshi/ebiten on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: hajimehoshi/ebiten
Generated by RepoPilot · 2026-05-09 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/hajimehoshi/ebiten shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
GO — Healthy across all four use cases
- Last commit 1d ago
- 5 active contributors
- Apache-2.0 licensed
- CI configured
- Tests present
- ⚠ Single-maintainer risk — top contributor 95% of recent commits
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live hajimehoshi/ebiten
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/hajimehoshi/ebiten.
What it runs against: a local clone of hajimehoshi/ebiten — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in hajimehoshi/ebiten | Confirms the artifact applies here, not a fork |
| 2 | License is still Apache-2.0 | Catches relicense before you depend on it |
| 3 | Default branch main exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 31 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of hajimehoshi/ebiten. If you don't
# have one yet, run these first:
#
# git clone https://github.com/hajimehoshi/ebiten.git
# cd ebiten
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of hajimehoshi/ebiten and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "hajimehoshi/ebiten(\\.git)?\\b" \\
&& ok "origin remote is hajimehoshi/ebiten" \\
|| miss "origin remote is not hajimehoshi/ebiten (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(Apache-2\\.0)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"Apache-2\\.0\"" package.json 2>/dev/null) \\
&& ok "license is Apache-2.0" \\
|| miss "license drift — was Apache-2.0 at generation time"
# 3. Default branch
git rev-parse --verify main >/dev/null 2>&1 \\
&& ok "default branch main exists" \\
|| miss "default branch main no longer exists"
# 4. Critical files exist
test -f "doc.go" \\
&& ok "doc.go" \\
|| miss "missing critical file: doc.go"
test -f "ebiten_test.go" \\
&& ok "ebiten_test.go" \\
|| miss "missing critical file: ebiten_test.go"
test -f "audio/context.go" \\
&& ok "audio/context.go" \\
|| miss "missing critical file: audio/context.go"
test -f "colorm/draw.go" \\
&& ok "colorm/draw.go" \\
|| miss "missing critical file: colorm/draw.go"
test -f "cmd/ebitenmobile/main.go" \\
&& ok "cmd/ebitenmobile/main.go" \\
|| miss "missing critical file: cmd/ebitenmobile/main.go"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 31 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~1d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/hajimehoshi/ebiten"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
Ebitengine is a lightweight, cross-platform 2D game engine for Go that provides graphics rendering, audio playback (Ogg/Vorbis, MP3, WAV), input handling (mouse, keyboard, gamepad, touch), and custom shader support. It compiles to native binaries for Windows, macOS, Linux, FreeBSD, Android, iOS, WebAssembly, and Nintendo Switch without requiring Cgo on desktop platforms. Monorepo with core engine in root (ebiten package), modular subsystems in subdirectories: audio/ for sound (mp3/, opus/, vorbis/ codec support), text/ for rendering, vector/ for drawing primitives, inpututil/ for higher-level input abstractions. Platform integration lives in internal/ directories with OS-specific build tags. The mobile/ and exp/ packages contain experimental/mobile-specific APIs.
👥Who it's for
Go developers building 2D games who want a simple, batteries-included engine that handles platform abstraction; indie game developers targeting multiple platforms with minimal setup; students learning game development in Go.
🌱Maturity & risk
Production-ready and actively maintained. The codebase is substantial (3.3M lines of Go, 567K of C platform bindings), has comprehensive CI via GitHub Actions (.github/workflows/test.yml, vuln.yml), and maintains platform-specific code for 8+ platforms. The v2 API is stable; regular updates and security scanning indicate healthy ongoing development.
Low-to-moderate risk. The project depends on ebitengine-maintained packages (ebitengine/oto for audio, ebitengine/purego for cross-platform syscalls) rather than third-party libraries, reducing supply-chain risk. However, it relies on platform-specific C bindings (Objective-C for macOS, Java for Android) and audio codec libraries (libogg, libvorbis, libopus) that must stay compatible. Single upstream maintainer (hajimehoshi) is a potential bottleneck.
Active areas of work
The project is actively developed with regular bug fixes and feature additions. Recent infrastructure includes GitHub Pages workflows for documentation (site/) and Steam integration (.github/workflows/steam.yml). Audio codec support is being expanded (opus/ added recently). Go 1.25.0 is the minimum version specified in go.mod, indicating rapid adoption of new language features.
🚀Get running
git clone https://github.com/hajimehoshi/ebiten.git
cd ebiten
go mod download
go test ./...
For a quick example game, clone and run: go run ./examples/flappybird/main.go (examples/ directory contains runnable demos).
Daily commands:
Ebitengine is a library, not a standalone application. Games import github.com/hajimehoshi/ebiten/v2 and call ebiten.RunGame() in their main(). Example: go run ./examples/snake/main.go from the repo root runs the bundled Snake game.
🗺️Map of the codebase
doc.go— Entry point and package documentation defining Ebitengine's core API and architecture philosophy.ebiten_test.go— Core test suite validating the main game loop, update, and draw cycle mechanics.audio/context.go— Audio system context management—essential for understanding multi-platform audio initialization and lifecycle.colorm/draw.go— Color matrix and drawing pipeline implementation, critical for rendering and graphics composition.cmd/ebitenmobile/main.go— Mobile build toolchain entry point; required reading for iOS/Android deployment workflows.blend.go— Blend mode definitions and compositing logic, foundational to graphics rendering abstraction.compositemode.go— Composite mode enum and rendering target composition—core to multi-target rendering architecture.
🛠️How to make changes
Add a new audio format decoder
- Create new format package directory under audio/ (e.g., audio/flac/) (
audio/flac/decode.go) - Implement decoder function returning io.ReadSeeker compatible with audio.Context.NewPlayer() (
audio/flac/decode.go) - Add format detection logic in audio/audio.go to route decoded streams (
audio/audio.go) - Create integration test in audio/flac/flac_test.go using test audio samples (
audio/flac/flac_test.go) - Document format support in doc.go public API reference (
doc.go)
Add a new example game
- Create game package directory under examples/ (e.g., examples/mygame/mygame/) (
examples/mygame/mygame/game.go) - Implement Game struct implementing ebiten.Game interface (Update, Draw, Layout) (
examples/mygame/mygame/game.go) - Create main.go entry point calling ebiten.RunGame() (
examples/mygame/main.go) - Add color palette, input handling, and tile/entity management modules as needed (
examples/mygame/mygame/colors.go) - Include go.mod and reference ebitengine in examples root (
examples/mygame/go.mod)
Add a new color transformation or blend mode
- Define new blend mode constant in blend.go (
blend.go) - Add color matrix helper function in colorscale.go or new colorm/helper.go (
colorm/colorm.go) - Implement drawing logic in colorm/draw.go that applies transformation to image data (
colorm/draw.go) - Add unit tests in colorm/colorm_test.go and colorm/draw_test.go (
colorm/colorm_test.go) - Document new blend mode usage in doc.go API reference (
doc.go)
Add platform-specific mobile functionality
- Create platform-specific Go file with +build tags (e.g., file_ios.go) or use conditional imports (
audio/error_ios.go) - Implement iOS native code in cmd/ebitenmobile/_files/ using Objective-C (
cmd/ebitenmobile/_files/EbitenViewController.m) - Implement Android native code in cmd/ebitenmobile/_files/ using Java (
cmd/ebitenmobile/_files/EbitenSurfaceView.java) - Create Go wrapper functions in main package and expose via gobind in cmd/ebitenmobile/gobind.go (
cmd/ebitenmobile/gobind.go) - Test cross-platform build via cmd/ebitenmobile/main.go build command (
cmd/ebitenmobile/main.go)
🔧Why these technologies
- Go — Cross-platform compilation, simple deployment, garbage collection, and straightforward API for game loop structuring
- OpenGL / Metal / D3D11 (via platform backends) — Hardware-accelerated 2D rendering with wide device support; abstracted behind internal graphics context
- Oto/v3 (ebitengine/oto) — Unified audio I/O abstraction across Windows, macOS, Linux, iOS, Android with low-latency ring buffers
- gomobile / XGb (X11) — Enable platform-native bindings (Java/Objective-C) for mobile and X11 event polling on Linux
- typesetting (go-text) — Unicode text rendering and layout for multilingual game UI without heavy external dependencies
⚖️Trade-offs already made
-
Single-threaded game loop in user code; async audio via separate thread pool
- Why: Simplicity and safety: users write synchronous Update/Draw without mutex complexity; audio runs safely in background
- Consequence: Slight latency between game state change and audio reflection; audio buffering adds ~100–200ms delay, acceptable for games but not real-time audio apps
-
Immutable Image interface; Pixel data accessed via ReadPixels() only
- Why: Enable GPU-resident textures, avoid per-frame CPU→GPU transfers, support multiple backends (GL/Metal/D3D)
- Consequence: Cannot directly mutate texture pixels
🪤Traps & gotchas
Cgo requirement varies by platform: Windows and macOS claim 'No Cgo required' but Linux, FreeBSD, and mobile builds need C toolchain and platform SDKs. Audio context lifecycle: audio.NewContext() must be called once and reused; multiple contexts cause resource contention. OpenGL/Metal version constraints: internal graphics drivers have implicit GPU capability requirements not documented in code. Build tags complexity: Android builds require gomobile toolchain integration; WebAssembly builds strip certain features. Text rendering: text/v2 uses go-text/typesetting; older text/ package may be deprecated but coexists.
🏗️Architecture
💡Concepts to learn
- Texture Atlas — Ebitengine automatically packs multiple game sprites into single GPU textures to reduce draw calls; understanding how atlasing works is critical for performance optimization
- Matrix Transformations (Affine) — All 2D rendering in Ebitengine uses matrix-based geometry (rotation, scale, translation); the API requires passing colorm.ColorM and geometry transforms to Draw()
- Audio Resampling — Ebitengine's audio subsystem resamples codec output to match hardware sample rates; understanding SRC (sample rate conversion) helps debug audio pitch/speed issues
- Double Buffering — Ebitengine uses offscreen rendering with automatic front/back buffer swapping; games call Draw() to render to backbuffer, then ebiten swaps to avoid tearing
- Cross-Platform FFI (Foreign Function Interface) — Ebitengine bridges Go and C/Objective-C/Java via purego and gomobile; understanding syscall binding strategies is essential for porting to new platforms
- Immediate Mode Rendering — Ebitengine's Draw() API is immediate-mode (stateless per-frame); games redraw entire scene each Update/Draw cycle, not retained-mode scenegraphs
- Codec Decoders (Vorbis, MP3, Opus) — Audio assets are decoded on-demand via jfreymuth/oggvorbis, go-mp3, and opus-go libraries; understanding codec differences (bitrate, latency, support) affects game audio quality
🔗Related repos
raylib/raylib— C-based 2D/3D game engine with similar multi-platform cross-compilation goals; common alternative for developers who prefer C/C++libgdx/libgdx— Java game framework with similar 2D focus and multi-platform (desktop, Android, WebGL) approach; closest competitor in JVM ecosystemebitengine/oto— Ebitengine's dedicated audio I/O library used as the low-level sound backend across all platformsebitengine/purego— Pure Go syscall wrapper eliminating Cgo dependency on Windows/macOS; core to Ebitengine's 'No Cgo required' claimgo-text/typesetting— Text layout and shaping library used by ebiten/text/v2 for Unicode and bidirectional text rendering
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add comprehensive integration tests for audio codec conversions
The audio subsystem has multiple codec handlers (MP3, Opus, Vorbis, WAV) with conversion utilities (float32, stereo/mono resampling), but audio/audio_test.go and individual codec tests lack integration tests covering edge cases like format mismatches, sample rate conversions across codecs, and error handling. This is critical for a game engine where audio playback reliability directly impacts user experience.
- [ ] Review existing tests in audio/*_test.go files and identify gaps in codec interaction scenarios
- [ ] Create audio/integration_test.go with cross-codec conversion tests (e.g., load MP3 then resample, load OGG with different channel counts)
- [ ] Add tests for audio/internal/convert/ covering all resampling edge cases (mono->stereo, various sample rates)
- [ ] Test error propagation from audio/error_ios.go and audio/error_notios.go in realistic scenarios
- [ ] Ensure tests cover the stopwatch.go timing logic under various playback conditions
Add Windows/Darwin-specific Cgo vulnerability scanning to CI
The repo maintains govetblock_darwin.txt and govetblock_windows.txt, suggesting platform-specific linting/vetting is needed, but the test.yml workflow doesn't explicitly run govet with these blocklists. Additionally, vuln.yml exists but may not cover platform-specific Cgo vulnerabilities from dependencies like ebitengine/purego and ebitengine/oto/v3 on each platform.
- [ ] Review .github/workflows/test.yml and add explicit 'go vet' steps with -blocklist flags for govetblock_darwin.txt and govetblock_windows.txt
- [ ] Extend .github/workflows/vuln.yml to run 'govulncheck' separately for windows and darwin GOOS targets
- [ ] Document in CONTRIBUTING.md why these blocklists exist and when developers should update them
- [ ] Add a pre-submit check that validates govetblock_*.txt files are not empty and contain real issues
Implement missing tests for colorm package
The colorm directory exists (colorm/colorm.go) but there's no colorm_test.go file visible in the file structure. Color matrix operations are fundamental to graphics rendering in a game engine and must have robust test coverage for composition, transformation edge cases, and performance.
- [ ] Examine colorm/colorm.go to understand the public API surface (matrix operations, transformations, compositing)
- [ ] Create colorm/colorm_test.go with unit tests covering: identity matrices, composition operations, edge cases (zero values, overflow), and property-based tests for commutativity where applicable
- [ ] Add benchmarks in colorm/colorm_bench_test.go for matrix multiplication since this is hot-path graphics code
- [ ] Cross-reference with blend.go to ensure colorm integrates correctly with blending operations (add integration tests if needed)
🌿Good first issues
- Add unit tests for audio/internal/convert/float32.go (file exists but has minimal test coverage compared to stereo variants)
- Document the Game interface contract in ebiten.go with example code for Draw, Update, Layout lifecycle and error handling
- Create integration tests in examples/ for gamepad input (mouse/keyboard covered, but gamepad cross-platform validation is sparse)
⭐Top contributors
Click to expand
Top contributors
- @hajimehoshi — 95 commits
- @TotallyGamerJet — 2 commits
- @AlexanderYastrebov — 1 commits
- @probeldev — 1 commits
- @tinne26 — 1 commits
📝Recent commits
Click to expand
Recent commits
31737d6— text/v2: cache shaping.Segmenter on GoTextFaceSource to reuse bidi buffers (hajimehoshi)54103ad— text/v2: update go-text/typesetting and use ShapeNoExtents to calculate advance (hajimehoshi)b73bd42— exp/textinput: re-anchor queued cross-session commits to the receiving session (hajimehoshi)6f33695— exp/textinput: replace invisible Unicode literals with \u escapes in tests (hajimehoshi)4465d5d— exp/textinput: hide replacement-range coordinates behind Commit methods (hajimehoshi)867b98f— exp/textinput, internal/glfw: fix NSNotFound sentinel on macOS to NSIntegerMax (hajimehoshi)6363c1b— exp/textinput: add Composer, an IME loop helper (hajimehoshi)84fc28f— exp/textinput: bound macOS IME UTF-16 conversions to one line (hajimehoshi)6320bf3— exp/textinput: update Field.ReadTextFrom to loose UTF-8 restrictions (hajimehoshi)dd2c949— all: update dependencies (hajimehoshi)
🔒Security observations
The Ebitengine repository shows generally good security practices but has several concerns: (1) Reliance on alpha/
- High · Alpha/Pre-release Dependencies —
go.mod - direct dependencies. The project uses multiple alpha and pre-release versions of critical dependencies: ebitengine/gomobile (0.0.0-20260211053922-3d992dae95d1), ebitengine/oto/v3 (v3.5.0-alpha.6), and ebitengine/purego (v0.11.0-alpha.2). Alpha versions have not undergone full security audits and may contain unpatched vulnerabilities. Fix: Transition to stable releases of these dependencies. If alpha versions are necessary, implement additional security testing and code review processes. Track security advisories closely for these packages. - Medium · Indirect Dependency on Older LZ4 —
go.mod - indirect dependencies. The project has an indirect dependency on pierrec/lz4/v4 v4.1.22. While not inherently dangerous, LZ4 libraries have historically had compression-related vulnerabilities. The specific version should be audited for known CVEs. Fix: Run 'go list -m -json all | nancy sleuth' or use 'go vuln ./...' to check for known vulnerabilities in lz4/v4 v4.1.22. Consider updating to the latest patched version if vulnerabilities are found. - Medium · Complex Cross-Platform Native Code Integration —
.github/workflows/govetblock_darwin.txt, .github/workflows/govetblock_windows.txt. The project uses ebitengine/purego for FFI and integrates with multiple platforms (Windows, macOS, Linux, iOS, Android). The .github/workflows/govetblock files suggest there are known go vet issues being suppressed, which could mask security problems in native code bindings. Fix: Review suppressed vet warnings to understand why they are being ignored. Implement additional security scanning for FFI code. Consider using tools like 'cgo-checker' to validate unsafe pointer usage. - Medium · Mobile Build System with Manual Code Generation —
cmd/ebitenmobile/, cmd/ebitenmobile/_files/. The ebitenmobile tool uses gomobile and code generation (cmd/ebitenmobile) to build mobile applications. The _files directory contains template files for Java and Objective-C code generation, which could be vulnerable to injection attacks if user input is not properly sanitized during code generation. Fix: Audit code generation logic to ensure all user-controlled inputs are properly escaped and sanitized. Validate that generated native code cannot be exploited through malicious project parameters. - Low · Test Files with External Audio Resources —
audio/vorbis/test_*.ogg. Test files reference external resources (test_mono.ogg, test_tooshort.ogg) in the vorbis audio package. These files should be validated to ensure they are not maliciously crafted audio files that could cause buffer overflows or DoS in the decoding library. Fix: Verify test audio files are legitimate and generated from known-safe sources. Implement fuzzing tests for audio decoding to catch malformed input handling. Consider generating test files programmatically instead of including pre-built binaries. - Low · No Security Policy Visible —
Repository root. While the repository has CODE_OF_CONDUCT.md and CONTRIBUTING.md, there is no visible SECURITY.md file for reporting security vulnerabilities. This makes it difficult for security researchers to responsibly disclose issues. Fix: Create a SECURITY.md file with vulnerability disclosure policy, contact information, and responsible disclosure guidelines. Consider joining the Go vulnerability database program. - Low · Potential Unsafe File Operations —
ebitenutil/loadimage.go, ebitenutil/fs.go, ebitenutil/io.go. The ebitenutil package contains file loading functionality (loadimage.go, io.go, fs.go) with separate implementations for JS and non-JS platforms. File path handling may be vulnerable to path traversal if not properly validated. Fix: Audit file path handling to prevent path traversal attacks. Use filepath.Clean() and validate paths are within expected directories. Implement unit tests for malicious path inputs.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.