Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions core/adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
func TestGetAdapterInfo(t *testing.T) {
GetGlobal().Clear()

instance := NewInstance(nil)
instance := NewInstanceWithMock(nil)
adapters := instance.EnumerateAdapters()
if len(adapters) == 0 {
t.Fatal("no adapters available")
Expand Down Expand Up @@ -49,7 +49,7 @@ func TestGetAdapterInfoInvalid(t *testing.T) {
func TestGetAdapterFeatures(t *testing.T) {
GetGlobal().Clear()

instance := NewInstance(nil)
instance := NewInstanceWithMock(nil)
adapters := instance.EnumerateAdapters()
if len(adapters) == 0 {
t.Fatal("no adapters available")
Expand Down Expand Up @@ -80,7 +80,7 @@ func TestGetAdapterFeaturesInvalid(t *testing.T) {
func TestGetAdapterLimits(t *testing.T) {
GetGlobal().Clear()

instance := NewInstance(nil)
instance := NewInstanceWithMock(nil)
adapters := instance.EnumerateAdapters()
if len(adapters) == 0 {
t.Fatal("no adapters available")
Expand Down Expand Up @@ -154,7 +154,7 @@ func TestRequestDevice(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
GetGlobal().Clear()

instance := NewInstance(nil)
instance := NewInstanceWithMock(nil)
adapters := instance.EnumerateAdapters()
if len(adapters) == 0 {
t.Fatal("no adapters available")
Expand Down Expand Up @@ -202,7 +202,7 @@ func TestRequestDeviceInvalidAdapter(t *testing.T) {
func TestAdapterDrop(t *testing.T) {
GetGlobal().Clear()

instance := NewInstance(nil)
instance := NewInstanceWithMock(nil)
adapters := instance.EnumerateAdapters()
if len(adapters) == 0 {
t.Fatal("no adapters available")
Expand Down Expand Up @@ -244,7 +244,7 @@ func TestAdapterLifecycle(t *testing.T) {
GetGlobal().Clear()

// 1. Create instance
instance := NewInstance(nil)
instance := NewInstanceWithMock(nil)

// 2. Request adapter
adapterID, err := instance.RequestAdapter(nil)
Expand Down Expand Up @@ -298,7 +298,7 @@ func TestAdapterLifecycle(t *testing.T) {
func TestAdapterConcurrentAccess(t *testing.T) {
GetGlobal().Clear()

instance := NewInstance(nil)
instance := NewInstanceWithMock(nil)
adapterID, err := instance.RequestAdapter(nil)
if err != nil {
t.Fatalf("RequestAdapter() error: %v", err)
Expand All @@ -324,7 +324,7 @@ func TestAdapterConcurrentAccess(t *testing.T) {
func TestRequestDeviceFeatureValidation(t *testing.T) {
GetGlobal().Clear()

instance := NewInstance(nil)
instance := NewInstanceWithMock(nil)
adapterID, err := instance.RequestAdapter(nil)
if err != nil {
t.Fatalf("RequestAdapter() error: %v", err)
Expand Down
7 changes: 3 additions & 4 deletions core/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ var (
gputypes.BackendMetal,
gputypes.BackendDX12,
gputypes.BackendGL,
gputypes.BackendEmpty, // noop/software fallback
gputypes.BackendEmpty, // explicitly registered software/noop provider
}
)

Expand Down Expand Up @@ -182,9 +182,8 @@ func FilterBackendsByMask(mask gputypes.Backends) []BackendProvider {
result = append(result, p)
}
case gputypes.BackendEmpty:
// Software/noop backend included as fallback for all masks.
// Adapter selection (RequestAdapter) prefers GPU adapters over CPU;
// software only wins if ForceFallbackAdapter is set or no GPU available.
// The software/noop provider is selectable when explicitly registered;
// NewInstance never fabricates an adapter when it is absent.
result = append(result, p)
default:
// Unknown backend types pass through if Primary is set
Expand Down
3 changes: 2 additions & 1 deletion core/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ import (
type testProvider struct {
variant gputypes.Backend
available bool
instance hal.Instance
}

func (p *testProvider) Variant() gputypes.Backend { return p.variant }
func (p *testProvider) CreateInstance(_ *hal.InstanceDescriptor) (hal.Instance, error) {
return nil, nil //nolint:nilnil
return p.instance, nil
}
func (p *testProvider) IsAvailable() bool { return p.available }

Expand Down
9 changes: 3 additions & 6 deletions core/hal_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,9 @@ func TestCoreHALIntegration(t *testing.T) {
}
defer instance.Destroy()

// Check if we're using real adapters or mock
if instance.IsMock() {
t.Log("Instance is using mock adapters (no GPU available)")
} else {
t.Log("Instance is using real HAL adapters")
}
// NewInstance never fabricates mock adapters. Any adapters returned here
// therefore came from an explicitly registered HAL provider.
t.Log("Instance is using registered HAL adapters")

// Enumerate adapters
adapterIDs := instance.EnumerateAdapters()
Expand Down
75 changes: 11 additions & 64 deletions core/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,18 @@ type Instance struct {
// glesEnumerated tracks whether deferred GLES adapters have been enumerated.
glesEnumerated bool

// useMock indicates whether to use mock adapters (for testing or when no HAL available).
// useMock indicates whether this instance was explicitly created with mock
// adapters through NewInstanceWithMock.
useMock bool
}

// NewInstance creates a new WebGPU instance with the given descriptor.
// If desc is nil, default settings are used.
//
// The instance will enumerate available GPU adapters based on the enabled
// backends specified in the descriptor. If HAL backends are available,
// real GPU adapters will be enumerated. Otherwise, a mock adapter is created
// for testing purposes.
// backends specified in the descriptor. If no provider is available, the
// instance remains empty and RequestAdapter reports the failure. Tests that
// need a deterministic adapter must opt in through NewInstanceWithMock.
func NewInstance(desc *gputypes.InstanceDescriptor) *Instance {
if desc == nil {
defaultDesc := gputypes.DefaultInstanceDescriptor()
Expand All @@ -73,13 +74,7 @@ func NewInstance(desc *gputypes.InstanceDescriptor) *Instance {
}

// Try to enumerate real adapters via HAL backends
realAdaptersFound := i.enumerateRealAdapters(desc)

// Fall back to mock adapter if no real adapters were found
if !realAdaptersFound {
i.useMock = true
i.createMockAdapter()
}
i.enumerateRealAdapters(desc)

trackResource(uintptr(unsafe.Pointer(i)), "Instance") //nolint:gosec // debug tracking uses pointer as unique ID
return i
Expand Down Expand Up @@ -107,19 +102,15 @@ func NewInstanceWithMock(desc *gputypes.InstanceDescriptor) *Instance {
return i
}

// enumerateRealAdapters attempts to enumerate real GPU adapters via HAL backends.
// Returns true if at least one real adapter was found.
func (i *Instance) enumerateRealAdapters(desc *gputypes.InstanceDescriptor) bool {
// enumerateRealAdapters attempts to enumerate real GPU adapters via HAL
// backends. If none are available, the instance remains empty.
func (i *Instance) enumerateRealAdapters(desc *gputypes.InstanceDescriptor) {
// First, ensure HAL backends are registered
RegisterHALBackends()

// Get backend providers filtered by the enabled backends mask
providers := FilterBackendsByMask(desc.Backends)
if len(providers) == 0 {
return false
}

foundAdapters := false
hub := GetGlobal().Hub()

// Create HAL descriptor
Expand Down Expand Up @@ -186,11 +177,8 @@ func (i *Instance) enumerateRealAdapters(desc *gputypes.InstanceDescriptor) bool
// Register in the hub
adapterID := hub.RegisterAdapter(adapter)
i.adapters = append(i.adapters, adapterID)
foundAdapters = true
}
}

return foundAdapters
}

// createMockAdapter creates a mock adapter for testing purposes.
Expand Down Expand Up @@ -401,47 +389,6 @@ func (i *Instance) enumerateDeferredGLES(surfaceHint hal.Surface) {

// Clear deferred list -- enumeration is done.
i.deferredGLES = nil

// If we were in mock mode and now have real adapters from GLES,
// remove mock adapters so real ones are selected first.
if i.useMock && i.hasRealAdaptersLocked(hub) {
i.useMock = false
i.removeMockAdaptersLocked(hub)
}
}

// hasRealAdaptersLocked checks if any adapter has a non-nil HAL adapter.
// Caller must hold i.mu.
func (i *Instance) hasRealAdaptersLocked(hub *Hub) bool {
for _, adapterID := range i.adapters {
adapter, err := hub.GetAdapter(adapterID)
if err != nil {
continue
}
if adapter.halAdapter != nil {
return true
}
}
return false
}

// removeMockAdaptersLocked filters out mock adapters (halAdapter == nil) from
// the adapter list and unregisters them from the hub.
// Caller must hold i.mu.
func (i *Instance) removeMockAdaptersLocked(hub *Hub) {
filtered := make([]AdapterID, 0, len(i.adapters))
for _, adapterID := range i.adapters {
adapter, err := hub.GetAdapter(adapterID)
if err != nil {
continue
}
if adapter.halAdapter != nil {
filtered = append(filtered, adapterID)
} else {
_, _ = hub.UnregisterAdapter(adapterID)
}
}
i.adapters = filtered
}

// matchesPowerPreference checks if a device type matches the power preference.
Expand Down Expand Up @@ -473,8 +420,8 @@ func (i *Instance) Flags() gputypes.InstanceFlags {
}

// IsMock returns true if the instance is using mock adapters.
// Mock adapters are used when no HAL backends are available or
// when the instance was explicitly created with NewInstanceWithMock.
// Mock adapters are used only when the instance was explicitly created with
// NewInstanceWithMock.
func (i *Instance) IsMock() bool {
i.mu.RLock()
defer i.mu.RUnlock()
Expand Down
Loading
Loading