Dictionnaires et unions¶
Niveau de fonctionnalités typé de l’adaptateur¶
WebIDL déclare GPURequestAdapterOptions.featureLevel comme DOMString. L’API Kotlin portable
utilise plutôt GPUFeatureLevel, avec Core par défaut et Compatibility comme autre valeur :
RequestAdapterOptions(featureLevel = GPUFeatureLevel.Compatibility)
Le mapper navigateur transmet "core" ou "compatibility" ; le binding brut
WGPURequestAdapterOptions.featureLevel reste une chaîne. Les valeurs natives suivent
WGPUFeatureLevel (Compatibility = 1u, Core = 2u). Ce changement casse la compatibilité source
et ABI des appels ou implémentations utilisant String.
Un adaptateur demandé en Compatibility peut encore annoncer des capacités Core. Les features du
device gouvernent la validation : sans demande explicite de features, Compatibility n’en active
aucune, tandis que Core active CoreFeaturesAndLimits. Une implémentation incapable d’appliquer
les restrictions Compatibility peut se replier sur Core ; la demande seule ne prouve donc pas
que ces restrictions sont actives.
Gestion des types union¶
Traitement des types « Dict »¶
WebGPU utilise des types union dans certains cas, notamment avec les séquences et les dictionnaires. Pour simplifier l'implémentation et garantir la sûreté de typage, ces unions sont transformées en un seul type dictionnaire.
Exemple : GPUColor¶
Dans la spécification WebIDL, GPUColor est défini comme une union :
typedef (sequence<double> or GPUColorDict) GPUColor;
Dans notre implémentation, il devient une interface unique :
// Depuis interfaces.kt
interface GPUColor {
val r: Double
val g: Double
val b: Double
val a: Double
}
Cette transformation apporte plusieurs bénéfices :
- Sûreté de typage — les propriétés sont définies explicitement avec leur type.
- Clarté — l'interface communique clairement la structure attendue.
- Cohérence — toutes les implémentations doivent fournir les mêmes propriétés.
- Indépendance de plateforme — l'interface peut être implémentée sur n'importe quelle cible.
Implémentation selon les modules¶
Dans le module cœur, l'interface est définie avec des types Kotlin :
// Depuis interfaces.kt dans webgpu-api
interface GPUColor {
val r: Double
val g: Double
val b: Double
val a: Double
}
Dans le module de bindings, l'interface est définie avec des types JavaScript :
// Depuis types.kt dans webgpu-web-bindings
external interface WGPUColor : JsAny {
var r: JsNumber /* double */
var g: JsNumber /* double */
var b: JsNumber /* double */
var a: JsNumber /* double */
}
Cette approche permet des implémentations spécifiques à chaque plateforme tout en conservant une API cohérente.
Records WebIDL et valeurs nullable¶
Un record<K, V> WebIDL n'est pas une Map Kotlin. Le binding généré le représente par
WebGpuRecord, un JsAny externe créé par createWebGpuRecord() (un objet sans prototype) et rempli
par setRecordValue(). Le dictionnaire reste ainsi un objet JavaScript ordinaire à propriétés
énumérables, comme l'exigent les API navigateur, au lieu d'une instance de Map. Il sert aux
constantes de pipeline et aux dictionnaires de limites demandées.
Les membres WebIDL nullable sont conservés à travers la frontière JavaScript. Un membre nullable est
généré en paramètre ou résultat nullable (WGPUBindGroup?, WGPUBuffer?), et une Promise dont la
valeur est nullable devient Promise<JsAny?>. Les wrappers navigateur transmettent donc null au
lieu de le contraindre, et les points d'entrée asynchrones renvoient des Result qui distinguent une
valeur absente, une promesse rejetée et une coroutine annulée.
Types dictionnaires et interfaces¶
Implémentation par interfaces¶
Les dictionnaires et interfaces WebGPU sont transformés en interface Kotlin. Ce choix apporte
plusieurs avantages :
- Indépendance de plateforme — les interfaces peuvent être implémentées différemment selon la cible tout en conservant une API cohérente.
- Extensibilité — de nouvelles fonctionnalités peuvent être ajoutées via des fonctions d'extension ou l'héritage d'interfaces.
- Sûreté de typage — le compilateur vérifie que toutes les propriétés et méthodes requises sont implémentées.
- Flexibilité — les implémentations peuvent être adaptées aux besoins de chaque plateforme.
Implémentation cœur¶
Le module cœur (webgpu-api) définit des interfaces portables avec des types Kotlin :
// Depuis interfaces.kt
interface GPUBufferDescriptor {
val size: GPUSize64
val usage: GPUBufferUsageFlags
val mappedAtCreation: Boolean
}
interface GPUDevice : GPUObjectBase, AutoCloseable {
val features: GPUSupportedFeatures
val limits: GPUSupportedLimits
val adapterInfo: GPUAdapterInfo
val queue: GPUQueue
fun createBuffer(descriptor: GPUBufferDescriptor): GPUBuffer
// Autres méthodes...
}
Implémentation web¶
Le module de bindings (webgpu-web-bindings) définit des interfaces JavaScript préfixées par WGPU :
// Depuis types.kt
external interface WGPUBufferDescriptor : JsAny, WGPUObjectDescriptorBase {
var size: JsNumber /* GPUSize64 */
var usage: JsNumber /* GPUBufferUsageFlags */
var mappedAtCreation: Boolean
}
external interface WGPUDevice : JsAny, WGPUObjectBase {
var features: JsAny /* GPUSupportedFeatures */
var limits: WGPUSupportedLimits /* GPUSupportedLimits */
var adapterInfo: WGPUAdapterInfo /* GPUAdapterInfo */
var queue: WGPUQueue /* GPUQueue */
fun createBuffer(descriptor: WGPUBufferDescriptor /* GPUBufferDescriptor */): WGPUBuffer
// Autres méthodes...
}
Extensibilité¶
Cette approche par interfaces autorise différentes implémentations et extensions :
- Implémentations de base — une implémentation fournit des versions directes de ces interfaces.
- Implémentations avancées — d'autres projets peuvent étendre ces interfaces pour proposer des DSL ou des méthodes utilitaires riches.
- Extensions personnalisées — les développeurs peuvent ajouter des fonctions d'extension sans modifier les interfaces cœur.
Valeurs par défaut¶
Chaque implémentation doit respecter les valeurs par défaut définies par le WebIDL pour rester conforme à la spécification WebGPU. Cette approche évite les divergences entre plateformes.
Par exemple, si une propriété WebIDL a une valeur par défaut :
dictionary GPUSamplerDescriptor {
GPUAddressMode addressModeU = "clamp-to-edge";
// Autres propriétés...
};
L'implémentation Kotlin doit respecter cette valeur :
interface GPUSamplerDescriptor {
val addressModeU: GPUAddressMode get() = GPUAddressMode.ClampToEdge
// Autres propriétés...
}