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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ wgpu (public API — Device, Queue, Buffer, Texture, Pipeline...)

## Current Version

v0.30.16 | Go 1.25+ | Dependencies: naga v0.17.15, gpucontext v0.21.1, gputypes v0.5.1
v0.30.17 | Go 1.25+ | Dependencies: naga v0.17.15, gpucontext v0.21.1, gputypes v0.5.1, goffi v0.6.0

## Build & Test

Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ 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.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.30.17] - 2026-07-12

### Changed

- **Bump `goffi`** v0.5.6 → v0.6.0 — `CallFunction` now returns `(syscall.Errno, error)`.
Migrated all 764 FFI call sites (17 files) to three-tier error handling per ADR-049:
Tier 1 (creation/submit) checks FFI error + API result; Tier 2 (void/hot path) infallible
by GPU API contract; Tier 3 (platform syscalls) captures errno for diagnostics.
Enterprise reference: Rust wgpu-hal `()` for all draw/destroy/barrier commands.

## [0.30.16] - 2026-07-12

### Changed
Expand Down
40 changes: 34 additions & 6 deletions cmd/vk-gen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"encoding/xml"
"flag"
"fmt"
"go/format"
"os"
"path/filepath"
"strconv"
Expand All @@ -22,7 +23,7 @@ import (

var (
specFile = flag.String("spec", "vk.xml", "Path to vk.xml specification")
outputDir = flag.String("out", "../hal/vulkan/vk/", "Output directory")
outputDir = flag.String("out", "../../hal/vulkan/vk/", "Output directory")
// apiVersion reserved for future multi-API support (e.g., vulkansc)
)

Expand Down Expand Up @@ -71,6 +72,25 @@ func main() {
os.Exit(1)
}

// Format generated files with gofmt.
for _, name := range []string{"const_gen.go", "types_gen.go", "commands_gen.go"} {
path := filepath.Join(*outputDir, name)
src, err := os.ReadFile(path)
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading %s for formatting: %v\n", name, err)
os.Exit(1)
}
formatted, err := format.Source(src)
if err != nil {
fmt.Fprintf(os.Stderr, "Error formatting %s: %v\n", name, err)
os.Exit(1)
}
if err := os.WriteFile(path, formatted, 0644); err != nil {
fmt.Fprintf(os.Stderr, "Error writing formatted %s: %v\n", name, err)
os.Exit(1)
}
}

fmt.Println("Generation complete!")
}

Expand Down Expand Up @@ -708,10 +728,18 @@ func generateCommands(registry *Registry, outDir string) error {
}
defer f.Close()

fmt.Fprintln(f, "//go:build !(js && wasm)")
fmt.Fprintln(f, "")
fmt.Fprintln(f, "// Code generated by vk-gen. DO NOT EDIT.")
fmt.Fprintln(f, "")
fmt.Fprintln(f, "package vk")
fmt.Fprintln(f, "")
fmt.Fprintln(f, "// FFI error handling follows ADR-049 three-tier strategy:")
fmt.Fprintln(f, "// Tier 1 (creation/submit): check both FFI error and API result code")
fmt.Fprintln(f, "// Tier 2 (void/hot path): infallible by GPU API contract — Vulkan §6.6, WebGPU §21.2")
fmt.Fprintln(f, "// Tier 3 (platform syscalls): use errno for diagnostics (Wayland, X11, Win32)")
fmt.Fprintln(f, "// Enterprise reference: Rust wgpu-hal returns () for all draw/destroy/barrier commands.")
fmt.Fprintln(f, "")
fmt.Fprintln(f, "import (")
fmt.Fprintln(f, "\t\"unsafe\"")
fmt.Fprintln(f, "")
Expand Down Expand Up @@ -1122,26 +1150,26 @@ func generateCommandMethod(f *os.File, cmd Command) error {
fmt.Fprintln(f, "\t}")

// Call ffi.CallFunction with goffi error checking (VK-002).
// goffi v0.6.0 returns (syscall.Errno, error); errno unused for Vulkan (VkResult is the API error).
// goffi returns zeros on nil function pointer — no crash, no error —
// which produces false VK_SUCCESS with null output handles (gogpu#119).
resultPtr := "nil"
if hasReturn {
resultPtr = "unsafe.Pointer(&result)"
}
if returnType == "VkResult" {
// VkResult functions: check goffi transport error and return
// ErrorInitializationFailed if the function pointer is nil or call failed.
fmt.Fprintf(f, "\tif err := ffi.CallFunction(&%s, c.%s, %s, args[:]); err != nil {\n", callInterface, goFieldName, resultPtr)
// Tier 1 (ADR-049): VkResult functions — check FFI transport error, then API result.
fmt.Fprintf(f, "\tif _, err := ffi.CallFunction(&%s, c.%s, %s, args[:]); err != nil {\n", callInterface, goFieldName, resultPtr)
fmt.Fprintln(f, "\t\treturn ErrorInitializationFailed")
fmt.Fprintln(f, "\t}")
} else {
// Void/other functions: guard against nil function pointer.
// Tier 2 (ADR-049): void/query functions — infallible by Vulkan spec §6.6.
fmt.Fprintf(f, "\tif c.%s == nil {\n\t\treturn", goFieldName)
if hasReturn && returnType != "VkResult" {
fmt.Fprint(f, " 0")
}
fmt.Fprintln(f, "\n\t}")
fmt.Fprintf(f, "\t_ = ffi.CallFunction(&%s, c.%s, %s, args[:])\n", callInterface, goFieldName, resultPtr)
fmt.Fprintf(f, "\t_, _ = ffi.CallFunction(&%s, c.%s, %s, args[:])\n", callInterface, goFieldName, resultPtr)
}

// Return result
Expand Down
2 changes: 1 addition & 1 deletion cmd/vulkan-renderpass-test/ffi_direct.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func DirectCreatePipeline(cmds *vk.Commands, device vk.Device, createInfo *vk.Gr
fmt.Printf(" createInfoPtr: %p\n", createInfoPtr)
fmt.Printf(" pipelinePtr (output): %p, value: 0x%X\n", pipelinePtr, pipeline)

_ = ffi.CallFunction(&cif, fnPtr, unsafe.Pointer(&result), args[:])
_, _ = ffi.CallFunction(&cif, fnPtr, unsafe.Pointer(&result), args[:])

fmt.Printf(" result: %d\n", result)
fmt.Printf(" pipeline after call: 0x%X\n", pipeline)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/gogpu/wgpu
go 1.25.0

require (
github.com/go-webgpu/goffi v0.5.6
github.com/go-webgpu/goffi v0.6.0
github.com/go-webgpu/webgpu v0.5.2
github.com/gogpu/gpucontext v0.21.1
github.com/gogpu/gputypes v0.5.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
github.com/go-webgpu/goffi v0.5.6 h1:jAN4ciR63DcpU/qdX4A3wbNvcpZ6AdFK+H7OB/1+9Lc=
github.com/go-webgpu/goffi v0.5.6/go.mod h1:wfoxNsJkU+5RFbV1kNN1kunhc1lFHuJKK3zpgx08/uM=
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/go-webgpu/webgpu v0.5.2 h1:E+yKCbRw/Dgn77pb/CX3kXatwwROGX7/B4ZFbRPaRbQ=
github.com/go-webgpu/webgpu v0.5.2/go.mod h1:d2RR3tI2kUtcMfpC+qJt46IRkDslRhftjCfQvnprGkY=
github.com/gogpu/gpucontext v0.21.1 h1:/X96uCLG8QtQVfGPYPz8ycnfsAg0iTyzqzEadWBUupU=
Expand Down
8 changes: 4 additions & 4 deletions hal/gles/egl/display.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func OpenX11Display() *DisplayOwner {
var display uintptr
var displayName uintptr // NULL pointer for default display
args := [1]unsafe.Pointer{unsafe.Pointer(&displayName)}
_ = ffi.CallFunction(&cifXOpenDisplay, symXOpenDisplay, unsafe.Pointer(&display), args[:])
_, _ = ffi.CallFunction(&cifXOpenDisplay, symXOpenDisplay, unsafe.Pointer(&display), args[:])
if display == 0 {
return nil
}
Expand Down Expand Up @@ -147,14 +147,14 @@ func TestWaylandDisplay() *DisplayOwner {
var display uintptr
var displayName uintptr // NULL pointer for default display
args := [1]unsafe.Pointer{unsafe.Pointer(&displayName)}
_ = ffi.CallFunction(&cifWlDisplayConnect, symWlDisplayConnect, unsafe.Pointer(&display), args[:])
_, _ = ffi.CallFunction(&cifWlDisplayConnect, symWlDisplayConnect, unsafe.Pointer(&display), args[:])
if display == 0 {
return nil
}

// Immediately disconnect - we just wanted to test availability
argsDisconnect := [1]unsafe.Pointer{unsafe.Pointer(&display)}
_ = ffi.CallFunction(&cifWlDisplayDisconnect, symWlDisplayDisconnect, nil, argsDisconnect[:])
_, _ = ffi.CallFunction(&cifWlDisplayDisconnect, symWlDisplayDisconnect, nil, argsDisconnect[:])

return &DisplayOwner{
kind: WindowKindWayland,
Expand Down Expand Up @@ -314,7 +314,7 @@ func (d *DisplayOwner) Close() {
// goffi API requires pointer TO pointer value (avalue is slice of pointers to argument values)
displayPtr := d.display
args := [1]unsafe.Pointer{unsafe.Pointer(&displayPtr)}
_ = ffi.CallFunction(&cifXCloseDisplay, symXCloseDisplay, unsafe.Pointer(&result), args[:])
_, _ = ffi.CallFunction(&cifXCloseDisplay, symXCloseDisplay, unsafe.Pointer(&result), args[:])
}
case WindowKindWayland:
// Wayland display is managed by the window system
Expand Down
54 changes: 32 additions & 22 deletions hal/gles/egl/egl.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@

//go:build linux && !(js && wasm)

// FFI error handling follows ADR-049 three-tier strategy:
//
// Tier 1 (creation/submit): check both FFI error and API result code
// Tier 2 (void/hot path): infallible by GPU API contract — Vulkan §6.6, WebGPU §21.2
// Tier 3 (platform syscalls): use errno for diagnostics (Wayland, X11, Win32)
//
// Enterprise reference: Rust wgpu-hal returns () for all draw/destroy/barrier commands.
//
// EGL calls are Tier 2: EGL errors are reported via eglGetError(), not FFI return values.
// EGLBoolean returns are checked after the call, not at the FFI transport level.
package egl

import (
Expand Down Expand Up @@ -397,7 +407,7 @@ func prepareEGLCallInterfaces() error {
// GetError returns the last EGL error.
func GetError() EGLInt {
var result EGLInt
_ = ffi.CallFunction(&cifEglGetError, symEglGetError, unsafe.Pointer(&result), nil)
_, _ = ffi.CallFunction(&cifEglGetError, symEglGetError, unsafe.Pointer(&result), nil)
return result
}

Expand All @@ -407,7 +417,7 @@ func GetDisplay(displayID EGLNativeDisplayType) EGLDisplay {
args := [1]unsafe.Pointer{
unsafe.Pointer(&displayID),
}
_ = ffi.CallFunction(&cifEglGetDisplay, symEglGetDisplay, unsafe.Pointer(&result), args[:])
_, _ = ffi.CallFunction(&cifEglGetDisplay, symEglGetDisplay, unsafe.Pointer(&result), args[:])
return result
}

Expand All @@ -421,7 +431,7 @@ func GetPlatformDisplay(platform EGLEnum, nativeDisplay uintptr, attribList *EGL
unsafe.Pointer(&nativeDisplay),
unsafe.Pointer(&attribList),
}
_ = ffi.CallFunction(&cifEglGetPlatformDisplay, symEglGetPlatformDisplay, unsafe.Pointer(&result), args[:])
_, _ = ffi.CallFunction(&cifEglGetPlatformDisplay, symEglGetPlatformDisplay, unsafe.Pointer(&result), args[:])
return result
}
// Fallback to eglGetDisplay
Expand All @@ -445,7 +455,7 @@ func Initialize(dpy EGLDisplay, major *EGLInt, minor *EGLInt) EGLBoolean {
unsafe.Pointer(&majorPtr),
unsafe.Pointer(&minorPtr),
}
_ = ffi.CallFunction(&cifEglInitialize, symEglInitialize, unsafe.Pointer(&result), args[:])
_, _ = ffi.CallFunction(&cifEglInitialize, symEglInitialize, unsafe.Pointer(&result), args[:])
return result
}

Expand All @@ -455,7 +465,7 @@ func Terminate(dpy EGLDisplay) EGLBoolean {
args := [1]unsafe.Pointer{
unsafe.Pointer(&dpy),
}
_ = ffi.CallFunction(&cifEglTerminate, symEglTerminate, unsafe.Pointer(&result), args[:])
_, _ = ffi.CallFunction(&cifEglTerminate, symEglTerminate, unsafe.Pointer(&result), args[:])
return result
}

Expand All @@ -466,7 +476,7 @@ func QueryString(dpy EGLDisplay, name EGLInt) string {
unsafe.Pointer(&dpy),
unsafe.Pointer(&name),
}
_ = ffi.CallFunction(&cifEglQueryString, symEglQueryString, unsafe.Pointer(&ptr), args[:])
_, _ = ffi.CallFunction(&cifEglQueryString, symEglQueryString, unsafe.Pointer(&ptr), args[:])
if ptr == 0 {
return ""
}
Expand All @@ -487,7 +497,7 @@ func ChooseConfig(dpy EGLDisplay, attribList *EGLInt, configs *EGLConfig, config
unsafe.Pointer(&configSize),
unsafe.Pointer(&numConfigPtr),
}
_ = ffi.CallFunction(&cifEglChooseConfig, symEglChooseConfig, unsafe.Pointer(&result), args[:])
_, _ = ffi.CallFunction(&cifEglChooseConfig, symEglChooseConfig, unsafe.Pointer(&result), args[:])
return result
}

Expand All @@ -502,7 +512,7 @@ func GetConfigAttrib(dpy EGLDisplay, config EGLConfig, attribute EGLInt, value *
unsafe.Pointer(&attribute),
unsafe.Pointer(&valuePtr),
}
_ = ffi.CallFunction(&cifEglGetConfigAttrib, symEglGetConfigAttrib, unsafe.Pointer(&result), args[:])
_, _ = ffi.CallFunction(&cifEglGetConfigAttrib, symEglGetConfigAttrib, unsafe.Pointer(&result), args[:])
return result
}

Expand All @@ -517,7 +527,7 @@ func CreateWindowSurface(dpy EGLDisplay, config EGLConfig, win EGLNativeWindowTy
unsafe.Pointer(&win),
unsafe.Pointer(&attribListPtr),
}
_ = ffi.CallFunction(&cifEglCreateWindowSurface, symEglCreateWindowSurface, unsafe.Pointer(&result), args[:])
_, _ = ffi.CallFunction(&cifEglCreateWindowSurface, symEglCreateWindowSurface, unsafe.Pointer(&result), args[:])
return result
}

Expand Down Expand Up @@ -547,7 +557,7 @@ func CreatePlatformWindowSurface(dpy EGLDisplay, config EGLConfig, nativeWindow
unsafe.Pointer(&nativeWindow),
unsafe.Pointer(&attribListPtr),
}
_ = ffi.CallFunction(&cifEglCreatePlatformWindowSurface, symEglCreatePlatformWindowSurface, unsafe.Pointer(&result), args[:])
_, _ = ffi.CallFunction(&cifEglCreatePlatformWindowSurface, symEglCreatePlatformWindowSurface, unsafe.Pointer(&result), args[:])
return result
}

Expand All @@ -561,7 +571,7 @@ func CreatePbufferSurface(dpy EGLDisplay, config EGLConfig, attribList *EGLInt)
unsafe.Pointer(&config),
unsafe.Pointer(&attribListPtr),
}
_ = ffi.CallFunction(&cifEglCreatePbufferSurface, symEglCreatePbufferSurface, unsafe.Pointer(&result), args[:])
_, _ = ffi.CallFunction(&cifEglCreatePbufferSurface, symEglCreatePbufferSurface, unsafe.Pointer(&result), args[:])
return result
}

Expand All @@ -572,7 +582,7 @@ func DestroySurface(dpy EGLDisplay, surface EGLSurface) EGLBoolean {
unsafe.Pointer(&dpy),
unsafe.Pointer(&surface),
}
_ = ffi.CallFunction(&cifEglDestroySurface, symEglDestroySurface, unsafe.Pointer(&result), args[:])
_, _ = ffi.CallFunction(&cifEglDestroySurface, symEglDestroySurface, unsafe.Pointer(&result), args[:])
return result
}

Expand All @@ -582,7 +592,7 @@ func BindAPI(api EGLEnum) EGLBoolean {
args := [1]unsafe.Pointer{
unsafe.Pointer(&api),
}
_ = ffi.CallFunction(&cifEglBindAPI, symEglBindAPI, unsafe.Pointer(&result), args[:])
_, _ = ffi.CallFunction(&cifEglBindAPI, symEglBindAPI, unsafe.Pointer(&result), args[:])
return result
}

Expand All @@ -593,7 +603,7 @@ func SwapInterval(dpy EGLDisplay, interval EGLInt) EGLBoolean {
unsafe.Pointer(&dpy),
unsafe.Pointer(&interval),
}
_ = ffi.CallFunction(&cifEglSwapInterval, symEglSwapInterval, unsafe.Pointer(&result), args[:])
_, _ = ffi.CallFunction(&cifEglSwapInterval, symEglSwapInterval, unsafe.Pointer(&result), args[:])
return result
}

Expand All @@ -608,7 +618,7 @@ func CreateContext(dpy EGLDisplay, config EGLConfig, shareContext EGLContext, at
unsafe.Pointer(&shareContext),
unsafe.Pointer(&attribListPtr),
}
_ = ffi.CallFunction(&cifEglCreateContext, symEglCreateContext, unsafe.Pointer(&result), args[:])
_, _ = ffi.CallFunction(&cifEglCreateContext, symEglCreateContext, unsafe.Pointer(&result), args[:])
return result
}

Expand All @@ -619,7 +629,7 @@ func DestroyContext(dpy EGLDisplay, ctx EGLContext) EGLBoolean {
unsafe.Pointer(&dpy),
unsafe.Pointer(&ctx),
}
_ = ffi.CallFunction(&cifEglDestroyContext, symEglDestroyContext, unsafe.Pointer(&result), args[:])
_, _ = ffi.CallFunction(&cifEglDestroyContext, symEglDestroyContext, unsafe.Pointer(&result), args[:])
return result
}

Expand All @@ -632,21 +642,21 @@ func MakeCurrent(dpy EGLDisplay, draw EGLSurface, read EGLSurface, ctx EGLContex
unsafe.Pointer(&read),
unsafe.Pointer(&ctx),
}
_ = ffi.CallFunction(&cifEglMakeCurrent, symEglMakeCurrent, unsafe.Pointer(&result), args[:])
_, _ = ffi.CallFunction(&cifEglMakeCurrent, symEglMakeCurrent, unsafe.Pointer(&result), args[:])
return result
}

// GetCurrentContext returns the current EGL rendering context.
func GetCurrentContext() EGLContext {
var result EGLContext
_ = ffi.CallFunction(&cifEglGetCurrentContext, symEglGetCurrentContext, unsafe.Pointer(&result), nil)
_, _ = ffi.CallFunction(&cifEglGetCurrentContext, symEglGetCurrentContext, unsafe.Pointer(&result), nil)
return result
}

// GetCurrentDisplay returns the current EGL display connection.
func GetCurrentDisplay() EGLDisplay {
var result EGLDisplay
_ = ffi.CallFunction(&cifEglGetCurrentDisplay, symEglGetCurrentDisplay, unsafe.Pointer(&result), nil)
_, _ = ffi.CallFunction(&cifEglGetCurrentDisplay, symEglGetCurrentDisplay, unsafe.Pointer(&result), nil)
return result
}

Expand All @@ -657,7 +667,7 @@ func SwapBuffers(dpy EGLDisplay, surface EGLSurface) EGLBoolean {
unsafe.Pointer(&dpy),
unsafe.Pointer(&surface),
}
_ = ffi.CallFunction(&cifEglSwapBuffers, symEglSwapBuffers, unsafe.Pointer(&result), args[:])
_, _ = ffi.CallFunction(&cifEglSwapBuffers, symEglSwapBuffers, unsafe.Pointer(&result), args[:])
return result
}

Expand All @@ -670,7 +680,7 @@ func GetProcAddress(procname string) uintptr {
args := [1]unsafe.Pointer{
unsafe.Pointer(&ptr),
}
_ = ffi.CallFunction(&cifEglGetProcAddress, symEglGetProcAddress, unsafe.Pointer(&result), args[:])
_, _ = ffi.CallFunction(&cifEglGetProcAddress, symEglGetProcAddress, unsafe.Pointer(&result), args[:])
return result
}

Expand Down Expand Up @@ -743,7 +753,7 @@ func SwapBuffersWithDamage(dpy EGLDisplay, surface EGLSurface, rects *int32, nRe
unsafe.Pointer(&rects),
unsafe.Pointer(&nRects),
}
_ = ffi.CallFunction(&cifEglSwapBuffersWithDamageKHR, symEglSwapBuffersWithDamageKHR, unsafe.Pointer(&result), args[:])
_, _ = ffi.CallFunction(&cifEglSwapBuffersWithDamageKHR, symEglSwapBuffersWithDamageKHR, unsafe.Pointer(&result), args[:])
return result
}

Expand Down
Loading
Loading