Skip to content
Closed
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
239 changes: 239 additions & 0 deletions cmd/cgohints/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
package main

import (
"flag"
"fmt"
"go/format"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"slices"
"strings"
)

var (
path string
)

func init() {
flag.StringVar(&path, "i", "", "Path that contains the wgpu source files")
}

func listUsed() []string {
re := regexp.MustCompile(`\bC.((?:go_)?wgpu[A-Z][A-Za-z]+)`)

seen := map[string]bool{}
var names []string

files := mustv(filepath.Glob(path + "/*.go"))
for _, file := range files {
source := string(mustv(os.ReadFile(file)))
for _, match := range re.FindAllStringSubmatch(source, -1) {
fn := match[1]

if !seen[fn] {
seen[fn] = true
names = append(names, fn)
}
}
}

slices.Sort(names)

return names
}

func main() {
flag.Parse()

{
code := generateHints()
writeTo(path+"/cgohints.go", fmtGo(code))
}

{
code := generateWrappers()
writeTo(path+"/wgpu_go_wrappers.h", fmtC(code))
}
}

func generateHints() string {
withCallback := map[string]bool{}
for _, fn := range FindFunctions() {
if strings.Contains(fn.Params, "Callback") {
withCallback[fn.Name] = true
}

if fn.RetType == "WGPUFuture" {
withCallback[fn.Name] = true
}
}

var code []string
code = append(code, "// Code generated by cmd/refcount: DO NOT EDIT.\n")
code = append(code, "//go:build !js")
code = append(code, "package wgpu")

code = append(code, "/*")

for _, fn := range listUsed() {
fnWGPU := strings.TrimPrefix(fn, "go_")
if withCallback[fnWGPU] {
fmt.Println("Skipping function with callback:", fn)
continue
}

if fnWGPU == "wgpuDevicePoll" || fnWGPU == "wgpuQueuesubmit" {
// if i understood correctly,
// those methods can run previously scheduled callbacks
continue
}

code = append(code, "#cgo noescape "+fn)
code = append(code, "#cgo nocallback "+fn)
}

code = append(code, "*/")
code = append(code, `import "C"`)

return strings.Join(code, "\n")
}

func writeTo(path string, content string) {
out, err := os.Create(path)
if err != nil {
panic(err)
}

defer doClose(out)

mustv(out.WriteString(content))
}

func doClose(out io.Closer) {
_ = out.Close()
}

type Function struct {
RetType string
Name string
Params string
}

func FindFunctions() (funcs []Function) {
webgpuH := string(mustv(os.ReadFile(path + "/lib/linux/arm64/webgpu.h")))

re := regexp.MustCompile(`(?m)^WGPU_EXPORT(?: WGPU_NULLABLE)? (\S+) (wgpu[^(]+)\((.*)\)`)

for _, match := range re.FindAllStringSubmatch(webgpuH, -1) {
funcs = append(funcs, Function{
RetType: match[1],
Name: match[2],
Params: match[3],
})
}

return
}

func generateWrappers() string {
var lines []string
lines = append(lines, "// Code generated by cmd/refcount: DO NOT EDIT.\n")

lines = append(lines, `
#pragma once

#include <stdlib.h>
#include <string.h>
#include <wgpu.h>

static inline void webgpu_error_callback(WGPUPopErrorScopeStatus status, WGPUErrorType type, WGPUStringView message, WGPU_NULLABLE void* userdata1, WGPU_NULLABLE void* userdata2) {
char* err = (char *)userdata1;
if (type == WGPUErrorType_NoError) {
return;
}

strncpy(err, message.data, 1024-1);
}
`)

for _, fn := range FindFunctions() {
var paramValues []string

reValue := regexp.MustCompile(`([a-zA-Z0-9]+)(,|$)`)
for _, match := range reValue.FindAllStringSubmatch(fn.Params, -1) {
paramValues = append(paramValues, match[1])
}

var returnVar, returnStmt string
if fn.RetType != "void" {
returnVar = fn.RetType + " ret = "
returnStmt = "return ret;"
}

lines = append(lines, fmt.Sprintf(`
static inline %[6]s go_%[1]s(WGPUDevice _dev, char *err, %[2]s) {
wgpuDevicePushErrorScope(_dev, WGPUErrorFilter_Validation);
%[4]s %[1]s(%[3]s);

WGPUPopErrorScopeCallbackInfo const err_cb = {
.callback = webgpu_error_callback,
.userdata1 = (void*) err,
};

wgpuDevicePopErrorScope(_dev, err_cb);
%[5]s
}
`,
fn.Name,
fn.Params,
strings.Join(paramValues, ", "),
returnVar,
returnStmt,
fn.RetType,
))

lines = append(lines, "")
}

return strings.Join(lines, "\n")
}

func fmtGo(input string) string {
b, err := format.Source([]byte(input))
if err != nil {
fmt.Println()
fmt.Println(input)
fmt.Println()
log.Fatalf("cannot run 'go fmt' on file: %v", err)
}

return string(b)
}

func fmtC(input string) string {
var w strings.Builder

cmd := exec.Command("clang-format")
cmd.Stdin = strings.NewReader(input)
cmd.Stdout = &w
must(cmd.Run())

return w.String()
}

func must(err error) {
if err != nil {
panic(err)
}
}

func mustv[T any](v T, err error) T {
if err != nil {
panic(err)
}
return v
}
8 changes: 8 additions & 0 deletions examples/triangle/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@ func (s *State) Render() error {
return nil
}

defer func() {
if nextTexture != nil {
nextTexture.Release()
}
}()

view, err := nextTexture.TryCreateView(nil)
if err != nil {
return err
Expand Down Expand Up @@ -191,6 +197,8 @@ func (s *State) Render() error {
s.queue.Submit(cmdBuffer)
s.surface.Present()

nextTexture = nil

return nil
}

Expand Down
7 changes: 7 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ require (
github.com/oliverbestmann/webgpu/libs-ios v0.0.0-20260509160803-765e39d2a48b
github.com/oliverbestmann/webgpu/libs-linux v0.0.0-20260509160809-2fefaf7c9ead
github.com/oliverbestmann/webgpu/libs-windows v0.0.0-20260509160807-0bc32b12c7bc
github.com/stretchr/testify v1.11.1
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

retract v1.27.0 // published before deciding on a version scheme. we start at v1.0.0
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-gl/glfw/v3.4/glfw v0.1.0-pre.1.0.20260406072232-3ac4aa2bb164 h1:c87Nyz3ox3QbCl0yozQPeVPW4mmgFOSKY4yyc1TrS0w=
github.com/go-gl/glfw/v3.4/glfw v0.1.0-pre.1.0.20260406072232-3ac4aa2bb164/go.mod h1:T5Dn0JwIJOX1euPZ/iT4tq6nFYtmukjcYa7937HuYK8=
github.com/oliverbestmann/webgpu/libs-android v0.0.0-20260509160813-48db59792a15 h1:HPxVSV8C8JaxGfa9hjDhzNmryoqPF3EwESBTFWpxNBo=
Expand All @@ -10,3 +12,11 @@ github.com/oliverbestmann/webgpu/libs-linux v0.0.0-20260509160809-2fefaf7c9ead h
github.com/oliverbestmann/webgpu/libs-linux v0.0.0-20260509160809-2fefaf7c9ead/go.mod h1:SOeo2YWe2UxWxOeAHyZtwaSXkBbP78cGnm7I+6lIWV0=
github.com/oliverbestmann/webgpu/libs-windows v0.0.0-20260509160807-0bc32b12c7bc h1:YVCfgeByW1ibKniigozHCwF2wC26TDmAIJwQG40bCBM=
github.com/oliverbestmann/webgpu/libs-windows v0.0.0-20260509160807-0bc32b12c7bc/go.mod h1:58qRJHG2+mjEu/AKJFh026bz3xE1zEHYt41i4TBM8NE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
46 changes: 8 additions & 38 deletions wgpu/buffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,8 @@
package wgpu

/*

#include <stdlib.h>
#include <wgpu.h>

extern void gowebgpu_error_callback_c(enum WGPUPopErrorScopeStatus status, WGPUErrorType type, WGPUStringView message, void * userdata, void * userdata2);

#include "wgpu_go_wrappers.h"
extern void gowebgpu_buffer_map_callback_c(WGPUMapAsyncStatus status, WGPUStringView message, void *userdata, void *userdata2);

static inline void gowebgpu_buffer_map_async(WGPUBuffer buffer, WGPUMapMode mode, size_t offset, size_t size, WGPUBufferMapCallbackInfo callback, WGPUDevice device, void * error_userdata) {
wgpuDevicePushErrorScope(device, WGPUErrorFilter_Validation);
wgpuBufferMapAsync(buffer, mode, offset, size, callback);

WGPUPopErrorScopeCallbackInfo const err_cb = {
.callback = gowebgpu_error_callback_c,
.userdata1 = error_userdata,
};

wgpuDevicePopErrorScope(device, err_cb);
}

static inline void gowebgpu_buffer_unmap(WGPUBuffer buffer, WGPUDevice device, void * error_userdata) {
wgpuDevicePushErrorScope(device, WGPUErrorFilter_Validation);
wgpuBufferUnmap(buffer);

WGPUPopErrorScopeCallbackInfo const err_cb = {
.callback = gowebgpu_error_callback_c,
.userdata1 = error_userdata,
};

wgpuDevicePopErrorScope(device, err_cb);
}

*/
import "C"
import (
Expand Down Expand Up @@ -75,7 +45,9 @@ func (p *Buffer) TryMapAsync(mode MapMode, offset uint64, size uint64, callback
errh := acquireErrorCallback()
defer errh.Done()

C.gowebgpu_buffer_map_async(
C.go_wgpuBufferMapAsync(
p.device,
errh.ToPointer(),
p.ref,
C.WGPUMapMode(mode),
C.size_t(offset),
Expand All @@ -84,22 +56,20 @@ func (p *Buffer) TryMapAsync(mode MapMode, offset uint64, size uint64, callback
callback: C.WGPUBufferMapCallback(C.gowebgpu_buffer_map_callback_c),
userdata1: callbackHandle.ToPointer(),
},
p.device,
errh.ToPointer(),
)

return errh.err
return errh.ToError()
}

func (p *Buffer) TryUnmap() error {
errh := acquireErrorCallback()
defer errh.Done()

C.gowebgpu_buffer_unmap(
p.ref,
C.go_wgpuBufferUnmap(
p.device,
errh.ToPointer(),
p.ref,
)

return errh.err
return errh.ToError()
}
Loading
Loading