-
-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathbind_native.go
More file actions
286 lines (256 loc) · 8.81 KB
/
Copy pathbind_native.go
File metadata and controls
286 lines (256 loc) · 8.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
//go:build !rust && !(js && wasm)
package wgpu
import (
"log/slog"
"runtime"
"sync/atomic"
"github.com/gogpu/gputypes"
"github.com/gogpu/wgpu/core"
"github.com/gogpu/wgpu/hal"
)
// bindGroupCleanupRef holds the data needed to destroy a bind group's HAL
// resources from a runtime.AddCleanup callback. This struct must NOT reference
// the BindGroup itself — runtime.AddCleanup requires the callback argument to
// be independent of the cleaned-up object.
type bindGroupCleanupRef struct {
label string
released *atomic.Bool
destroyQueue *core.DestroyQueue
lastSubIdx func() uint64
destroyFn func()
}
// BindGroupLayout defines the structure of resource bindings for shaders.
type BindGroupLayout struct {
hal hal.BindGroupLayout
device *Device
released bool
// entries stores the layout entries for entry-by-entry compatibility checks.
// This matches Rust wgpu-core's pattern where binder.check_compatibility()
// compares layouts by their entries, not by pointer identity.
entries []gputypes.BindGroupLayoutEntry
}
// isCompatibleWith returns true if two layouts have identical entries.
// This matches Rust wgpu-core's entry-by-entry compatibility check in
// binder.check_compatibility(), allowing equivalent layouts created via
// separate CreateBindGroupLayout calls to be considered compatible.
func (l *BindGroupLayout) isCompatibleWith(other *BindGroupLayout) bool {
if l == other {
return true // pointer equality fast path
}
if len(l.entries) != len(other.entries) {
return false
}
for i := range l.entries {
if !bindGroupLayoutEntriesEqual(&l.entries[i], &other.entries[i]) {
return false
}
}
return true
}
// bindGroupLayoutEntriesEqual compares two BindGroupLayoutEntry values,
// dereferencing pointer fields (Buffer, Sampler, Texture, StorageTexture)
// to compare by value rather than by pointer identity.
func bindGroupLayoutEntriesEqual(a, b *gputypes.BindGroupLayoutEntry) bool {
if a.Binding != b.Binding || a.Visibility != b.Visibility {
return false
}
// Compare Buffer pointer fields by value.
if !optionalEqual(a.Buffer, b.Buffer) {
return false
}
// Compare Sampler pointer fields by value.
if !optionalEqual(a.Sampler, b.Sampler) {
return false
}
// Compare Texture pointer fields by value.
if !optionalEqual(a.Texture, b.Texture) {
return false
}
// Compare StorageTexture pointer fields by value.
if !optionalEqual(a.StorageTexture, b.StorageTexture) {
return false
}
return true
}
// optionalEqual compares two optional (pointer) values by dereferenced content.
// Both nil → equal; one nil → not equal; both non-nil → compare dereferenced values.
func optionalEqual[T comparable](a, b *T) bool {
if a == nil && b == nil {
return true
}
if a == nil || b == nil {
return false
}
return *a == *b
}
// Release destroys the bind group layout. Destruction is deferred until the
// GPU completes any submission that may reference this layout.
func (l *BindGroupLayout) Release() {
if l.released {
return
}
l.released = true
halDevice := l.device.halDevice()
if halDevice == nil {
return
}
dq := l.device.destroyQueue()
if dq == nil {
halDevice.DestroyBindGroupLayout(l.hal)
return
}
subIdx := l.device.lastSubmissionIndex()
halLayout := l.hal
dq.Defer(subIdx, "BindGroupLayout", func() {
halDevice.DestroyBindGroupLayout(halLayout)
})
}
// PipelineLayout defines the bind group layout arrangement for a pipeline.
type PipelineLayout struct {
hal hal.PipelineLayout
device *Device
released bool
// bindGroupCount is the number of bind group layouts in this layout.
// Used for validation in SetBindGroup.
bindGroupCount uint32
// bindGroupLayouts stores the layouts used to create this pipeline layout.
// Used by the binder for draw-time compatibility validation.
bindGroupLayouts []*BindGroupLayout
}
// Release destroys the pipeline layout. Destruction is deferred until the
// GPU completes any submission that may reference this layout.
func (l *PipelineLayout) Release() {
if l.released {
return
}
l.released = true
halDevice := l.device.halDevice()
if halDevice == nil {
return
}
dq := l.device.destroyQueue()
if dq == nil {
halDevice.DestroyPipelineLayout(l.hal)
return
}
subIdx := l.device.lastSubmissionIndex()
halLayout := l.hal
dq.Defer(subIdx, "PipelineLayout", func() {
halDevice.DestroyPipelineLayout(halLayout)
})
}
// LateBufferBindingInfo records the actual buffer binding size for a layout entry
// with MinBindingSize == 0. At draw/dispatch time, these sizes are compared against
// the shader-required minimums stored on the pipeline.
//
// Matches Rust wgpu-core's BindGroupLateBufferBindingInfo (binding_model.rs:1167-1173).
type LateBufferBindingInfo struct {
// BindingIndex is the binding number from the layout entry.
BindingIndex uint32
// Size is the actual buffer binding size at bind group creation time.
Size uint64
}
// BindGroup represents bound GPU resources for shader access.
type BindGroup struct {
hal hal.BindGroup
device *Device
cleanup runtime.Cleanup
// released is heap-allocated separately so that the cleanup ref can share
// it without holding an interior pointer into the BindGroup struct. An interior
// pointer would make the BindGroup reachable from the cleanup arg, preventing
// GC collection and causing the cleanup to never fire.
released *atomic.Bool
// layout is the bind group layout used to create this bind group.
// Stored for draw-time compatibility validation via the binder.
layout *BindGroupLayout
// lateBufferBindingInfos records actual buffer sizes for layout entries
// with MinBindingSize == 0. Listed in iteration order of the layout entries,
// matching Rust wgpu-core's BindGroup.late_buffer_binding_infos.
lateBufferBindingInfos []LateBufferBindingInfo
// ref is the GPU-aware reference counter for this bind group (Phase 2).
// Clone'd when used in a render/compute pass, Drop'd when GPU completes submission.
ref *core.ResourceRef
// boundBuffers holds references to buffers bound in this bind group (VAL-A6).
// Used at Submit time to verify that referenced buffers are still alive
// and not mapped. Matches Rust wgpu-core's pattern where
// validate_command_buffer iterates cmd_buf_data.trackers.buffers.
boundBuffers []*Buffer
// boundTextures holds references to textures bound in this bind group (VAL-A6).
// Used at Submit time to verify that referenced textures are still alive.
boundTextures []*Texture
}
// Release marks the bind group for destruction. The underlying HAL BindGroup
// (and its descriptor heap slots) is not freed immediately — it is deferred via
// DestroyQueue until the GPU completes any submission that may reference it.
// This prevents descriptor use-after-free on DX12 with maxFramesInFlight=2
// (BUG-DX12-007).
//
// Matches Rust wgpu pattern: BindGroup::drop() only fires after
// triage_submissions confirms fence completion.
func (g *BindGroup) Release() {
if g.released == nil || !g.released.CompareAndSwap(false, true) {
return
}
// Cancel the GC cleanup — we are destroying explicitly.
g.cleanup.Stop()
if g.device == nil {
return
}
halDevice := g.device.halDevice()
if halDevice == nil {
return
}
dq := g.device.destroyQueue()
if dq == nil {
halDevice.DestroyBindGroup(g.hal)
return
}
subIdx := g.device.lastSubmissionIndex()
halBG := g.hal
dq.Defer(subIdx, "BindGroup", func() {
halDevice.DestroyBindGroup(halBG)
})
}
// registerBindGroupCleanup registers a runtime.AddCleanup handler on the bind group.
// When GC collects the bind group without an explicit Release(), the cleanup
// schedules deferred destruction via DestroyQueue — the same path as Release().
func registerBindGroupCleanup(bg *BindGroup, dev *Device, label string) runtime.Cleanup {
halDevice := dev.halDevice()
if halDevice == nil {
// No HAL device — nothing to destroy.
return runtime.Cleanup{}
}
halBG := bg.hal
destroyFn := func() {
halDevice.DestroyBindGroup(halBG)
}
dq := dev.destroyQueue()
if dq == nil {
// No DestroyQueue — register cleanup that destroys immediately.
return runtime.AddCleanup(bg, func(ref bindGroupCleanupRef) {
if !ref.released.CompareAndSwap(false, true) {
return
}
slog.Warn("wgpu: BindGroup released by GC (missing explicit Release)", "label", ref.label)
ref.destroyFn()
}, bindGroupCleanupRef{
label: label,
released: bg.released,
destroyFn: destroyFn,
})
}
return runtime.AddCleanup(bg, func(ref bindGroupCleanupRef) {
if !ref.released.CompareAndSwap(false, true) {
return
}
slog.Warn("wgpu: BindGroup released by GC (missing explicit Release)", "label", ref.label)
subIdx := ref.lastSubIdx()
ref.destroyQueue.Defer(subIdx, "BindGroup(GC):"+ref.label, ref.destroyFn)
}, bindGroupCleanupRef{
label: label,
released: bg.released,
destroyQueue: dq,
lastSubIdx: dev.lastSubmissionIndex,
destroyFn: destroyFn,
})
}