From 572bbee046f4ff12a2b51167d9204d14fff116b0 Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 29 Jan 2026 17:01:44 +0300 Subject: [PATCH 1/3] fix: complete gputypes integration with wgpu-native v27 compatibility Conversion layer fixes: - TextureViewDimension values match between gputypes and wgpu-native v27 - Add struct padding for FFI alignment (sampler/texture/storage binding layouts) - Fix VertexFormat, VertexStepMode, TextureSampleType conversions Example fixes: - Use CreatePipelineLayoutSimple in cube, rotating-triangle, mrt, textured-quad - Fix GetModuleHandleW: use kernel32.dll instead of user32.dll (8 examples) - render_bundle: fallback shader without primitive_index (works on all GPUs) Default value fixes: - sampler: MaxAnisotropy >= 1 - texture: SampleCount >= 1, MipLevelCount >= 1 All 11 examples now work on all supported hardware. --- examples/colored-triangle/main.go | 3 +- examples/cube/main.go | 9 +- examples/indirect/main.go | 3 +- examples/mrt/main.go | 9 +- examples/render_bundle/main.go | 42 ++- examples/rotating-triangle/main.go | 9 +- examples/textured-quad/main.go | 9 +- examples/triangle/main.go | 3 +- wgpu/bindgroup.go | 134 +++++++- wgpu/convert.go | 524 +++++++++++++++++++++++++++++ wgpu/debug_render_test.go | 126 +++++++ wgpu/render.go | 38 ++- wgpu/render_bundle.go | 15 +- wgpu/render_pipeline.go | 95 ++++-- wgpu/sampler.go | 9 +- wgpu/surface.go | 38 ++- wgpu/texture.go | 81 ++++- 17 files changed, 1040 insertions(+), 107 deletions(-) create mode 100644 wgpu/convert.go create mode 100644 wgpu/debug_render_test.go 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 From 5230eee16234e14c8b9deab216b30ea1647c3c9e Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 29 Jan 2026 17:11:06 +0300 Subject: [PATCH 2/3] docs: update documentation for v0.2.0 gputypes integration CHANGELOG.md: - Document conversion layer and all fixes README.md: - Update wgpu-native version to v27.0.4.0 ROADMAP.md (modernized following GitHub best practices): - Add disclaimer about plans vs commitments - Use phase labels instead of hardcoded dates - Add How to Contribute section - Link to GitHub Issues/Projects for live tracking - Add upstream dependencies tracking examples/README.md: - Update version to 0.2.0 - Add gputypes note --- CHANGELOG.md | 10 ++ README.md | 2 +- ROADMAP.md | 298 ++++++++++++++++++++++----------------------- examples/README.md | 6 +- 4 files changed, 161 insertions(+), 155 deletions(-) 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. From 6863de4cf95618a2499a085f28e4dfcafee415a3 Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 29 Jan 2026 17:17:21 +0300 Subject: [PATCH 3/3] chore: exclude gocyclo/cyclop/funlen for conversion files Conversion functions (toWGPUTextureFormat, fromWGPUTextureFormat, toWGPUVertexFormat) have large switch statements for enum mapping which are inherently high complexity but straightforward. CreateRenderPipeline has high complexity due to wire struct initialization but is a single-purpose function. Exclude convert.go and render_pipeline.go from gocyclo/cyclop/funlen checks since these patterns are acceptable for FFI conversion code. --- .golangci.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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: