Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## v0.5.3 (2026-07-12)

### Changed

- **deps:** goffi v0.5.2 → v0.6.0 — assembly-level errno capture, callback stack safety fix, zero-allocation FFI
- **deps:** gputypes v0.5.0 → v0.5.1
- **deps:** golang.org/x/sys v0.42.0 → v0.47.0

### Fixed

- Adapt to goffi v0.6.0 breaking change: `CallFunction` now returns `(syscall.Errno, error)`
- Fix all golangci-lint issues: unchecked `Buffer.Unmap()` errors in examples, `os.Remove` in setup, variable shadowing

### Highlights (from goffi v0.6.0)

- **errno capture** — first pure-Go FFI with correct errno on Linux (captured in assembly trampoline, ~3-5ns overhead)
- **Callback stack fix** — `sync.Pool` + `runtime.Pinner` prevents goroutine stack move corruption during C callbacks
- **Zero-allocation FFI** — ABI-safe struct layout eliminates allocations in steady state

## v0.5.2 (2026-05-27)

### Fixed
Expand Down
7 changes: 4 additions & 3 deletions UPSTREAM.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ This document tracks upstream dependencies, pinned versions, and compatibility f
|------------|---------|--------|------|
| **wgpu-native** | [v29.0.0.0](https://github.com/gfx-rs/wgpu-native/releases/tag/v29.0.0.0) | — | 2026-04-10 |
| **webgpu.h** | wgpu-native bundled | [`7d3186c`](https://github.com/webgpu-native/webgpu-headers/commit/7d3186c3dd2c708703524027b46b8703534ab3cc) | stable |
| **goffi** | [v0.5.0](https://github.com/go-webgpu/goffi/releases/tag/v0.5.0) | [`ca3231c`](https://github.com/go-webgpu/goffi/commit/ca3231c) | 2026-03-20 |
| **gputypes** | [v0.3.0](https://github.com/gogpu/gputypes/releases/tag/v0.3.0) | [`209398e`](https://github.com/gogpu/gputypes/commit/209398e) | 2026-03-20 |
| **goffi** | [v0.6.0](https://github.com/go-webgpu/goffi/releases/tag/v0.6.0) | [`895a3fa`](https://github.com/go-webgpu/goffi/commit/895a3fa) | 2026-07-12 |
| **gputypes** | [v0.5.1](https://github.com/gogpu/gputypes/releases/tag/v0.5.1) | [`239da90`](https://github.com/gogpu/gputypes/commit/239da90) | 2026-07-12 |

## Compatibility Matrix

| go-webgpu | wgpu-native | goffi | gputypes | Go |
|-----------|-------------|-------|----------|----|
| v0.5.3 | v29.0.0.0 | v0.6.0 | v0.5.1 | 1.25+ |
| v0.5.0 | v29.0.0.0 | v0.5.0 | v0.3.0 | 1.25+ |
| v0.4.3 | v27.0.4.0 | v0.5.0 | v0.3.0 | 1.25+ |
| v0.4.2 | v27.0.4.0 | v0.4.2 | v0.2.0 | 1.25+ |
Expand Down Expand Up @@ -111,4 +112,4 @@ Enum values in gputypes follow the webgpu.h specification. When gputypes updates

---

*Last updated: 2026-05-26 (v0.5.0)*
*Last updated: 2026-07-12 (v0.5.3)*
4 changes: 3 additions & 1 deletion examples/buffer_introspection/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ func main() {
fmt.Printf("Initial map state (MappedAtCreation): %s\n", mapStateToString(mapState))

// Unmap the buffer
mappableBuffer.Unmap()
if err := mappableBuffer.Unmap(); err != nil {
log.Printf("unmap: %v", err)
}

// Check state after unmap
mapState = mappableBuffer.MapState()
Expand Down
4 changes: 3 additions & 1 deletion examples/colored-triangle/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,9 @@ func (app *App) createVertexBuffer() error {
copy(mappedSlice, vertices)

// Unmap buffer to commit data to GPU
app.vertexBuffer.Unmap()
if err := app.vertexBuffer.Unmap(); err != nil {
log.Printf("unmap: %v", err)
}

return nil
}
Expand Down
10 changes: 7 additions & 3 deletions examples/compute/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
}
`

func main() {
func main() { //nolint:gocyclo,cyclop // example: sequential GPU setup is inherently linear
// Initialize WebGPU
if err := wgpu.Init(); err != nil {
log.Fatal(err)
Expand Down Expand Up @@ -94,7 +94,9 @@ func main() {
mappedSlice := unsafe.Slice((*float32)(ptr), numElements)
copy(mappedSlice, inputData)
}
storageBuffer.Unmap()
if unmapErr := storageBuffer.Unmap(); unmapErr != nil {
log.Printf("unmap storage buffer: %v", unmapErr)
}

// Create readback buffer for results
readbackBuffer, err := device.CreateBuffer(&wgpu.BufferDescriptor{
Expand Down Expand Up @@ -194,7 +196,9 @@ func main() {
fmt.Println("All results correct!")
}
}
readbackBuffer.Unmap()
if unmapErr := readbackBuffer.Unmap(); unmapErr != nil {
log.Printf("unmap readback buffer: %v", unmapErr)
}

fmt.Println()
fmt.Println("Key concepts demonstrated:")
Expand Down
4 changes: 3 additions & 1 deletion examples/cube/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,9 @@ func (app *App) createVertexBuffer() error {
copy(mappedSlice, vertices)

// Unmap buffer to commit data to GPU
app.vertexBuffer.Unmap()
if unmapErr := app.vertexBuffer.Unmap(); unmapErr != nil {
log.Printf("unmap vertex buffer: %v", unmapErr)
}

return nil
}
Expand Down
12 changes: 6 additions & 6 deletions examples/error_handling/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func main() {
fmt.Println("Queue obtained successfully")

// Pop error scope and check for errors
errType, message := device.PopErrorScope(instance)
errType, message := device.PopErrorScope(instance) //nolint:staticcheck // example demonstrates deprecated API
if errType != wgpu.ErrorTypeNoError {
fmt.Printf("ERROR: Validation error occurred: %s\n", message)
} else {
Expand All @@ -64,11 +64,11 @@ func main() {
fmt.Println("Performing GPU operations...")

// Pop inner scope first (LIFO)
errType, message = device.PopErrorScope(instance)
errType, message = device.PopErrorScope(instance) //nolint:staticcheck // example demonstrates deprecated API
fmt.Printf("Inner scope: errType=%v, message=%q\n", errType, message)

// Pop outer scope
errType, message = device.PopErrorScope(instance)
errType, message = device.PopErrorScope(instance) //nolint:staticcheck // example demonstrates deprecated API
fmt.Printf("Outer scope: errType=%v, message=%q\n", errType, message)

// Example 3: Different error filters
Expand All @@ -77,19 +77,19 @@ func main() {
// Catch validation errors
device.PushErrorScope(wgpu.ErrorFilterValidation)
fmt.Println("Monitoring for validation errors...")
errType, _ = device.PopErrorScope(instance)
errType, _ = device.PopErrorScope(instance) //nolint:staticcheck // example demonstrates deprecated API
fmt.Printf("Validation filter: errType=%v\n", errType)

// Catch out-of-memory errors
device.PushErrorScope(wgpu.ErrorFilterOutOfMemory)
fmt.Println("Monitoring for out-of-memory errors...")
errType, _ = device.PopErrorScope(instance)
errType, _ = device.PopErrorScope(instance) //nolint:staticcheck // example demonstrates deprecated API
fmt.Printf("Out-of-memory filter: errType=%v\n", errType)

// Catch internal errors
device.PushErrorScope(wgpu.ErrorFilterInternal)
fmt.Println("Monitoring for internal errors...")
errType, _ = device.PopErrorScope(instance)
errType, _ = device.PopErrorScope(instance) //nolint:staticcheck // example demonstrates deprecated API
fmt.Printf("Internal filter: errType=%v\n", errType)

fmt.Println("\n=== Error Handling Examples Completed ===")
Expand Down
8 changes: 6 additions & 2 deletions examples/indirect/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,9 @@ func (app *App) createBuffers() error {
mappedSlice := unsafe.Slice((*Vertex)(ptr), len(vertices))
copy(mappedSlice, vertices)
}
app.vertexBuffer.Unmap()
if err := app.vertexBuffer.Unmap(); err != nil {
log.Printf("unmap vertex buffer: %v", err)
}

// Create indirect buffer with draw arguments
// This is the key part - the GPU reads these parameters!
Expand Down Expand Up @@ -388,7 +390,9 @@ func (app *App) createBuffers() error {
if indirectPtr != nil {
*(*wgpu.DrawIndirectArgs)(indirectPtr) = indirectArgs
}
app.indirectBuffer.Unmap()
if err := app.indirectBuffer.Unmap(); err != nil {
log.Printf("unmap indirect buffer: %v", err)
}

return nil
}
Expand Down
8 changes: 6 additions & 2 deletions examples/instanced/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,9 @@ func main() {
mappedSlice := unsafe.Slice((*Vertex)(ptr), len(triangleVertices))
copy(mappedSlice, triangleVertices)
}
vertexBuffer.Unmap()
if unmapErr := vertexBuffer.Unmap(); unmapErr != nil {
log.Printf("unmap vertex buffer: %v", unmapErr)
}

// Create instance buffer (per-instance data)
instanceBufferSize := uint64(len(instanceData)) * uint64(unsafe.Sizeof(instanceData[0]))
Expand All @@ -165,7 +167,9 @@ func main() {
mappedSlice := unsafe.Slice((*InstanceData)(ptr), len(instanceData))
copy(mappedSlice, instanceData)
}
instanceBuffer.Unmap()
if unmapErr := instanceBuffer.Unmap(); unmapErr != nil {
log.Printf("unmap instance buffer: %v", unmapErr)
}

// Define vertex attributes for per-vertex buffer
vertexAttributes := []wgpu.VertexAttribute{
Expand Down
4 changes: 3 additions & 1 deletion examples/mrt/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,9 @@ func (app *App) createVertexBuffer() error {
copy(mappedSlice, vertices)

// Unmap buffer to commit data to GPU
app.vertexBuffer.Unmap()
if unmapErr := app.vertexBuffer.Unmap(); unmapErr != nil {
log.Printf("unmap vertex buffer: %v", unmapErr)
}

return nil
}
Expand Down
4 changes: 3 additions & 1 deletion examples/rotating-triangle/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,9 @@ func (app *App) createVertexBuffer() error {
copy(mappedSlice, vertices)

// Unmap buffer to commit data to GPU
app.vertexBuffer.Unmap()
if err := app.vertexBuffer.Unmap(); err != nil {
log.Printf("unmap: %v", err)
}

return nil
}
Expand Down
8 changes: 6 additions & 2 deletions examples/textured-quad/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,9 @@ func (app *App) createBuffers() error {
// nolint:gosec,govet // vPtr is from GetMappedRange, validated non-nil, safe for slice conversion
vMappedSlice := unsafe.Slice((*float32)(vPtr), len(vertices))
copy(vMappedSlice, vertices)
app.vertexBuffer.Unmap()
if unmapErr := app.vertexBuffer.Unmap(); unmapErr != nil {
log.Printf("unmap vertex buffer: %v", unmapErr)
}

// Create index buffer
// nolint:gosec // len(indices) is 6, * 2 = 12 bytes - no overflow risk
Expand All @@ -530,7 +532,9 @@ func (app *App) createBuffers() error {
// nolint:gosec,govet // iPtr is from GetMappedRange, validated non-nil, safe for slice conversion
iMappedSlice := unsafe.Slice((*uint16)(iPtr), len(indices))
copy(iMappedSlice, indices)
app.indexBuffer.Unmap()
if err := app.indexBuffer.Unmap(); err != nil {
log.Printf("unmap index buffer: %v", err)
}

return nil
}
Expand Down
4 changes: 3 additions & 1 deletion examples/timestamp_query/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,9 @@ fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
startTimestamp := *(*uint64)(unsafe.Pointer(&data[0]))
endTimestamp := *(*uint64)(unsafe.Pointer(&data[8]))

stagingBuffer.Unmap()
if err := stagingBuffer.Unmap(); err != nil {
log.Printf("unmap staging buffer: %v", err)
}

// Calculate elapsed ticks.
// Note: To convert to nanoseconds, you need the timestamp period
Expand Down
4 changes: 2 additions & 2 deletions examples/triangle/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -401,8 +401,8 @@ func (app *App) render() error {
defer encoder.Release()

// Render triangle
if err := app.renderTriangle(encoder, app.surfaceTexView); err != nil {
return err
if renderErr := app.renderTriangle(encoder, app.surfaceTexView); renderErr != nil {
return renderErr
}

// Finish encoding
Expand Down
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ module github.com/go-webgpu/webgpu

go 1.25.0

require github.com/go-webgpu/goffi v0.5.2
require github.com/go-webgpu/goffi v0.6.0

require golang.org/x/sys v0.42.0
require golang.org/x/sys v0.47.0

require github.com/gogpu/gputypes v0.5.0
require github.com/gogpu/gputypes v0.5.1
12 changes: 6 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
github.com/go-webgpu/goffi v0.5.2 h1:Kq2llPlA6IhkkmAffkM5CtsgaXuBQC4mBI0BOREwV04=
github.com/go-webgpu/goffi v0.5.2/go.mod h1:wfoxNsJkU+5RFbV1kNN1kunhc1lFHuJKK3zpgx08/uM=
github.com/gogpu/gputypes v0.5.0 h1:i2ED/9w6m6yLxf8XJT69/NIMSNTLO2y5F1LqvugCKIE=
github.com/gogpu/gputypes v0.5.0/go.mod h1:cnXrDMwTpWTvJLW1Vreop3PcT6a2YP/i3s91rPaOavw=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
github.com/go-webgpu/goffi v0.6.0 h1:dTBwfzj8CZUW0w0fgeMaYGBrIktK7nzfjMsnSpkSt4Y=
github.com/go-webgpu/goffi v0.6.0/go.mod h1:wfoxNsJkU+5RFbV1kNN1kunhc1lFHuJKK3zpgx08/uM=
github.com/gogpu/gputypes v0.5.1 h1:X38OPcP6umQqqubzzJYL6Nm1tXHSNQj6TRSAoxdAJmg=
github.com/gogpu/gputypes v0.5.1/go.mod h1:cnXrDMwTpWTvJLW1Vreop3PcT6a2YP/i3s91rPaOavw=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
4 changes: 2 additions & 2 deletions internal/nativelib/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,12 @@ func Download(url string) (string, error) {

if _, err := io.Copy(tmpFile, io.LimitReader(resp.Body, maxLibSize)); err != nil {
tmpFile.Close() //nolint:errcheck // cleanup on write error
os.Remove(tmpPath)
_ = os.Remove(tmpPath)
return "", fmt.Errorf("download write: %w", err)
}

if err := tmpFile.Close(); err != nil {
os.Remove(tmpPath)
_ = os.Remove(tmpPath)
return "", fmt.Errorf("close %s: %w", tmpPath, err)
}

Expand Down
4 changes: 2 additions & 2 deletions internal/nativelib/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func LibraryName() string {

func FindLibrary() string {
if p := os.Getenv("WGPU_NATIVE_PATH"); p != "" {
if _, err := os.Stat(p); err == nil {
if _, err := os.Stat(p); err == nil { //nolint:gosec // G703: user-provided env var path, validated by Stat
return p
}
}
Expand All @@ -102,7 +102,7 @@ func FindLibrary() string {

for _, dir := range strings.Split(os.Getenv("PATH"), string(os.PathListSeparator)) {
p := filepath.Join(dir, libName)
if _, err := os.Stat(p); err == nil {
if _, err := os.Stat(p); err == nil { //nolint:gosec // G703: dir from PATH env, libName from internal constants
return p
}
}
Expand Down
6 changes: 3 additions & 3 deletions setup/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ func Install(destDir string) (string, error) {
if destDir == "" {
destDir = "lib"
}
if err := os.MkdirAll(destDir, 0o755); err != nil {
return "", fmt.Errorf("create directory %s: %w", destDir, err)
if mkdirErr := os.MkdirAll(destDir, 0o755); mkdirErr != nil { //nolint:gosec // G301: 0755 is standard for library directories
return "", fmt.Errorf("create directory %s: %w", destDir, mkdirErr)
}

url := platform.DownloadURL(Version)
Expand All @@ -44,7 +44,7 @@ func Install(destDir string) (string, error) {
if err != nil {
return "", err
}
defer os.Remove(zipPath)
defer func() { _ = os.Remove(zipPath) }()

fmt.Printf("Extracting %s...\n", platform.LibName)
libPath, err := nativelib.ExtractLibrary(zipPath, destDir, platform.LibName)
Expand Down
2 changes: 1 addition & 1 deletion wgpu/loader_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func (u *unixProc) Call(args ...uintptr) (uintptr, uintptr, error) {

// Call the function
var result uintptr
err := ffi.CallFunction(&u.cif, u.fnPtr, unsafe.Pointer(&result), argPtrs)
_, err := ffi.CallFunction(&u.cif, u.fnPtr, unsafe.Pointer(&result), argPtrs)
if err != nil {
return 0, 0, fmt.Errorf("wgpu: call to %s failed: %w", u.name, err)
}
Expand Down
Loading