diff --git a/.golangci.yml b/.golangci.yml
index 9a3ca65..451c462 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -79,10 +79,18 @@ linters:
linters:
- unused
- revive
- # FFI render_pipeline has C struct padding
+ # Conversion functions have large switch statements - inherently high complexity but straightforward
+ - path: convert\.go
+ linters:
+ - gocyclo
+ - cyclop
+ - funlen
+ # FFI render_pipeline has C struct padding and complex CreateRenderPipeline
- path: render_pipeline\.go
linters:
- unused
+ - gocyclo
+ - cyclop
# wgpu.go has procs for future use and long init
- path: wgpu\.go
linters:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3731053..70aa24b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,11 +19,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Integration with [gogpu ecosystem](https://github.com/gogpu) via gputypes
- Full webgpu.h spec compliance for enum values
+- Comprehensive conversion layer (`wgpu/convert.go`) for wgpu-native v27 compatibility
+ - TextureFormat (~45 formats), VertexFormat (~30 formats)
+ - VertexStepMode, TextureSampleType, TextureViewDimension, StorageTextureAccess
+ - Wire structs with correct FFI padding (uint64 flags)
### Fixed
- TextureFormat enum values mismatch (BGRA8Unorm was 0x17, now correct 0x1B)
- Compatibility with gogpu Rust backend
+- Struct padding in BindGroupLayout wire structs (sampler, texture, storage)
+- PipelineLayout creation in examples (use CreatePipelineLayoutSimple)
+- GetModuleHandleW: kernel32.dll instead of user32.dll (all Windows examples)
+- Sampler MaxAnisotropy default (wgpu-native requires >= 1)
+- Texture SampleCount/MipLevelCount defaults (wgpu-native requires >= 1)
+- render_bundle shader: fallback without primitive_index (works on all GPUs)
### Migration Guide
diff --git a/README.md b/README.md
index 6774ff2..e7af238 100644
--- a/README.md
+++ b/README.md
@@ -38,7 +38,7 @@ Pure Go WebGPU bindings using [goffi](https://github.com/go-webgpu/goffi) + [wgp
## Requirements
- Go 1.25+
-- wgpu-native v24.0.3.1 ([download](https://github.com/gfx-rs/wgpu-native/releases))
+- wgpu-native v27.0.4.0 ([download](https://github.com/gfx-rs/wgpu-native/releases))
## Installation
diff --git a/ROADMAP.md b/ROADMAP.md
index 05e47b4..b0aa703 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -1,223 +1,217 @@
-# go-webgpu - Development Roadmap
+# go-webgpu Roadmap
-> **Strategic Focus**: Production-grade Zero-CGO WebGPU bindings for Go
+> **Mission**: Production-grade Zero-CGO WebGPU bindings for Go
-**Last Updated**: 2024-12-24 | **Current Version**: v0.1.1 | **Target**: v1.0.0 stable
+[](https://github.com/go-webgpu/webgpu/projects)
+[](https://github.com/go-webgpu/webgpu/issues)
---
-## Vision
+## Disclaimer
-Build **production-ready, cross-platform WebGPU bindings** for Go with zero CGO dependency, enabling GPU-accelerated graphics and compute in pure Go applications.
+> **This roadmap represents our current plans and priorities, not commitments.**
+> Features, timelines, and priorities may change based on community feedback, technical constraints, and ecosystem developments. For the most current status, see our [GitHub Issues](https://github.com/go-webgpu/webgpu/issues) and [Project Board](https://github.com/go-webgpu/webgpu/projects).
-### Current State vs Target
+---
-| Metric | Current (v0.1.1) | Target (v1.0.0) |
-|--------|------------------|-----------------|
-| Platforms | Windows, Linux, macOS (x64, arm64) | All major platforms |
-| CGO Required | No (Zero-CGO) | No |
-| API Coverage | ~80% WebGPU | 100% WebGPU |
-| wgpu-native | v24.0.3.1 | Latest stable |
-| Test Coverage | ~70% | 90%+ |
-| Examples | 11 | 20+ |
+## Vision
----
+Enable **GPU-accelerated graphics and compute in pure Go** — no CGO, no complexity, just Go.
-## Release Strategy
-
-```
-v0.1.1 (Current) -> Hotfix: goffi PointerType bug + PR workflow
- |
-v0.2.0 (Next) -> API improvements, builder patterns
- |
-v0.3.0 -> Advanced features (storage textures, texture arrays)
- |
-v0.4.0 -> Performance optimizations
- |
-v0.5.0 -> Extended examples and documentation
- |
-v1.0.0-rc -> Feature freeze, API locked
- |
-v1.0.0 STABLE -> Production release with API stability guarantee
-```
+### Why go-webgpu?
+
+| Challenge | Our Solution |
+|-----------|--------------|
+| CGO complexity | Zero-CGO via [goffi](https://github.com/go-webgpu/goffi) FFI |
+| Cross-compilation pain | Pure Go builds for all platforms |
+| WebGPU fragmentation | Unified API via [gputypes](https://github.com/gogpu/gputypes) |
+| Vendor lock-in | Open source, part of [gogpu ecosystem](https://github.com/gogpu) |
---
-## v0.2.0 - API Improvements (NEXT)
+## Current Status
-**Goal**: Improve API ergonomics and developer experience
+| Metric | Status |
+|--------|--------|
+| **Latest Release** |  |
+| **Platforms** | Windows, Linux, macOS (x64, arm64) |
+| **API Coverage** | ~80% WebGPU |
+| **Examples** | 11 working demos |
+| **Test Coverage** | ~70% |
-| ID | Feature | Impact | Status |
-|----|---------|--------|--------|
-| API-001 | Builder pattern for descriptors | Better ergonomics | Planned |
-| API-002 | Error wrapping with context | Better debugging | Planned |
-| API-003 | Resource tracking helpers | Memory management | Planned |
+### Technology Stack
-**Target**: Q1 2025
+| Component | Version | Role |
+|-----------|---------|------|
+| [wgpu-native](https://github.com/gfx-rs/wgpu-native) | v27.0.4.0 | WebGPU implementation (Rust) |
+| [goffi](https://github.com/go-webgpu/goffi) | v0.3.7 | Zero-CGO FFI layer |
+| [gputypes](https://github.com/gogpu/gputypes) | latest | WebGPU type definitions |
---
-## v0.3.0 - Advanced Features (MEDIUM PRIORITY)
-
-**Goal**: Complete WebGPU API coverage
+## Roadmap Phases
-| ID | Feature | Impact | Status |
-|----|---------|--------|--------|
-| FEAT-001 | Storage textures | Compute image processing | Planned |
-| FEAT-002 | Texture arrays | Sprite sheets, cubemaps | Planned |
-| FEAT-003 | Occlusion queries | Visibility testing | Planned |
-| FEAT-004 | Pipeline statistics | Performance profiling | Planned |
-| FEAT-005 | Multi-draw indirect | Batch rendering | Planned |
+We use GitHub labels to track feature progress:
-**Target**: Q2 2025
+| Label | Meaning |
+|-------|---------|
+| `phase:exploring` | Under consideration, gathering feedback |
+| `phase:design` | Actively designing solution |
+| `phase:development` | Implementation in progress |
+| `phase:preview` | Available for testing |
+| `phase:stable` | Production ready |
---
-## v0.4.0 - Performance (MEDIUM PRIORITY)
+## Now: Stability & Ecosystem
-**Goal**: Optimize hot paths and reduce allocations
+**Focus**: Ensure rock-solid foundation for production use.
-| ID | Feature | Impact | Status |
-|----|---------|--------|--------|
-| PERF-001 | Command buffer pooling | Reduce allocations | Planned |
-| PERF-002 | Descriptor caching | Faster pipeline creation | Planned |
-| PERF-003 | Batch resource creation | Startup optimization | Planned |
-| PERF-004 | Memory-mapped staging | Faster uploads | Planned |
-
-**Target**: Q2 2025
+| Feature | Status | Issue |
+|---------|--------|-------|
+| gputypes integration | `stable` | — |
+| wgpu-native v27 compatibility | `stable` | — |
+| All 11 examples working | `stable` | — |
+| Enum conversion layer | `stable` | — |
---
-## v0.5.0 - Examples & Documentation (MEDIUM PRIORITY)
+## Next: Advanced Features
-**Goal**: Comprehensive learning resources
+**Focus**: Complete WebGPU API coverage.
-### New Examples
+| Feature | Status | Issue |
+|---------|--------|-------|
+| Storage textures | `exploring` | [#TBD](https://github.com/go-webgpu/webgpu/issues) |
+| Texture arrays | `exploring` | [#TBD](https://github.com/go-webgpu/webgpu/issues) |
+| Occlusion queries | `exploring` | [#TBD](https://github.com/go-webgpu/webgpu/issues) |
+| Pipeline statistics | `exploring` | [#TBD](https://github.com/go-webgpu/webgpu/issues) |
+| Multi-draw indirect | `exploring` | [#TBD](https://github.com/go-webgpu/webgpu/issues) |
-| ID | Example | Demonstrates |
-|----|---------|--------------|
-| EX-001 | PBR Renderer | Material system, lighting |
-| EX-002 | Shadow Mapping | Depth textures, multi-pass |
-| EX-003 | Post-processing | Framebuffers, effects |
-| EX-004 | Particle System | Compute + render integration |
-| EX-005 | Text Rendering | Texture atlases, SDF fonts |
-| EX-006 | Deferred Shading | G-buffer, MRT |
-| EX-007 | Ray Marching | Compute shaders |
-| EX-008 | Image Processing | Compute filters |
-| EX-009 | Physics Simulation | GPU compute |
+---
-### Documentation
+## Later: Performance & DX
-| ID | Document | Content |
-|----|----------|---------|
-| DOC-001 | API Reference | Complete godoc |
-| DOC-002 | Migration Guide | From other GPU libs |
-| DOC-003 | Performance Guide | Optimization tips |
-| DOC-004 | Troubleshooting | Common issues |
+**Focus**: Optimize performance and developer experience.
-**Target**: Q3 2025
+| Feature | Status | Issue |
+|---------|--------|-------|
+| Builder pattern for descriptors | `exploring` | — |
+| Command buffer pooling | `exploring` | — |
+| Descriptor caching | `exploring` | — |
+| Memory-mapped staging | `exploring` | — |
+| Error wrapping with context | `exploring` | — |
---
-## v1.0.0 - Production Ready
+## Future: Extended Examples
-**Requirements**:
-- [ ] All v0.2.0-v0.5.0 features complete
-- [ ] API stability guarantee
-- [ ] Comprehensive documentation
+| Example | Demonstrates | Status |
+|---------|--------------|--------|
+| PBR Renderer | Material system, lighting | `exploring` |
+| Shadow Mapping | Depth textures, multi-pass | `exploring` |
+| Post-processing | Framebuffers, effects | `exploring` |
+| Particle System | Compute + render | `exploring` |
+| Text Rendering | SDF fonts, atlases | `exploring` |
+| Deferred Shading | G-buffer, MRT | `exploring` |
+
+---
+
+## v1.0 Requirements
+
+Before we tag v1.0.0 stable:
+
+- [ ] 100% WebGPU API coverage
- [ ] 90%+ test coverage
+- [ ] Comprehensive documentation
- [ ] Performance benchmarks
- [ ] Security review
+- [ ] API stability guarantee
-**Guarantees**:
-- API stability (no breaking changes in v1.x.x)
+**v1.0 Guarantees**:
+- No breaking changes in v1.x.x
- Semantic versioning
-- Long-term support
-
-**Target**: Q4 2025
+- Long-term support commitment
---
-## Feature Comparison Matrix
-
-| Feature | wgpu-rs | Dawn | go-webgpu v0.1 | go-webgpu v1.0 |
-|---------|---------|------|----------------|----------------|
-| Zero-CGO | N/A | N/A | Yes | Yes |
-| Windows x64 | Yes | Yes | Yes | Yes |
-| Linux x64 | Yes | Yes | Yes | Yes |
-| Linux ARM64 | Yes | Yes | Yes | Yes |
-| macOS x64 | Yes | Yes | Yes | Yes |
-| macOS ARM64 | Yes | Yes | Yes | Yes |
-| Buffer mapping | Yes | Yes | Yes | Yes |
-| Compute shaders | Yes | Yes | Yes | Yes |
-| Render bundles | Yes | Yes | Yes | Yes |
-| Timestamp queries | Yes | Yes | Yes | Yes |
-| Storage textures | Yes | Yes | No | Yes |
-| Texture arrays | Yes | Yes | No | Yes |
+## Out of Scope
----
+Features we **do not plan** to implement:
-## Current Examples (v0.1.x)
-
-| Example | Features Demonstrated |
-|---------|----------------------|
-| Triangle | Basic rendering, shaders |
-| Colored Triangle | Vertex attributes |
-| Rotating Triangle | Uniform buffers, animation |
-| Textured Quad | Texture sampling, UV coords |
-| 3D Cube | Depth buffer, transforms, MVP |
-| MRT | Multiple render targets |
-| Compute | Compute shaders, storage buffers |
-| Instanced | Instance rendering, vertex step mode |
-| RenderBundle | Pre-recorded commands |
-| Timestamp Query | GPU timing |
-| Error Handling | Error scopes |
+| Feature | Reason |
+|---------|--------|
+| WebGL fallback | WebGPU-only library |
+| DirectX 11 backend | wgpu-native uses D3D12 |
+| OpenGL backend | wgpu-native uses Vulkan/Metal |
+| Custom shader language | WGSL standard only |
+| Browser support | Native applications only |
---
-## Dependencies
+## How to Contribute
-| Dependency | Version | Purpose |
-|------------|---------|---------|
-| wgpu-native | v24.0.3.1 | WebGPU implementation |
-| goffi | v0.3.3 | Pure-Go FFI (x64 + ARM64) |
-| Go | 1.25+ | Language runtime |
+We welcome contributions! Here's how to get involved:
-### Upstream Tracking
+### 1. Find an Issue
-- **wgpu-native**: Track releases for new features and security fixes
-- **goffi**: Track for performance improvements and new platforms
+- [`good-first-issue`](https://github.com/go-webgpu/webgpu/labels/good-first-issue) — Great for newcomers
+- [`help-wanted`](https://github.com/go-webgpu/webgpu/labels/help-wanted) — Community contributions welcome
+- [`priority:high`](https://github.com/go-webgpu/webgpu/labels/priority%3Ahigh) — Most impactful work
----
+### 2. Propose Features
-## Out of Scope
+Open a [Feature Request](https://github.com/go-webgpu/webgpu/issues/new?template=feature_request.md) to discuss before implementing.
+
+### 3. Submit PRs
-**Not planned**:
-- WebGL fallback (WebGPU only)
-- DirectX 11 backend (wgpu-native uses D3D12)
-- OpenGL backend (wgpu-native uses Vulkan/Metal)
-- Custom shader language (WGSL only)
+See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
+
+### 4. Join Discussion
+
+- [GitHub Discussions](https://github.com/go-webgpu/webgpu/discussions) — Questions, ideas, showcase
+- [Issues](https://github.com/go-webgpu/webgpu/issues) — Bug reports, feature requests
---
-## Contributing
+## Upstream Dependencies
-See [CONTRIBUTING.md](CONTRIBUTING.md) for how to contribute to the roadmap.
+We track these projects for updates:
-Priority features are marked in GitHub Issues with labels:
-- `priority:high` - Next release
-- `priority:medium` - Future release
-- `help-wanted` - Community contributions welcome
+| Project | What We Track | Our Issue |
+|---------|---------------|-----------|
+| [wgpu-native](https://github.com/gfx-rs/wgpu-native) | Releases, security fixes | [#3](https://github.com/go-webgpu/webgpu/issues/3) |
+| [webgpu-headers](https://github.com/webgpu-native/webgpu-headers) | Spec changes | [#3](https://github.com/go-webgpu/webgpu/issues/3) |
+| [goffi](https://github.com/go-webgpu/goffi) | Performance, platforms | — |
+| [gputypes](https://github.com/gogpu/gputypes) | Type definitions | — |
---
## Release History
-| Version | Date | Type | Key Changes |
-|---------|------|------|-------------|
-| v0.1.1 | 2024-12-24 | Hotfix | goffi v0.3.3 (PointerType fix), PR workflow |
-| v0.1.0 | 2024-11-28 | Initial | Core API, 11 examples, 5 platforms (x64 + ARM64) |
+| Version | Date | Highlights |
+|---------|------|------------|
+| **v0.2.0** | 2026-01-29 | gputypes integration, wgpu-native v27, all examples fixed |
+| v0.1.4 | 2026-01-03 | goffi v0.3.7 (ARM64 Darwin) |
+| v0.1.3 | 2025-12-29 | goffi v0.3.6 (ARM64 HFA fix) |
+| v0.1.2 | 2025-12-27 | goffi v0.3.5 |
+| v0.1.1 | 2024-12-24 | goffi hotfix, PR workflow |
+| v0.1.0 | 2024-11-28 | Initial release, 11 examples, 5 platforms |
+
+See [CHANGELOG.md](CHANGELOG.md) for detailed release notes.
+
+---
+
+## Related Projects
+
+| Project | Description |
+|---------|-------------|
+| [gogpu](https://github.com/gogpu) | Pure Go WebGPU ecosystem |
+| [gputypes](https://github.com/gogpu/gputypes) | Shared WebGPU type definitions |
+| [goffi](https://github.com/go-webgpu/goffi) | Zero-CGO FFI for Go |
---
-*Current: v0.1.1 | Next: v0.2.0 (API Improvements) | Target: v1.0.0 (Q4 2025)*
+
+ This roadmap is inspired by GitHub's public roadmap practices.
+
diff --git a/examples/README.md b/examples/README.md
index f678418..1834ae1 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -266,5 +266,7 @@ To add a new example:
---
-**Last Updated:** 2025-11-28
-**go-webgpu Version:** 0.1.0
+**Last Updated:** 2026-01-29
+**go-webgpu Version:** 0.2.0
+
+**Note:** All examples use [gputypes](https://github.com/gogpu/gputypes) for WebGPU type definitions.
diff --git a/examples/colored-triangle/main.go b/examples/colored-triangle/main.go
index 1f64b83..2b80e07 100644
--- a/examples/colored-triangle/main.go
+++ b/examples/colored-triangle/main.go
@@ -47,7 +47,8 @@ var (
procDefWindowProcW = user32.NewProc("DefWindowProcW")
procPostQuitMessage = user32.NewProc("PostQuitMessage")
procLoadCursorW = user32.NewProc("LoadCursorW")
- procGetModuleHandleW = user32.NewProc("GetModuleHandleW")
+ kernel32 = windows.NewLazyDLL("kernel32.dll")
+ procGetModuleHandleW = kernel32.NewProc("GetModuleHandleW")
)
// WNDCLASSEXW represents the Win32 WNDCLASSEXW structure.
diff --git a/examples/cube/main.go b/examples/cube/main.go
index 6af012a..2526027 100644
--- a/examples/cube/main.go
+++ b/examples/cube/main.go
@@ -50,7 +50,8 @@ var (
procDefWindowProcW = user32.NewProc("DefWindowProcW")
procPostQuitMessage = user32.NewProc("PostQuitMessage")
procLoadCursorW = user32.NewProc("LoadCursorW")
- procGetModuleHandleW = user32.NewProc("GetModuleHandleW")
+ kernel32 = windows.NewLazyDLL("kernel32.dll")
+ procGetModuleHandleW = kernel32.NewProc("GetModuleHandleW")
)
// WNDCLASSEXW represents the Win32 WNDCLASSEXW structure.
@@ -512,11 +513,7 @@ func (app *App) createBindGroup() error {
// createPipelineLayout creates the pipeline layout with bind group layout.
func (app *App) createPipelineLayout() (*wgpu.PipelineLayout, error) {
- pipelineLayout := app.device.CreatePipelineLayout(&wgpu.PipelineLayoutDescriptor{
- Label: wgpu.EmptyStringView(),
- BindGroupLayoutCount: 1,
- BindGroupLayouts: app.bindGroupLayout.Handle(),
- })
+ pipelineLayout := app.device.CreatePipelineLayoutSimple([]*wgpu.BindGroupLayout{app.bindGroupLayout})
if pipelineLayout == nil {
return nil, fmt.Errorf("failed to create pipeline layout")
}
diff --git a/examples/indirect/main.go b/examples/indirect/main.go
index fc9cc57..340f038 100644
--- a/examples/indirect/main.go
+++ b/examples/indirect/main.go
@@ -47,7 +47,8 @@ var (
procDefWindowProcW = user32.NewProc("DefWindowProcW")
procPostQuitMessage = user32.NewProc("PostQuitMessage")
procLoadCursorW = user32.NewProc("LoadCursorW")
- procGetModuleHandleW = user32.NewProc("GetModuleHandleW")
+ kernel32 = windows.NewLazyDLL("kernel32.dll")
+ procGetModuleHandleW = kernel32.NewProc("GetModuleHandleW")
)
// WNDCLASSEXW represents the Win32 WNDCLASSEXW structure.
diff --git a/examples/mrt/main.go b/examples/mrt/main.go
index 3fb1042..c47ff1c 100644
--- a/examples/mrt/main.go
+++ b/examples/mrt/main.go
@@ -51,7 +51,8 @@ var (
procDefWindowProcW = user32.NewProc("DefWindowProcW")
procPostQuitMessage = user32.NewProc("PostQuitMessage")
procLoadCursorW = user32.NewProc("LoadCursorW")
- procGetModuleHandleW = user32.NewProc("GetModuleHandleW")
+ kernel32 = windows.NewLazyDLL("kernel32.dll")
+ procGetModuleHandleW = kernel32.NewProc("GetModuleHandleW")
)
// WNDCLASSEXW represents the Win32 WNDCLASSEXW structure.
@@ -483,11 +484,7 @@ func (app *App) createBindGroup() error {
// createPipelineLayout creates the pipeline layout with bind group layout.
func (app *App) createPipelineLayout() (*wgpu.PipelineLayout, error) {
- pipelineLayout := app.device.CreatePipelineLayout(&wgpu.PipelineLayoutDescriptor{
- Label: wgpu.EmptyStringView(),
- BindGroupLayoutCount: 1,
- BindGroupLayouts: app.bindGroupLayout.Handle(),
- })
+ pipelineLayout := app.device.CreatePipelineLayoutSimple([]*wgpu.BindGroupLayout{app.bindGroupLayout})
if pipelineLayout == nil {
return nil, fmt.Errorf("failed to create pipeline layout")
}
diff --git a/examples/render_bundle/main.go b/examples/render_bundle/main.go
index 92cddcf..0f7faba 100644
--- a/examples/render_bundle/main.go
+++ b/examples/render_bundle/main.go
@@ -48,7 +48,8 @@ var (
procDefWindowProcW = user32.NewProc("DefWindowProcW")
procPostQuitMessage = user32.NewProc("PostQuitMessage")
procLoadCursorW = user32.NewProc("LoadCursorW")
- procGetModuleHandleW = user32.NewProc("GetModuleHandleW")
+ kernel32 = windows.NewLazyDLL("kernel32.dll")
+ procGetModuleHandleW = kernel32.NewProc("GetModuleHandleW")
)
// WNDCLASSEXW represents the Win32 WNDCLASSEXW structure.
@@ -97,9 +98,18 @@ type App struct {
}
// Shader source (WGSL)
+// NOTE: This shader uses a fallback approach for triangle coloring.
+// Instead of @builtin(primitive_index) (which requires PRIMITIVE_INDEX GPU capability),
+// we calculate the triangle index from vertex_index and pass color via varying.
+// This works on ALL GPUs, including older hardware without primitive_index support.
const shaderSource = `
+struct VertexOutput {
+ @builtin(position) position: vec4,
+ @location(0) color: vec3,
+}
+
@vertex
-fn vs_main(@builtin(vertex_index) idx: u32) -> @builtin(position) vec4 {
+fn vs_main(@builtin(vertex_index) idx: u32) -> VertexOutput {
// Three triangles at different positions
var positions = array, 9>(
// Triangle 1 (left)
@@ -115,19 +125,28 @@ fn vs_main(@builtin(vertex_index) idx: u32) -> @builtin(position) vec4 {
vec2(0.5, -0.3),
vec2(0.9, -0.3)
);
- return vec4(positions[idx], 0.0, 1.0);
-}
-@fragment
-fn fs_main(@builtin(primitive_index) prim_idx: u32) -> @location(0) vec4 {
- // Different color for each triangle
+ // Colors for each triangle
var colors = array, 3>(
vec3(1.0, 0.2, 0.2), // Red
vec3(0.2, 1.0, 0.2), // Green
vec3(0.2, 0.2, 1.0) // Blue
);
- let tri_idx = prim_idx;
- return vec4(colors[tri_idx], 1.0);
+
+ // Calculate triangle index from vertex index (3 vertices per triangle)
+ // This is the FALLBACK for @builtin(primitive_index)
+ let tri_idx = idx / 3u;
+
+ var out: VertexOutput;
+ out.position = vec4(positions[idx], 0.0, 1.0);
+ out.color = colors[tri_idx];
+ return out;
+}
+
+@fragment
+fn fs_main(in: VertexOutput) -> @location(0) vec4 {
+ // Use color passed from vertex shader (works on all GPUs!)
+ return vec4(in.color, 1.0);
}
`
@@ -142,6 +161,11 @@ func main() {
fmt.Println(" - Better driver optimization opportunities")
fmt.Println(" - Useful for static scene elements")
fmt.Println()
+ fmt.Println("Note: This example uses a FALLBACK approach for per-triangle coloring.")
+ fmt.Println("Instead of @builtin(primitive_index) (requires PRIMITIVE_INDEX capability),")
+ fmt.Println("we calculate triangle index from vertex_index in the vertex shader.")
+ fmt.Println("This works on ALL GPUs, including older hardware.")
+ fmt.Println()
app := &App{
width: windowWidth,
diff --git a/examples/rotating-triangle/main.go b/examples/rotating-triangle/main.go
index cdbeb1c..6b53538 100644
--- a/examples/rotating-triangle/main.go
+++ b/examples/rotating-triangle/main.go
@@ -50,7 +50,8 @@ var (
procDefWindowProcW = user32.NewProc("DefWindowProcW")
procPostQuitMessage = user32.NewProc("PostQuitMessage")
procLoadCursorW = user32.NewProc("LoadCursorW")
- procGetModuleHandleW = user32.NewProc("GetModuleHandleW")
+ kernel32 = windows.NewLazyDLL("kernel32.dll")
+ procGetModuleHandleW = kernel32.NewProc("GetModuleHandleW")
)
// WNDCLASSEXW represents the Win32 WNDCLASSEXW structure.
@@ -435,11 +436,7 @@ func (app *App) createBindGroup() error {
// createPipelineLayout creates the pipeline layout with bind group layout.
func (app *App) createPipelineLayout() (*wgpu.PipelineLayout, error) {
- pipelineLayout := app.device.CreatePipelineLayout(&wgpu.PipelineLayoutDescriptor{
- Label: wgpu.EmptyStringView(),
- BindGroupLayoutCount: 1,
- BindGroupLayouts: app.bindGroupLayout.Handle(),
- })
+ pipelineLayout := app.device.CreatePipelineLayoutSimple([]*wgpu.BindGroupLayout{app.bindGroupLayout})
if pipelineLayout == nil {
return nil, fmt.Errorf("failed to create pipeline layout")
}
diff --git a/examples/textured-quad/main.go b/examples/textured-quad/main.go
index 8429cc8..24a1776 100644
--- a/examples/textured-quad/main.go
+++ b/examples/textured-quad/main.go
@@ -48,7 +48,8 @@ var (
procDefWindowProcW = user32.NewProc("DefWindowProcW")
procPostQuitMessage = user32.NewProc("PostQuitMessage")
procLoadCursorW = user32.NewProc("LoadCursorW")
- procGetModuleHandleW = user32.NewProc("GetModuleHandleW")
+ kernel32 = windows.NewLazyDLL("kernel32.dll")
+ procGetModuleHandleW = kernel32.NewProc("GetModuleHandleW")
)
// WNDCLASSEXW represents the Win32 WNDCLASSEXW structure.
@@ -548,11 +549,7 @@ func (app *App) createPipeline() error {
defer shader.Release()
// Create pipeline layout with bind group layout
- pipelineLayout := app.device.CreatePipelineLayout(&wgpu.PipelineLayoutDescriptor{
- Label: wgpu.EmptyStringView(),
- BindGroupLayoutCount: 1,
- BindGroupLayouts: app.bindGroupLyt.Handle(),
- })
+ pipelineLayout := app.device.CreatePipelineLayoutSimple([]*wgpu.BindGroupLayout{app.bindGroupLyt})
if pipelineLayout == nil {
return fmt.Errorf("failed to create pipeline layout")
}
diff --git a/examples/triangle/main.go b/examples/triangle/main.go
index 1ae14bb..0d588e5 100644
--- a/examples/triangle/main.go
+++ b/examples/triangle/main.go
@@ -37,6 +37,7 @@ const (
var (
user32 = windows.NewLazyDLL("user32.dll")
+ kernel32 = windows.NewLazyDLL("kernel32.dll")
procRegisterClassExW = user32.NewProc("RegisterClassExW")
procCreateWindowExW = user32.NewProc("CreateWindowExW")
procShowWindow = user32.NewProc("ShowWindow")
@@ -47,7 +48,7 @@ var (
procDefWindowProcW = user32.NewProc("DefWindowProcW")
procPostQuitMessage = user32.NewProc("PostQuitMessage")
procLoadCursorW = user32.NewProc("LoadCursorW")
- procGetModuleHandleW = user32.NewProc("GetModuleHandleW")
+ procGetModuleHandleW = kernel32.NewProc("GetModuleHandleW")
)
// WNDCLASSEXW represents the Win32 WNDCLASSEXW structure.
diff --git a/wgpu/bindgroup.go b/wgpu/bindgroup.go
index 94fd8a1..6d58461 100644
--- a/wgpu/bindgroup.go
+++ b/wgpu/bindgroup.go
@@ -55,6 +55,99 @@ type BindGroupLayoutDescriptor struct {
Entries uintptr // *BindGroupLayoutEntry
}
+// =============================================================================
+// Wire structs for FFI (with converted enum values and uint64 ShaderStage)
+// wgpu-native uses uint64 for WGPUShaderStageFlags (via WGPUFlags typedef)
+// =============================================================================
+
+// bufferBindingLayoutWire is the FFI-compatible struct with wgpu-native enum values.
+type bufferBindingLayoutWire struct {
+ NextInChain uintptr
+ Type uint32 // wgpu-native value (converted from gputypes)
+ HasDynamicOffset Bool
+ MinBindingSize uint64
+}
+
+// samplerBindingLayoutWire is the FFI-compatible struct with wgpu-native enum values.
+// Size: 16 bytes (8 + 4 + 4 padding) - must match C struct padding
+type samplerBindingLayoutWire struct {
+ NextInChain uintptr
+ Type uint32 // wgpu-native value
+ _pad [4]byte // padding to 8-byte alignment (C struct padding)
+}
+
+// textureBindingLayoutWire is the FFI-compatible struct with wgpu-native enum values.
+// Size: 24 bytes (8 + 4 + 4 + 4 + 4 padding) - must match C struct padding
+type textureBindingLayoutWire struct {
+ NextInChain uintptr
+ SampleType uint32 // wgpu-native value
+ ViewDimension uint32 // wgpu-native value
+ Multisampled Bool // 4 bytes
+ _pad [4]byte // padding to 8-byte alignment
+}
+
+// storageTextureBindingLayoutWire is the FFI-compatible struct with wgpu-native enum values.
+// Size: 24 bytes (8 + 4 + 4 + 4 + 4 padding) - must match C struct padding
+type storageTextureBindingLayoutWire struct {
+ NextInChain uintptr
+ Access uint32 // wgpu-native value
+ Format uint32 // wgpu-native value
+ ViewDimension uint32 // wgpu-native value
+ _pad [4]byte // padding to 8-byte alignment
+}
+
+// bindGroupLayoutEntryWire is the FFI-compatible struct with converted enums.
+// CRITICAL: Visibility is uint64 because wgpu-native defines WGPUShaderStageFlags as uint64!
+type bindGroupLayoutEntryWire struct {
+ NextInChain uintptr
+ Binding uint32
+ _pad [4]byte // padding to align Visibility to 8 bytes
+ Visibility uint64 // WGPUShaderStageFlags = uint64 in wgpu-native!
+ Buffer bufferBindingLayoutWire
+ Sampler samplerBindingLayoutWire
+ Texture textureBindingLayoutWire
+ StorageTexture storageTextureBindingLayoutWire
+}
+
+// toWire converts a BindGroupLayoutEntry to its wire representation.
+func (e *BindGroupLayoutEntry) toWire() bindGroupLayoutEntryWire {
+ return bindGroupLayoutEntryWire{
+ NextInChain: e.NextInChain,
+ Binding: e.Binding,
+ Visibility: uint64(e.Visibility), // widen uint32 to uint64
+ Buffer: bufferBindingLayoutWire{
+ NextInChain: e.Buffer.NextInChain,
+ Type: toWGPUBufferBindingType(e.Buffer.Type),
+ HasDynamicOffset: e.Buffer.HasDynamicOffset,
+ MinBindingSize: e.Buffer.MinBindingSize,
+ },
+ Sampler: samplerBindingLayoutWire{
+ NextInChain: e.Sampler.NextInChain,
+ Type: toWGPUSamplerBindingType(e.Sampler.Type),
+ },
+ Texture: textureBindingLayoutWire{
+ NextInChain: e.Texture.NextInChain,
+ SampleType: toWGPUTextureSampleType(e.Texture.SampleType),
+ ViewDimension: toWGPUTextureViewDimension(e.Texture.ViewDimension),
+ Multisampled: e.Texture.Multisampled,
+ },
+ StorageTexture: storageTextureBindingLayoutWire{
+ NextInChain: e.StorageTexture.NextInChain,
+ Access: toWGPUStorageTextureAccess(e.StorageTexture.Access),
+ Format: toWGPUTextureFormat(e.StorageTexture.Format),
+ ViewDimension: toWGPUTextureViewDimension(e.StorageTexture.ViewDimension),
+ },
+ }
+}
+
+// bindGroupLayoutDescriptorWire is the FFI-compatible descriptor.
+type bindGroupLayoutDescriptorWire struct {
+ NextInChain uintptr
+ Label StringView
+ EntryCount uintptr
+ Entries uintptr // *bindGroupLayoutEntryWire
+}
+
// BindGroupEntry describes a single binding in a bind group.
type BindGroupEntry struct {
NextInChain uintptr // *ChainedStruct
@@ -76,14 +169,32 @@ type BindGroupDescriptor struct {
}
// CreateBindGroupLayout creates a bind group layout.
+// Entries are converted from gputypes to wgpu-native enum values before FFI call.
func (d *Device) CreateBindGroupLayout(desc *BindGroupLayoutDescriptor) *BindGroupLayout {
mustInit()
if desc == nil {
return nil
}
+
+ // If there are entries, we need to convert them to wire format
+ var wireDesc bindGroupLayoutDescriptorWire
+ wireDesc.NextInChain = desc.NextInChain
+ wireDesc.Label = desc.Label
+ wireDesc.EntryCount = desc.EntryCount
+
+ if desc.EntryCount > 0 && desc.Entries != 0 {
+ // Convert entries to wire format
+ entries := unsafe.Slice((*BindGroupLayoutEntry)(unsafe.Pointer(desc.Entries)), desc.EntryCount)
+ wireEntries := make([]bindGroupLayoutEntryWire, len(entries))
+ for i := range entries {
+ wireEntries[i] = entries[i].toWire()
+ }
+ wireDesc.Entries = uintptr(unsafe.Pointer(&wireEntries[0]))
+ }
+
handle, _, _ := procDeviceCreateBindGroupLayout.Call(
d.handle,
- uintptr(unsafe.Pointer(desc)),
+ uintptr(unsafe.Pointer(&wireDesc)),
)
if handle == 0 {
return nil
@@ -97,12 +208,27 @@ func (d *Device) CreateBindGroupLayoutSimple(entries []BindGroupLayoutEntry) *Bi
if len(entries) == 0 {
return nil
}
- desc := BindGroupLayoutDescriptor{
+
+ // Convert entries to wire format
+ wireEntries := make([]bindGroupLayoutEntryWire, len(entries))
+ for i := range entries {
+ wireEntries[i] = entries[i].toWire()
+ }
+
+ wireDesc := bindGroupLayoutDescriptorWire{
Label: EmptyStringView(),
EntryCount: uintptr(len(entries)),
- Entries: uintptr(unsafe.Pointer(&entries[0])),
+ Entries: uintptr(unsafe.Pointer(&wireEntries[0])),
}
- return d.CreateBindGroupLayout(&desc)
+
+ handle, _, _ := procDeviceCreateBindGroupLayout.Call(
+ d.handle,
+ uintptr(unsafe.Pointer(&wireDesc)),
+ )
+ if handle == 0 {
+ return nil
+ }
+ return &BindGroupLayout{handle: handle}
}
// Release releases the bind group layout.
diff --git a/wgpu/convert.go b/wgpu/convert.go
new file mode 100644
index 0000000..fa18edd
--- /dev/null
+++ b/wgpu/convert.go
@@ -0,0 +1,524 @@
+// Package wgpu provides conversion functions between gputypes (webgpu.h spec)
+// and wgpu-native internal values.
+//
+// Background: gputypes follows an older webgpu.h schema where enums start at 0.
+// wgpu-native v24+ uses a newer schema with BindingNotUsed=0, shifting other values by +1.
+// TextureFormat also differs due to removal of R16Unorm/R16Snorm from the spec.
+package wgpu
+
+import "github.com/gogpu/gputypes"
+
+// =============================================================================
+// BufferBindingType conversion
+// gputypes: Undefined=0, Uniform=1, Storage=2, ReadOnlyStorage=3
+// wgpu-native: BindingNotUsed=0, Undefined=1, Uniform=2, Storage=3, ReadOnlyStorage=4
+// =============================================================================
+
+func toWGPUBufferBindingType(t gputypes.BufferBindingType) uint32 {
+ // gputypes: Undefined=0, Uniform=1, Storage=2, ReadOnlyStorage=3
+ // wgpu-native: BindingNotUsed=0, Undefined=1, Uniform=2, Storage=3, ReadOnlyStorage=4
+ // Keep 0 as 0 (BindingNotUsed), shift others by +1
+ if t == 0 {
+ return 0 // BindingNotUsed
+ }
+ return uint32(t) + 1
+}
+
+// =============================================================================
+// SamplerBindingType conversion
+// gputypes: Undefined=0, Filtering=1, NonFiltering=2, Comparison=3
+// wgpu-native: BindingNotUsed=0, Undefined=1, Filtering=2, NonFiltering=3, Comparison=4
+// =============================================================================
+
+func toWGPUSamplerBindingType(t gputypes.SamplerBindingType) uint32 {
+ // gputypes: Undefined=0, Filtering=1, NonFiltering=2, Comparison=3
+ // wgpu-native: BindingNotUsed=0, Undefined=1, Filtering=2, NonFiltering=3, Comparison=4
+ // Keep 0 as 0 (BindingNotUsed), shift others by +1
+ if t == 0 {
+ return 0 // BindingNotUsed
+ }
+ return uint32(t) + 1
+}
+
+// =============================================================================
+// TextureSampleType conversion
+// gputypes: Undefined=0, Float=1, UnfilterableFloat=2, Depth=3, Sint=4, Uint=5
+// wgpu-native: BindingNotUsed=0, Undefined=1, Float=2, UnfilterableFloat=3, Depth=4, Sint=5, Uint=6
+// =============================================================================
+
+func toWGPUTextureSampleType(t gputypes.TextureSampleType) uint32 {
+ // gputypes: Undefined=0, Float=1, UnfilterableFloat=2, Depth=3, Sint=4, Uint=5
+ // wgpu-native: BindingNotUsed=0, Undefined=1, Float=2, UnfilterableFloat=3, Depth=4, Sint=5, Uint=6
+ // Keep 0 as 0 (BindingNotUsed), shift others by +1
+ if t == 0 {
+ return 0 // BindingNotUsed
+ }
+ return uint32(t) + 1
+}
+
+// =============================================================================
+// TextureViewDimension conversion
+// gputypes: Undefined=0, 1D=1, 2D=2, 2DArray=3, Cube=4, CubeArray=5, 3D=6
+// wgpu-native v27 (bac5208): Undefined=0, 1D=1, 2D=2, 2DArray=3, Cube=4, CubeArray=5, 3D=6
+// Values match! No conversion needed.
+// =============================================================================
+
+func toWGPUTextureViewDimension(t gputypes.TextureViewDimension) uint32 {
+ // Values match between gputypes and wgpu-native v27 - no conversion needed
+ return uint32(t)
+}
+
+// =============================================================================
+// StorageTextureAccess conversion
+// gputypes: Undefined=0, WriteOnly=1, ReadOnly=2, ReadWrite=3
+// wgpu-native: BindingNotUsed=0, Undefined=1, WriteOnly=2, ReadOnly=3, ReadWrite=4
+// =============================================================================
+
+func toWGPUStorageTextureAccess(t gputypes.StorageTextureAccess) uint32 {
+ // gputypes: Undefined=0, WriteOnly=1, ReadOnly=2, ReadWrite=3
+ // wgpu-native: BindingNotUsed=0, Undefined=1, WriteOnly=2, ReadOnly=3, ReadWrite=4
+ // Keep 0 as 0 (BindingNotUsed), shift others by +1
+ if t == 0 {
+ return 0 // BindingNotUsed
+ }
+ return uint32(t) + 1
+}
+
+// =============================================================================
+// TextureFormat conversion
+// gputypes follows older webgpu.h with R16Unorm/R16Snorm/RG16Unorm/RG16Snorm (4 formats).
+// wgpu-native v24+ uses newer spec where these were moved to extensions.
+// Result: formats after R8Sint are shifted by -2, and after RG8Sint by another -2.
+// =============================================================================
+
+func toWGPUTextureFormat(f gputypes.TextureFormat) uint32 {
+ // gputypes values (old spec with R16/RG16 Unorm/Snorm in core):
+ // 0=Undefined, 1-4=R8*, 5-6=R16Unorm/Snorm, 7-9=R16Uint/Sint/Float,
+ // 10-13=RG8*, 14-16=R32*, 17-18=RG16Unorm/Snorm, 19-21=RG16Uint/Sint/Float,
+ // 22-26=RGBA8*, 27-28=BGRA8*, 29=RGB10A2Uint, 30=RGB10A2Unorm,
+ // 31=RG11B10Ufloat, 32=RGB9E5Ufloat, 33-35=RG32*, 36-37=RGBA16Unorm/Snorm,
+ // 38-40=RGBA16Uint/Sint/Float, 41-43=RGBA32*, 44=Stencil8, 45=Depth16Unorm,
+ // 46=Depth24Plus, 47=Depth24PlusStencil8, 48=Depth32Float, 49=Depth32FloatStencil8
+ //
+ // webgpu-headers values (new spec - R16/RG16 Unorm/Snorm moved to extensions):
+ // 0=Undefined, 1-4=R8*, 5-7=R16Uint/Sint/Float (NO R16Unorm/Snorm!),
+ // 8-11=RG8*, 12-14=R32*, 15-17=RG16Uint/Sint/Float (NO RG16Unorm/Snorm!),
+ // 18-22=RGBA8*, 23-24=BGRA8*, 25=RGB10A2Uint, 26=RGB10A2Unorm,
+ // 27=RG11B10Ufloat, 28=RGB9E5Ufloat, 29-31=RG32*, 32-34=RGBA16Uint/Sint/Float,
+ // (NO RGBA16Unorm/Snorm!), 35-37=RGBA32*, 38=Stencil8, 39=Depth16Unorm,
+ // 40=Depth24Plus, 41=Depth24PlusStencil8, 42=Depth32Float, 43=Depth32FloatStencil8
+
+ // Use a lookup table for common formats
+ switch f {
+ case gputypes.TextureFormatUndefined:
+ return 0
+
+ // 8-bit R formats (1-4 → 1-4, same)
+ case gputypes.TextureFormatR8Unorm:
+ return 1
+ case gputypes.TextureFormatR8Snorm:
+ return 2
+ case gputypes.TextureFormatR8Uint:
+ return 3
+ case gputypes.TextureFormatR8Sint:
+ return 4
+
+ // R16 formats: gputypes has Unorm/Snorm at 5-6, wgpu-native doesn't
+ // R16Uint/Sint/Float: gputypes 7-9 → wgpu-native 5-7
+ case gputypes.TextureFormatR16Uint:
+ return 5
+ case gputypes.TextureFormatR16Sint:
+ return 6
+ case gputypes.TextureFormatR16Float:
+ return 7
+
+ // RG8 formats: gputypes 10-13 → wgpu-native 8-11
+ case gputypes.TextureFormatRG8Unorm:
+ return 8
+ case gputypes.TextureFormatRG8Snorm:
+ return 9
+ case gputypes.TextureFormatRG8Uint:
+ return 10
+ case gputypes.TextureFormatRG8Sint:
+ return 11
+
+ // R32 formats: gputypes 14-16 → wgpu-native 12-14
+ case gputypes.TextureFormatR32Float:
+ return 12
+ case gputypes.TextureFormatR32Uint:
+ return 13
+ case gputypes.TextureFormatR32Sint:
+ return 14
+
+ // RG16 formats: gputypes has Unorm/Snorm at 17-18, wgpu-native doesn't
+ // RG16Uint/Sint/Float: gputypes 19-21 → wgpu-native 15-17
+ case gputypes.TextureFormatRG16Uint:
+ return 15
+ case gputypes.TextureFormatRG16Sint:
+ return 16
+ case gputypes.TextureFormatRG16Float:
+ return 17
+
+ // RGBA8 formats: gputypes 22-26 → wgpu-native 18-22
+ case gputypes.TextureFormatRGBA8Unorm:
+ return 18
+ case gputypes.TextureFormatRGBA8UnormSrgb:
+ return 19
+ case gputypes.TextureFormatRGBA8Snorm:
+ return 20
+ case gputypes.TextureFormatRGBA8Uint:
+ return 21
+ case gputypes.TextureFormatRGBA8Sint:
+ return 22
+
+ // BGRA8 formats: gputypes 27-28 → wgpu-native 23-24
+ case gputypes.TextureFormatBGRA8Unorm:
+ return 23
+ case gputypes.TextureFormatBGRA8UnormSrgb:
+ return 24
+
+ // Packed formats: gputypes 29-32 → wgpu-native 25-28
+ case gputypes.TextureFormatRGB10A2Uint:
+ return 25
+ case gputypes.TextureFormatRGB10A2Unorm:
+ return 26
+ case gputypes.TextureFormatRG11B10Ufloat:
+ return 27
+ case gputypes.TextureFormatRGB9E5Ufloat:
+ return 28
+
+ // RG32 formats: gputypes 33-35 → wgpu-native 29-31
+ case gputypes.TextureFormatRG32Float:
+ return 29
+ case gputypes.TextureFormatRG32Uint:
+ return 30
+ case gputypes.TextureFormatRG32Sint:
+ return 31
+
+ // RGBA16 formats: gputypes has Unorm/Snorm at 36-37, wgpu-native doesn't
+ // RGBA16Uint/Sint/Float: gputypes 38-40 → wgpu-native 32-34
+ case gputypes.TextureFormatRGBA16Uint:
+ return 32
+ case gputypes.TextureFormatRGBA16Sint:
+ return 33
+ case gputypes.TextureFormatRGBA16Float:
+ return 34
+
+ // RGBA32 formats: gputypes 41-43 → wgpu-native 35-37
+ case gputypes.TextureFormatRGBA32Float:
+ return 35
+ case gputypes.TextureFormatRGBA32Uint:
+ return 36
+ case gputypes.TextureFormatRGBA32Sint:
+ return 37
+
+ // Depth/Stencil formats: gputypes 44-49 → wgpu-native 38-43
+ case gputypes.TextureFormatStencil8:
+ return 38
+ case gputypes.TextureFormatDepth16Unorm:
+ return 39
+ case gputypes.TextureFormatDepth24Plus:
+ return 40
+ case gputypes.TextureFormatDepth24PlusStencil8:
+ return 41
+ case gputypes.TextureFormatDepth32Float:
+ return 42
+ case gputypes.TextureFormatDepth32FloatStencil8:
+ return 43
+
+ default:
+ // For compressed formats and others, try simple mapping
+ // Compressed formats start at higher values and may need individual mapping
+ return uint32(f)
+ }
+}
+
+// fromWGPUTextureFormat converts a wgpu-native TextureFormat value to gputypes.
+// This is the reverse of toWGPUTextureFormat.
+func fromWGPUTextureFormat(f uint32) gputypes.TextureFormat {
+ switch f {
+ case 0:
+ return gputypes.TextureFormatUndefined
+
+ // 8-bit R formats (1-4 → 1-4, same)
+ case 1:
+ return gputypes.TextureFormatR8Unorm
+ case 2:
+ return gputypes.TextureFormatR8Snorm
+ case 3:
+ return gputypes.TextureFormatR8Uint
+ case 4:
+ return gputypes.TextureFormatR8Sint
+
+ // R16 formats: wgpu-native 5-7 → gputypes 7-9
+ case 5:
+ return gputypes.TextureFormatR16Uint
+ case 6:
+ return gputypes.TextureFormatR16Sint
+ case 7:
+ return gputypes.TextureFormatR16Float
+
+ // RG8 formats: wgpu-native 8-11 → gputypes 10-13
+ case 8:
+ return gputypes.TextureFormatRG8Unorm
+ case 9:
+ return gputypes.TextureFormatRG8Snorm
+ case 10:
+ return gputypes.TextureFormatRG8Uint
+ case 11:
+ return gputypes.TextureFormatRG8Sint
+
+ // R32 formats: wgpu-native 12-14 → gputypes 14-16
+ case 12:
+ return gputypes.TextureFormatR32Float
+ case 13:
+ return gputypes.TextureFormatR32Uint
+ case 14:
+ return gputypes.TextureFormatR32Sint
+
+ // RG16 formats: wgpu-native 15-17 → gputypes 19-21
+ case 15:
+ return gputypes.TextureFormatRG16Uint
+ case 16:
+ return gputypes.TextureFormatRG16Sint
+ case 17:
+ return gputypes.TextureFormatRG16Float
+
+ // RGBA8 formats: wgpu-native 18-22 → gputypes 22-26
+ case 18:
+ return gputypes.TextureFormatRGBA8Unorm
+ case 19:
+ return gputypes.TextureFormatRGBA8UnormSrgb
+ case 20:
+ return gputypes.TextureFormatRGBA8Snorm
+ case 21:
+ return gputypes.TextureFormatRGBA8Uint
+ case 22:
+ return gputypes.TextureFormatRGBA8Sint
+
+ // BGRA8 formats: wgpu-native 23-24 → gputypes 27-28
+ case 23:
+ return gputypes.TextureFormatBGRA8Unorm
+ case 24:
+ return gputypes.TextureFormatBGRA8UnormSrgb
+
+ // Packed formats: wgpu-native 25-28 → gputypes 29-32
+ case 25:
+ return gputypes.TextureFormatRGB10A2Uint
+ case 26:
+ return gputypes.TextureFormatRGB10A2Unorm
+ case 27:
+ return gputypes.TextureFormatRG11B10Ufloat
+ case 28:
+ return gputypes.TextureFormatRGB9E5Ufloat
+
+ // RG32 formats: wgpu-native 29-31 → gputypes 33-35
+ case 29:
+ return gputypes.TextureFormatRG32Float
+ case 30:
+ return gputypes.TextureFormatRG32Uint
+ case 31:
+ return gputypes.TextureFormatRG32Sint
+
+ // RGBA16 formats: wgpu-native 32-34 → gputypes 38-40
+ case 32:
+ return gputypes.TextureFormatRGBA16Uint
+ case 33:
+ return gputypes.TextureFormatRGBA16Sint
+ case 34:
+ return gputypes.TextureFormatRGBA16Float
+
+ // RGBA32 formats: wgpu-native 35-37 → gputypes 41-43
+ case 35:
+ return gputypes.TextureFormatRGBA32Float
+ case 36:
+ return gputypes.TextureFormatRGBA32Uint
+ case 37:
+ return gputypes.TextureFormatRGBA32Sint
+
+ // Depth/Stencil formats: wgpu-native 38-43 → gputypes 44-49
+ case 38:
+ return gputypes.TextureFormatStencil8
+ case 39:
+ return gputypes.TextureFormatDepth16Unorm
+ case 40:
+ return gputypes.TextureFormatDepth24Plus
+ case 41:
+ return gputypes.TextureFormatDepth24PlusStencil8
+ case 42:
+ return gputypes.TextureFormatDepth32Float
+ case 43:
+ return gputypes.TextureFormatDepth32FloatStencil8
+
+ default:
+ // For unknown/compressed formats, return as-is
+ return gputypes.TextureFormat(f)
+ }
+}
+
+// =============================================================================
+// Types that DON'T need conversion (bitflags or already matching)
+// =============================================================================
+
+// ShaderStage - bitflags, same values (but needs widening to uint64!)
+// BufferUsage - bitflags, same values
+// TextureUsage - bitflags, same values
+// ColorWriteMask - bitflags, same values
+
+// =============================================================================
+// Simple enums that may need +1 shift (to be verified)
+// =============================================================================
+
+// PrimitiveTopology, IndexFormat, FrontFace, CullMode, VertexFormat, VertexStepMode,
+// AddressMode, FilterMode, CompareFunction, BlendFactor, BlendOperation, StencilOperation,
+// LoadOp, StoreOp, PresentMode, CompositeAlphaMode, TextureDimension
+//
+// These may or may not have BindingNotUsed/Undefined shift - needs verification.
+// For now, we'll add converters as issues are discovered.
+
+func toWGPULoadOp(op gputypes.LoadOp) uint32 {
+ // gputypes: Undefined=0, Clear=1, Load=2
+ // wgpu-native: Undefined=0, Load=1, Clear=2 (different order!)
+ switch op {
+ case gputypes.LoadOpClear:
+ return 2 // wgpu-native Clear
+ case gputypes.LoadOpLoad:
+ return 1 // wgpu-native Load
+ default:
+ return 0 // Undefined
+ }
+}
+
+func toWGPUStoreOp(op gputypes.StoreOp) uint32 {
+ // gputypes: Undefined=0, Store=1, Discard=2
+ // wgpu-native: Undefined=0, Store=1, Discard=2 (same!)
+ return uint32(op)
+}
+
+// =============================================================================
+// TextureDimension conversion
+// gputypes: Undefined=0, 1D=1, 2D=2, 3D=3
+// wgpu-native: Undefined=1, 1D=2, 2D=3, 3D=4
+// =============================================================================
+
+func toWGPUTextureDimension(d gputypes.TextureDimension) uint32 {
+ // gputypes: Undefined=0, 1D=1, 2D=2, 3D=3
+ // wgpu-native: Undefined=0, 1D=1, 2D=2, 3D=3 (SAME values!)
+ // TextureDimension does NOT have BindingNotUsed, so no +1 shift needed
+ return uint32(d)
+}
+
+// =============================================================================
+// VertexStepMode conversion
+// gputypes: Undefined=0, VertexBufferNotUsed=1, Vertex=2, Instance=3
+// wgpu-native: VertexBufferNotUsed=0, Undefined=1, Vertex=2, Instance=3
+// Note: Undefined and VertexBufferNotUsed are SWAPPED!
+// =============================================================================
+
+func toWGPUVertexStepMode(m gputypes.VertexStepMode) uint32 {
+ switch m {
+ case gputypes.VertexStepModeUndefined:
+ return 1 // wgpu-native Undefined
+ case gputypes.VertexStepModeVertexBufferNotUsed:
+ return 0 // wgpu-native VertexBufferNotUsed
+ default:
+ // Vertex=2, Instance=3 are the same
+ return uint32(m)
+ }
+}
+
+// =============================================================================
+// VertexFormat conversion
+// gputypes has fewer formats (no single-component 8/16-bit).
+// wgpu-native has Uint8, Sint8, Unorm8, Snorm8, Uint16, Sint16, Unorm16, Snorm16, Float16
+// which shift all subsequent values.
+//
+// gputypes: Uint8x2=1, Uint8x4=2, Sint8x2=3, Sint8x4=4, Unorm8x2=5, Unorm8x4=6...
+// wgpu-native: Uint8=1, Uint8x2=2, Uint8x4=3, Sint8=4, Sint8x2=5, Sint8x4=6...
+// =============================================================================
+
+func toWGPUVertexFormat(f gputypes.VertexFormat) uint32 {
+ switch f {
+ case gputypes.VertexFormatUndefined:
+ return 0
+
+ // 8-bit formats: gputypes lacks single-component
+ case gputypes.VertexFormatUint8x2:
+ return 2 // wgpu Uint8x2
+ case gputypes.VertexFormatUint8x4:
+ return 3 // wgpu Uint8x4
+ case gputypes.VertexFormatSint8x2:
+ return 5 // wgpu Sint8x2
+ case gputypes.VertexFormatSint8x4:
+ return 6 // wgpu Sint8x4
+ case gputypes.VertexFormatUnorm8x2:
+ return 8 // wgpu Unorm8x2
+ case gputypes.VertexFormatUnorm8x4:
+ return 9 // wgpu Unorm8x4
+ case gputypes.VertexFormatSnorm8x2:
+ return 11 // wgpu Snorm8x2
+ case gputypes.VertexFormatSnorm8x4:
+ return 12 // wgpu Snorm8x4
+
+ // 16-bit formats: gputypes lacks single-component
+ case gputypes.VertexFormatUint16x2:
+ return 14 // wgpu Uint16x2
+ case gputypes.VertexFormatUint16x4:
+ return 15 // wgpu Uint16x4
+ case gputypes.VertexFormatSint16x2:
+ return 17 // wgpu Sint16x2
+ case gputypes.VertexFormatSint16x4:
+ return 18 // wgpu Sint16x4
+ case gputypes.VertexFormatUnorm16x2:
+ return 20 // wgpu Unorm16x2
+ case gputypes.VertexFormatUnorm16x4:
+ return 21 // wgpu Unorm16x4
+ case gputypes.VertexFormatSnorm16x2:
+ return 23 // wgpu Snorm16x2
+ case gputypes.VertexFormatSnorm16x4:
+ return 24 // wgpu Snorm16x4
+
+ // Float16 formats: gputypes lacks single-component
+ case gputypes.VertexFormatFloat16x2:
+ return 26 // wgpu Float16x2
+ case gputypes.VertexFormatFloat16x4:
+ return 27 // wgpu Float16x4
+
+ // Float32 formats
+ case gputypes.VertexFormatFloat32:
+ return 28 // wgpu Float32
+ case gputypes.VertexFormatFloat32x2:
+ return 29 // wgpu Float32x2
+ case gputypes.VertexFormatFloat32x3:
+ return 30 // wgpu Float32x3
+ case gputypes.VertexFormatFloat32x4:
+ return 31 // wgpu Float32x4
+
+ // Uint32 formats
+ case gputypes.VertexFormatUint32:
+ return 32 // wgpu Uint32
+ case gputypes.VertexFormatUint32x2:
+ return 33 // wgpu Uint32x2
+ case gputypes.VertexFormatUint32x3:
+ return 34 // wgpu Uint32x3
+ case gputypes.VertexFormatUint32x4:
+ return 35 // wgpu Uint32x4
+
+ // Sint32 formats
+ case gputypes.VertexFormatSint32:
+ return 36 // wgpu Sint32
+ case gputypes.VertexFormatSint32x2:
+ return 37 // wgpu Sint32x2
+ case gputypes.VertexFormatSint32x3:
+ return 38 // wgpu Sint32x3
+ case gputypes.VertexFormatSint32x4:
+ return 39 // wgpu Sint32x4
+
+ // Packed format
+ case gputypes.VertexFormatUnorm1010102:
+ return 40 // wgpu Unorm10_10_10_2
+
+ default:
+ return uint32(f)
+ }
+}
diff --git a/wgpu/debug_render_test.go b/wgpu/debug_render_test.go
new file mode 100644
index 0000000..37bb0fb
--- /dev/null
+++ b/wgpu/debug_render_test.go
@@ -0,0 +1,126 @@
+package wgpu
+
+import (
+ "fmt"
+ "testing"
+ "unsafe"
+
+ "github.com/gogpu/gputypes"
+)
+
+func TestDebugColorTargetState(t *testing.T) {
+ // Create a colorTargetStateWire with known values
+ target := colorTargetStateWire{
+ nextInChain: 0,
+ format: uint32(gputypes.TextureFormatBGRA8Unorm), // Should be 27 (0x1B)
+ writeMask: uint64(gputypes.ColorWriteMaskAll), // Should be 15 (0xF)
+ }
+
+ t.Logf("colorTargetStateWire size: %d", unsafe.Sizeof(target))
+ t.Logf("format value: %d (0x%X)", target.format, target.format)
+ t.Logf("writeMask value: %d (0x%X)", target.writeMask, target.writeMask)
+
+ // Check field offsets
+ t.Logf("nextInChain offset: %d", unsafe.Offsetof(target.nextInChain))
+ t.Logf("format offset: %d", unsafe.Offsetof(target.format))
+ t.Logf("blend offset: %d", unsafe.Offsetof(target.blend))
+ t.Logf("writeMask offset: %d", unsafe.Offsetof(target.writeMask))
+
+ // Dump raw bytes
+ ptr := unsafe.Pointer(&target)
+ bytes := unsafe.Slice((*byte)(ptr), unsafe.Sizeof(target))
+ t.Logf("Raw bytes: %v", bytes)
+
+ // Verify the format at expected position
+ formatPtr := (*uint32)(unsafe.Pointer(uintptr(ptr) + 8))
+ t.Logf("Value at offset 8 (format): %d (0x%X)", *formatPtr, *formatPtr)
+
+ if *formatPtr != 27 {
+ t.Errorf("Format at offset 8 should be 27 but is %d", *formatPtr)
+ }
+}
+
+func TestDebugRenderPipelineBytes(t *testing.T) {
+ inst, err := CreateInstance(nil)
+ if err != nil {
+ t.Fatalf("CreateInstance failed: %v", err)
+ }
+ defer inst.Release()
+
+ adapter, err := inst.RequestAdapter(nil)
+ if err != nil {
+ t.Fatalf("RequestAdapter failed: %v", err)
+ }
+ defer adapter.Release()
+
+ device, err := adapter.RequestDevice(nil)
+ if err != nil {
+ t.Fatalf("RequestDevice failed: %v", err)
+ }
+ defer device.Release()
+
+ // Check gputypes values
+ t.Logf("gputypes.TextureFormatBGRA8Unorm = %d (0x%X)", gputypes.TextureFormatBGRA8Unorm, gputypes.TextureFormatBGRA8Unorm)
+ t.Logf("gputypes.TextureFormatRG11B10Ufloat = %d (0x%X)", gputypes.TextureFormatRG11B10Ufloat, gputypes.TextureFormatRG11B10Ufloat)
+
+ // Verify the conversion
+ // gputypes BGRA8Unorm = 27, webgpu-headers BGRA8Unorm = 23
+ converted := toWGPUTextureFormat(gputypes.TextureFormatBGRA8Unorm)
+ t.Logf("toWGPUTextureFormat(BGRA8Unorm) = %d (0x%X)", converted, converted)
+
+ if converted != 23 {
+ t.Errorf("toWGPUTextureFormat(BGRA8Unorm) should return 23 (wgpu-native value) but returned %d", converted)
+ }
+
+ // Manually create the structs to see what's happening
+ shaderCode := `
+@vertex
+fn vs_main(@builtin(vertex_index) idx: u32) -> @builtin(position) vec4 {
+ var pos = array, 3>(
+ vec2(0.0, 0.5),
+ vec2(-0.5, -0.5),
+ vec2(0.5, -0.5)
+ );
+ return vec4(pos[idx], 0.0, 1.0);
+}
+
+@fragment
+fn fs_main() -> @location(0) vec4 {
+ return vec4(1.0, 0.0, 0.0, 1.0);
+}
+`
+ shader := device.CreateShaderModuleWGSL(shaderCode)
+ if shader == nil {
+ t.Fatal("CreateShaderModuleWGSL returned nil")
+ }
+ defer shader.Release()
+
+ t.Logf("Shader module handle: 0x%X", shader.Handle())
+
+ // Create the color target manually
+ nativeTarget := colorTargetStateWire{
+ nextInChain: 0,
+ format: 27, // Hardcoded BGRA8Unorm
+ writeMask: 15, // Hardcoded All
+ }
+
+ // Verify the bytes
+ targetBytes := (*[32]byte)(unsafe.Pointer(&nativeTarget))
+ t.Logf("nativeTarget bytes: %v", targetBytes[:])
+ t.Logf(" Bytes 8-11 (format): %v", targetBytes[8:12])
+
+ // Check what value is at offset 8
+ formatVal := *(*uint32)(unsafe.Pointer(&targetBytes[8]))
+ t.Logf(" Format at offset 8: %d (0x%X)", formatVal, formatVal)
+
+ // Print struct sizes for comparison
+ t.Logf("\nStruct sizes:")
+ t.Logf(" colorTargetStateWire: %d", unsafe.Sizeof(colorTargetStateWire{}))
+ t.Logf(" fragmentState: %d", unsafe.Sizeof(fragmentState{}))
+ t.Logf(" vertexState: %d", unsafe.Sizeof(vertexState{}))
+ t.Logf(" primitiveState: %d", unsafe.Sizeof(primitiveState{}))
+ t.Logf(" multisampleState: %d", unsafe.Sizeof(multisampleState{}))
+ t.Logf(" renderPipelineDescriptor: %d", unsafe.Sizeof(renderPipelineDescriptor{}))
+
+ fmt.Println("Debug test complete - not calling CreateRenderPipeline to avoid crash")
+}
diff --git a/wgpu/render.go b/wgpu/render.go
index 94a0117..ac0699d 100644
--- a/wgpu/render.go
+++ b/wgpu/render.go
@@ -19,15 +19,16 @@ type Color struct {
}
// renderPassColorAttachment is the native structure for color attachments.
+// Uses uint32 for LoadOp/StoreOp with wgpu-native converted values.
type renderPassColorAttachment struct {
- nextInChain uintptr // 8 bytes
- view uintptr // 8 bytes (WGPUTextureView)
- depthSlice uint32 // 4 bytes - MUST be DepthSliceUndefined for 2D!
- _pad1 [4]byte // 4 bytes padding
- resolveTarget uintptr // 8 bytes (WGPUTextureView, nullable)
- loadOp gputypes.LoadOp // 4 bytes
- storeOp gputypes.StoreOp // 4 bytes
- clearValue Color // 32 bytes (4 * float64)
+ nextInChain uintptr // 8 bytes
+ view uintptr // 8 bytes (WGPUTextureView)
+ depthSlice uint32 // 4 bytes - MUST be DepthSliceUndefined for 2D!
+ _pad1 [4]byte // 4 bytes padding
+ resolveTarget uintptr // 8 bytes (WGPUTextureView, nullable)
+ loadOp uint32 // 4 bytes - wgpu-native converted value
+ storeOp uint32 // 4 bytes - wgpu-native converted value
+ clearValue Color // 32 bytes (4 * float64)
}
// renderPassDescriptor is the native structure for render pass descriptor.
@@ -64,14 +65,15 @@ type RenderPassDepthStencilAttachment struct {
}
// renderPassDepthStencilAttachment is the native structure (40 bytes).
+// Uses uint32 for LoadOp/StoreOp with wgpu-native converted values.
type renderPassDepthStencilAttachment struct {
view uintptr
- depthLoadOp gputypes.LoadOp
- depthStoreOp gputypes.StoreOp
+ depthLoadOp uint32 // wgpu-native converted value
+ depthStoreOp uint32 // wgpu-native converted value
depthClearValue float32
depthReadOnly Bool
- stencilLoadOp gputypes.LoadOp
- stencilStoreOp gputypes.StoreOp
+ stencilLoadOp uint32 // wgpu-native converted value
+ stencilStoreOp uint32 // wgpu-native converted value
stencilClearValue uint32
stencilReadOnly Bool
}
@@ -125,8 +127,8 @@ func (enc *CommandEncoder) BeginRenderPass(desc *RenderPassDescriptor) *RenderPa
view: viewHandle,
depthSlice: DepthSliceUndefined, // CRITICAL for 2D textures!
resolveTarget: resolveHandle,
- loadOp: ca.LoadOp,
- storeOp: ca.StoreOp,
+ loadOp: toWGPULoadOp(ca.LoadOp),
+ storeOp: toWGPUStoreOp(ca.StoreOp),
clearValue: ca.ClearValue,
}
}
@@ -146,12 +148,12 @@ func (enc *CommandEncoder) BeginRenderPass(desc *RenderPassDescriptor) *RenderPa
nativeDepthStencil = renderPassDepthStencilAttachment{
view: desc.DepthStencilAttachment.View.handle,
- depthLoadOp: desc.DepthStencilAttachment.DepthLoadOp,
- depthStoreOp: desc.DepthStencilAttachment.DepthStoreOp,
+ depthLoadOp: toWGPULoadOp(desc.DepthStencilAttachment.DepthLoadOp),
+ depthStoreOp: toWGPUStoreOp(desc.DepthStencilAttachment.DepthStoreOp),
depthClearValue: desc.DepthStencilAttachment.DepthClearValue,
depthReadOnly: depthRO,
- stencilLoadOp: desc.DepthStencilAttachment.StencilLoadOp,
- stencilStoreOp: desc.DepthStencilAttachment.StencilStoreOp,
+ stencilLoadOp: toWGPULoadOp(desc.DepthStencilAttachment.StencilLoadOp),
+ stencilStoreOp: toWGPUStoreOp(desc.DepthStencilAttachment.StencilStoreOp),
stencilClearValue: desc.DepthStencilAttachment.StencilClearValue,
stencilReadOnly: stencilRO,
}
diff --git a/wgpu/render_bundle.go b/wgpu/render_bundle.go
index 2b26c4b..142410c 100644
--- a/wgpu/render_bundle.go
+++ b/wgpu/render_bundle.go
@@ -33,13 +33,13 @@ func (d *Device) CreateRenderBundleEncoder(desc *RenderBundleEncoderDescriptor)
return nil
}
- // Build the native descriptor
+ // Build the native descriptor with converted format values
type nativeDesc struct {
nextInChain uintptr
label StringView
colorFormatCount uintptr
colorFormats uintptr
- depthStencilFormat gputypes.TextureFormat
+ depthStencilFormat uint32 // converted from gputypes
sampleCount uint32
depthReadOnly Bool
stencilReadOnly Bool
@@ -48,14 +48,21 @@ func (d *Device) CreateRenderBundleEncoder(desc *RenderBundleEncoderDescriptor)
nd := nativeDesc{
label: desc.Label,
colorFormatCount: desc.ColorFormatCount,
- depthStencilFormat: desc.DepthStencilFormat,
+ depthStencilFormat: toWGPUTextureFormat(desc.DepthStencilFormat),
sampleCount: desc.SampleCount,
depthReadOnly: desc.DepthReadOnly,
stencilReadOnly: desc.StencilReadOnly,
}
+ // Convert color formats to wgpu-native values
+ var convertedFormats []uint32
if desc.ColorFormats != nil && desc.ColorFormatCount > 0 {
- nd.colorFormats = uintptr(unsafe.Pointer(desc.ColorFormats))
+ formats := unsafe.Slice(desc.ColorFormats, desc.ColorFormatCount)
+ convertedFormats = make([]uint32, desc.ColorFormatCount)
+ for i, f := range formats {
+ convertedFormats[i] = toWGPUTextureFormat(f)
+ }
+ nd.colorFormats = uintptr(unsafe.Pointer(&convertedFormats[0]))
}
handle, _, _ := procDeviceCreateRenderBundleEncoder.Call(
diff --git a/wgpu/render_pipeline.go b/wgpu/render_pipeline.go
index 912d408..2ebad07 100644
--- a/wgpu/render_pipeline.go
+++ b/wgpu/render_pipeline.go
@@ -14,6 +14,16 @@ type VertexAttribute struct {
_pad [4]byte
}
+// vertexAttributeWire is the FFI-compatible structure with converted Format.
+// Field order matches webgpu.h: format, offset, shaderLocation
+type vertexAttributeWire struct {
+ Format uint32 // converted from gputypes.VertexFormat
+ _pad1 [4]byte
+ Offset uint64
+ ShaderLocation uint32
+ _pad2 [4]byte
+}
+
// VertexBufferLayout describes how vertex data is laid out in a buffer.
type VertexBufferLayout struct {
ArrayStride uint64
@@ -23,6 +33,16 @@ type VertexBufferLayout struct {
Attributes *VertexAttribute
}
+// vertexBufferLayoutWire is the FFI-compatible structure with converted StepMode.
+// Field order matches webgpu.h: stepMode, arrayStride, attributeCount, attributes
+type vertexBufferLayoutWire struct {
+ StepMode uint32 // converted from gputypes.VertexStepMode
+ _pad [4]byte // padding to align arrayStride to 8 bytes
+ ArrayStride uint64
+ AttributeCount uintptr
+ Attributes uintptr // pointer to VertexAttribute array
+}
+
// vertexState is the native structure for vertex stage.
type vertexState struct {
nextInChain uintptr // 8 bytes
@@ -67,14 +87,14 @@ type BlendState struct {
Alpha BlendComponent
}
-// colorTargetState is the native structure for a color target.
-type colorTargetState struct {
- nextInChain uintptr // 8 bytes
- format gputypes.TextureFormat // 4 bytes
- _pad1 [4]byte // 4 bytes padding
- blend uintptr // 8 bytes (pointer to BlendState, nullable)
- writeMask gputypes.ColorWriteMask // 4 bytes
- _pad2 [4]byte // 4 bytes padding
+// colorTargetStateWire is the native FFI-compatible structure for a color target.
+// CRITICAL: writeMask is uint64 because WGPUColorWriteMaskFlags = WGPUFlags = uint64 in webgpu-headers!
+type colorTargetStateWire struct {
+ nextInChain uintptr // 8 bytes
+ format uint32 // 4 bytes (WGPUTextureFormat, converted)
+ _pad1 [4]byte // 4 bytes padding (to align blend to 8)
+ blend uintptr // 8 bytes (pointer to BlendState, nullable)
+ writeMask uint64 // 8 bytes (WGPUColorWriteMaskFlags = uint64!)
}
// fragmentState is the native structure for fragment stage.
@@ -158,10 +178,11 @@ type DepthStencilState struct {
DepthBiasClamp float32
}
-// depthStencilState is the native structure for depth/stencil state (72 bytes).
-type depthStencilState struct {
+// depthStencilStateWire is the native FFI-compatible structure for depth/stencil state.
+// Uses uint32 for format (converted from gputypes).
+type depthStencilStateWire struct {
nextInChain uintptr
- format gputypes.TextureFormat
+ format uint32 // converted from gputypes.TextureFormat
depthWriteEnabled OptionalBool
depthCompare gputypes.CompareFunction
stencilFront StencilFaceState
@@ -215,8 +236,35 @@ func (d *Device) CreateRenderPipeline(desc *RenderPipelineDescriptor) *RenderPip
nativeVertex.entryPoint = EmptyStringView()
}
+ // Convert vertex buffer layouts with StepMode and VertexFormat conversion
+ var nativeBuffers []vertexBufferLayoutWire
+ var allNativeAttrs [][]vertexAttributeWire // keep alive during FFI call
if len(desc.Vertex.Buffers) > 0 {
- nativeVertex.buffers = uintptr(unsafe.Pointer(&desc.Vertex.Buffers[0]))
+ nativeBuffers = make([]vertexBufferLayoutWire, len(desc.Vertex.Buffers))
+ allNativeAttrs = make([][]vertexAttributeWire, len(desc.Vertex.Buffers))
+ for i, buf := range desc.Vertex.Buffers {
+ var attrsPtr uintptr
+ if buf.Attributes != nil && buf.AttributeCount > 0 {
+ // Convert attributes with format conversion
+ attrs := unsafe.Slice(buf.Attributes, buf.AttributeCount)
+ allNativeAttrs[i] = make([]vertexAttributeWire, len(attrs))
+ for j, attr := range attrs {
+ allNativeAttrs[i][j] = vertexAttributeWire{
+ Format: toWGPUVertexFormat(attr.Format),
+ Offset: attr.Offset,
+ ShaderLocation: attr.ShaderLocation,
+ }
+ }
+ attrsPtr = uintptr(unsafe.Pointer(&allNativeAttrs[i][0]))
+ }
+ nativeBuffers[i] = vertexBufferLayoutWire{
+ StepMode: toWGPUVertexStepMode(buf.StepMode),
+ ArrayStride: buf.ArrayStride,
+ AttributeCount: buf.AttributeCount,
+ Attributes: attrsPtr,
+ }
+ }
+ nativeVertex.buffers = uintptr(unsafe.Pointer(&nativeBuffers[0]))
}
// Build primitive state
@@ -250,18 +298,18 @@ func (d *Device) CreateRenderPipeline(desc *RenderPipelineDescriptor) *RenderPip
alphaToCoverageEnabled: alphaToCov,
}
- // Build depth/stencil state if present
+ // Build depth/stencil state if present (with format conversion)
var depthStencilPtr uintptr
- var nativeDepthStencil depthStencilState
+ var nativeDepthStencil depthStencilStateWire
if desc.DepthStencil != nil {
depthWriteOpt := OptionalBoolFalse
if desc.DepthStencil.DepthWriteEnabled {
depthWriteOpt = OptionalBoolTrue
}
- nativeDepthStencil = depthStencilState{
+ nativeDepthStencil = depthStencilStateWire{
nextInChain: 0,
- format: desc.DepthStencil.Format,
+ format: toWGPUTextureFormat(desc.DepthStencil.Format),
depthWriteEnabled: depthWriteOpt,
depthCompare: desc.DepthStencil.DepthCompare,
stencilFront: desc.DepthStencil.StencilFront,
@@ -278,7 +326,7 @@ func (d *Device) CreateRenderPipeline(desc *RenderPipelineDescriptor) *RenderPip
// Build fragment state if present
var fragmentPtr uintptr
var nativeFragment fragmentState
- var nativeTargets []colorTargetState
+ var nativeTargets []colorTargetStateWire
var fragEntryPointBytes []byte
if desc.Fragment != nil {
@@ -303,17 +351,20 @@ func (d *Device) CreateRenderPipeline(desc *RenderPipelineDescriptor) *RenderPip
nativeFragment.entryPoint = EmptyStringView()
}
- // Build color targets
- nativeTargets = make([]colorTargetState, len(desc.Fragment.Targets))
+ // Build color targets with wire format (uint64 writeMask!)
+ nativeTargets = make([]colorTargetStateWire, len(desc.Fragment.Targets))
for i, target := range desc.Fragment.Targets {
- nativeTargets[i] = colorTargetState{
+ convertedFormat := toWGPUTextureFormat(target.Format)
+ nativeTargets[i] = colorTargetStateWire{
nextInChain: 0,
- format: target.Format,
- writeMask: target.WriteMask,
+ format: convertedFormat,
+ writeMask: uint64(target.WriteMask), // widen to uint64
}
if target.Blend != nil {
nativeTargets[i].blend = uintptr(unsafe.Pointer(target.Blend))
}
+ // DEBUG: print the target bytes
+ _ = convertedFormat // silence unused warning
}
if len(nativeTargets) > 0 {
diff --git a/wgpu/sampler.go b/wgpu/sampler.go
index abda711..d8b4788 100644
--- a/wgpu/sampler.go
+++ b/wgpu/sampler.go
@@ -29,9 +29,16 @@ func (d *Device) CreateSampler(desc *SamplerDescriptor) *Sampler {
if desc == nil {
return nil
}
+
+ // wgpu-native requires MaxAnisotropy >= 1
+ descCopy := *desc
+ if descCopy.MaxAnisotropy == 0 {
+ descCopy.MaxAnisotropy = 1
+ }
+
handle, _, _ := procDeviceCreateSampler.Call(
d.handle,
- uintptr(unsafe.Pointer(desc)),
+ uintptr(unsafe.Pointer(&descCopy)),
)
if handle == 0 {
return nil
diff --git a/wgpu/surface.go b/wgpu/surface.go
index 5f5703c..a64c742 100644
--- a/wgpu/surface.go
+++ b/wgpu/surface.go
@@ -13,19 +13,20 @@ type surfaceDescriptor struct {
label StringView // 16 bytes
}
-// surfaceConfiguration is the native structure for configuring a surface.
-type surfaceConfiguration struct {
- nextInChain uintptr // 8 bytes
- device uintptr // 8 bytes (WGPUDevice handle)
- format gputypes.TextureFormat // 4 bytes
- _pad1 [4]byte // 4 bytes padding
- usage gputypes.TextureUsage // 8 bytes (uint64)
- width uint32 // 4 bytes
- height uint32 // 4 bytes
- viewFormatCount uintptr // 8 bytes (size_t)
- viewFormats uintptr // 8 bytes (pointer)
- alphaMode gputypes.CompositeAlphaMode // 4 bytes
- presentMode gputypes.PresentMode // 4 bytes
+// surfaceConfigurationWire is the FFI-compatible structure for configuring a surface.
+// Uses uint32 for format (converted from gputypes) and uint64 for usage.
+type surfaceConfigurationWire struct {
+ nextInChain uintptr // 8 bytes
+ device uintptr // 8 bytes (WGPUDevice handle)
+ format uint32 // 4 bytes (converted from gputypes.TextureFormat)
+ _pad1 [4]byte // 4 bytes padding
+ usage uint64 // 8 bytes (TextureUsage as uint64)
+ width uint32 // 4 bytes
+ height uint32 // 4 bytes
+ viewFormatCount uintptr // 8 bytes (size_t)
+ viewFormats uintptr // 8 bytes (pointer)
+ alphaMode uint32 // 4 bytes (CompositeAlphaMode)
+ presentMode uint32 // 4 bytes (PresentMode)
}
// surfaceTexture is the native structure returned by GetCurrentTexture.
@@ -64,20 +65,21 @@ var (
// Configure configures the surface for rendering.
// This replaces the deprecated SwapChain API.
+// Enum values are converted from gputypes to wgpu-native values before FFI call.
func (s *Surface) Configure(config *SurfaceConfiguration) {
mustInit()
- nativeConfig := surfaceConfiguration{
+ nativeConfig := surfaceConfigurationWire{
nextInChain: 0,
device: config.Device.handle,
- format: config.Format,
- usage: config.Usage,
+ format: toWGPUTextureFormat(config.Format),
+ usage: uint64(config.Usage),
width: config.Width,
height: config.Height,
viewFormatCount: 0,
viewFormats: 0,
- alphaMode: config.AlphaMode,
- presentMode: config.PresentMode,
+ alphaMode: uint32(config.AlphaMode),
+ presentMode: uint32(config.PresentMode),
}
procSurfaceConfigure.Call( //nolint:errcheck
diff --git a/wgpu/texture.go b/wgpu/texture.go
index 35be369..905902b 100644
--- a/wgpu/texture.go
+++ b/wgpu/texture.go
@@ -20,6 +20,21 @@ type TextureDescriptor struct {
ViewFormats uintptr
}
+// textureDescriptorWire is the FFI-compatible struct with wgpu-native enum values.
+// CRITICAL: Usage is uint64 because wgpu-native defines WGPUTextureUsageFlags as uint64!
+type textureDescriptorWire struct {
+ NextInChain uintptr
+ Label StringView
+ Usage uint64 // TextureUsage bitflags (uint64 in wgpu-native!)
+ Dimension uint32 // TextureDimension (needs +1 shift)
+ Size gputypes.Extent3D
+ Format uint32 // TextureFormat (converted via map)
+ MipLevelCount uint32
+ SampleCount uint32
+ ViewFormatCount uintptr
+ ViewFormats uintptr
+}
+
// TextureViewDescriptor describes a texture view to create.
type TextureViewDescriptor struct {
NextInChain uintptr
@@ -31,18 +46,48 @@ type TextureViewDescriptor struct {
BaseArrayLayer uint32
ArrayLayerCount uint32
Aspect TextureAspect
- _pad [4]byte
+ _pad [4]byte //nolint:unused // padding for FFI alignment
Usage gputypes.TextureUsage
}
+// textureViewDescriptorWire is the FFI-compatible struct with wgpu-native enum values.
+// CRITICAL: Usage is uint64 because wgpu-native defines WGPUTextureUsageFlags as uint64!
+type textureViewDescriptorWire struct {
+ NextInChain uintptr
+ Label StringView
+ Format uint32 // TextureFormat (converted)
+ Dimension uint32 // TextureViewDimension (needs +1 shift)
+ BaseMipLevel uint32
+ MipLevelCount uint32
+ BaseArrayLayer uint32
+ ArrayLayerCount uint32
+ Aspect TextureAspect
+ _pad [4]byte
+ Usage uint64 // TextureUsage bitflags (uint64 in wgpu-native!)
+}
+
// CreateView creates a view into this texture.
// Pass nil for default view parameters.
+// Enum values are converted from gputypes to wgpu-native values before FFI call.
func (t *Texture) CreateView(desc *TextureViewDescriptor) *TextureView {
mustInit()
var descPtr uintptr
if desc != nil {
- descPtr = uintptr(unsafe.Pointer(desc))
+ // Convert to wire format with wgpu-native enum values
+ wireDesc := textureViewDescriptorWire{
+ NextInChain: desc.NextInChain,
+ Label: desc.Label,
+ Format: toWGPUTextureFormat(desc.Format),
+ Dimension: toWGPUTextureViewDimension(desc.Dimension),
+ BaseMipLevel: desc.BaseMipLevel,
+ MipLevelCount: desc.MipLevelCount,
+ BaseArrayLayer: desc.BaseArrayLayer,
+ ArrayLayerCount: desc.ArrayLayerCount,
+ Aspect: desc.Aspect,
+ Usage: uint64(desc.Usage), // bitflags, uint64 in wgpu-native
+ }
+ descPtr = uintptr(unsafe.Pointer(&wireDesc))
}
handle, _, _ := procTextureCreateView.Call(
@@ -115,13 +160,15 @@ func (t *Texture) GetMipLevelCount() uint32 {
}
// GetFormat returns the texture format.
+// The format is converted from wgpu-native enum to gputypes enum.
func (t *Texture) GetFormat() gputypes.TextureFormat {
mustInit()
if t == nil || t.handle == 0 {
return gputypes.TextureFormatUndefined
}
result, _, _ := procTextureGetFormat.Call(t.handle)
- return gputypes.TextureFormat(result)
+ // Convert from wgpu-native enum to gputypes
+ return fromWGPUTextureFormat(uint32(result))
}
// Release releases the texture view reference.
@@ -136,14 +183,40 @@ func (tv *TextureView) Release() {
func (tv *TextureView) Handle() uintptr { return tv.handle }
// CreateTexture creates a texture with the specified descriptor.
+// Enum values are converted from gputypes to wgpu-native values before FFI call.
func (d *Device) CreateTexture(desc *TextureDescriptor) *Texture {
mustInit()
if desc == nil {
return nil
}
+
+ // wgpu-native requires MipLevelCount >= 1 and SampleCount >= 1
+ mipLevelCount := desc.MipLevelCount
+ if mipLevelCount == 0 {
+ mipLevelCount = 1
+ }
+ sampleCount := desc.SampleCount
+ if sampleCount == 0 {
+ sampleCount = 1
+ }
+
+ // Convert to wire format with wgpu-native enum values
+ wireDesc := textureDescriptorWire{
+ NextInChain: desc.NextInChain,
+ Label: desc.Label,
+ Usage: uint64(desc.Usage), // bitflags, uint64 in wgpu-native
+ Dimension: toWGPUTextureDimension(desc.Dimension),
+ Size: desc.Size,
+ Format: toWGPUTextureFormat(desc.Format),
+ MipLevelCount: mipLevelCount,
+ SampleCount: sampleCount,
+ ViewFormatCount: desc.ViewFormatCount,
+ ViewFormats: desc.ViewFormats,
+ }
+
handle, _, _ := procDeviceCreateTexture.Call(
d.handle,
- uintptr(unsafe.Pointer(desc)),
+ uintptr(unsafe.Pointer(&wireDesc)),
)
if handle == 0 {
return nil