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 +[![GitHub Project](https://img.shields.io/badge/GitHub-Project%20Board-blue?style=flat-square&logo=github)](https://github.com/go-webgpu/webgpu/projects) +[![GitHub Issues](https://img.shields.io/github/issues/go-webgpu/webgpu?style=flat-square&logo=github)](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** | ![GitHub Release](https://img.shields.io/github/v/release/go-webgpu/webgpu?style=flat-square) | +| **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