Generate multiple variants of webgpu.h header
Adds a "tag" system so that entries of dawn.json can be conditionally emitted in different configurations. With a few more dawn.json changes, this will enable generating the exact upstream header. Bug: dawn:1080 Change-Id: I3506dadd485e31786578a3a64c3603c964c5354f Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/62580 Commit-Queue: Kai Ninomiya <kainino@chromium.org> Reviewed-by: Austin Eng <enga@chromium.org>
This commit is contained in:
parent
0a4cb8d859
commit
930e9186a6
91
dawn.json
91
dawn.json
|
@ -22,8 +22,8 @@
|
|||
"extensible": true,
|
||||
"output": true,
|
||||
"members": [
|
||||
{"name": "device ID", "type": "uint32_t"},
|
||||
{"name": "vendor ID", "type": "uint32_t"},
|
||||
{"name": "device ID", "type": "uint32_t"},
|
||||
{"name": "name", "type": "char", "annotation": "const*"},
|
||||
{"name": "driver description", "type": "char", "annotation": "const*"},
|
||||
{"name": "adapter type", "type": "adapter type"},
|
||||
|
@ -32,7 +32,7 @@
|
|||
},
|
||||
"adapter type": {
|
||||
"category": "enum",
|
||||
"javascript": false,
|
||||
"emscripten_no_enum_table": true,
|
||||
"values": [
|
||||
{"value": 0, "name": "discrete GPU"},
|
||||
{"value": 1, "name": "integrated GPU"},
|
||||
|
@ -50,7 +50,7 @@
|
|||
},
|
||||
"backend type": {
|
||||
"category": "enum",
|
||||
"javascript": false,
|
||||
"emscripten_no_enum_table": true,
|
||||
"values": [
|
||||
{"value": 0, "name": "null"},
|
||||
{"value": 1, "name": "D3D11"},
|
||||
|
@ -150,6 +150,7 @@
|
|||
"external texture binding entry": {
|
||||
"category": "structure",
|
||||
"chained": true,
|
||||
"tags": ["dawn"],
|
||||
"members": [
|
||||
{"name": "external texture", "type": "external texture"}
|
||||
]
|
||||
|
@ -158,6 +159,7 @@
|
|||
"external texture binding layout": {
|
||||
"category": "structure",
|
||||
"chained": true,
|
||||
"tags": ["dawn"],
|
||||
"members": []
|
||||
},
|
||||
|
||||
|
@ -165,7 +167,7 @@
|
|||
"category": "enum",
|
||||
"values": [
|
||||
{"value": 0, "name": "undefined", "jsrepr": "undefined", "valid": false},
|
||||
{"value": 1, "name": "write only", "jsrepr": "writeonly"}
|
||||
{"value": 1, "name": "write only"}
|
||||
]
|
||||
},
|
||||
"storage texture binding layout": {
|
||||
|
@ -271,6 +273,8 @@
|
|||
{
|
||||
"name": "set label",
|
||||
"returns": "void",
|
||||
"tags": ["dawn"],
|
||||
"_TODO": "needs an upstream equivalent",
|
||||
"args": [
|
||||
{"name": "label", "type": "char", "annotation": "const*", "length": "strlen"}
|
||||
]
|
||||
|
@ -302,6 +306,7 @@
|
|||
},
|
||||
"buffer map async status": {
|
||||
"category": "enum",
|
||||
"emscripten_no_enum_table": true,
|
||||
"values": [
|
||||
{"value": 0, "name": "success"},
|
||||
{"value": 1, "name": "error"},
|
||||
|
@ -420,6 +425,7 @@
|
|||
},
|
||||
{
|
||||
"name": "copy texture to texture internal",
|
||||
"tags": ["dawn"],
|
||||
"args": [
|
||||
{"name": "source", "type": "image copy texture", "annotation": "const*"},
|
||||
{"name": "destination", "type": "image copy texture", "annotation": "const*"},
|
||||
|
@ -428,10 +434,10 @@
|
|||
},
|
||||
{
|
||||
"name": "inject validation error",
|
||||
"tags": ["dawn"],
|
||||
"args": [
|
||||
{"name": "message", "type": "char", "annotation": "const*", "length": "strlen"}
|
||||
],
|
||||
"TODO": "enga@: Make this a Dawn extension"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "insert debug marker",
|
||||
|
@ -499,6 +505,7 @@
|
|||
},
|
||||
"compilation info callback": {
|
||||
"category": "callback",
|
||||
"tags": ["dawn"],
|
||||
"args": [
|
||||
{"name": "status", "type": "compilation info request status"},
|
||||
{"name": "compilation info", "type": "compilation info", "annotation": "const*"},
|
||||
|
@ -507,6 +514,7 @@
|
|||
},
|
||||
"compilation info request status": {
|
||||
"category": "enum",
|
||||
"tags": ["dawn"],
|
||||
"values": [
|
||||
{"value": 0, "name": "success"},
|
||||
{"value": 1, "name": "error"},
|
||||
|
@ -528,6 +536,7 @@
|
|||
},
|
||||
"compilation message type": {
|
||||
"category": "enum",
|
||||
"emscripten_no_enum_table": true,
|
||||
"values": [
|
||||
{"value": 0, "name": "error"},
|
||||
{"value": 1, "name": "warning"},
|
||||
|
@ -632,6 +641,7 @@
|
|||
},
|
||||
"alpha op": {
|
||||
"category": "enum",
|
||||
"tags": ["dawn"],
|
||||
"values": [
|
||||
{"value": 0, "name": "dont change"},
|
||||
{"value": 1, "name": "premultiply"},
|
||||
|
@ -641,6 +651,7 @@
|
|||
"copy texture for browser options": {
|
||||
"category": "structure",
|
||||
"extensible": true,
|
||||
"tags": ["dawn"],
|
||||
"members": [
|
||||
{"name": "flipY", "type": "bool", "default": "false"},
|
||||
{"name": "alphaOp", "type": "alpha op", "default": "dont change"}
|
||||
|
@ -657,6 +668,7 @@
|
|||
},
|
||||
"create pipeline async status": {
|
||||
"category": "enum",
|
||||
"emscripten_no_enum_table": true,
|
||||
"values": [
|
||||
{"value": 0, "name": "success"},
|
||||
{"value": 1, "name": "error"},
|
||||
|
@ -709,7 +721,7 @@
|
|||
{
|
||||
"name": "create error buffer",
|
||||
"returns": "buffer",
|
||||
"TODO": "enga@: Make this part of a dawn_wire extension"
|
||||
"tags": ["dawn"]
|
||||
},
|
||||
{
|
||||
"name": "create command encoder",
|
||||
|
@ -737,6 +749,7 @@
|
|||
{
|
||||
"name": "create external texture",
|
||||
"returns": "external texture",
|
||||
"tags": ["dawn"],
|
||||
"args": [
|
||||
{"name": "external texture descriptor", "type": "external texture descriptor", "annotation": "const*"}
|
||||
]
|
||||
|
@ -824,13 +837,15 @@
|
|||
{"name": "type", "type": "error type"},
|
||||
{"name": "message", "type": "char", "annotation": "const*", "length": "strlen"}
|
||||
],
|
||||
"TODO": "enga@: Make this a Dawn extension"
|
||||
"tags": ["dawn"]
|
||||
},
|
||||
{
|
||||
"name": "lose for testing"
|
||||
"name": "lose for testing",
|
||||
"tags": ["dawn"]
|
||||
},
|
||||
{
|
||||
"name": "tick"
|
||||
"name": "tick",
|
||||
"tags": ["dawn"]
|
||||
},
|
||||
{
|
||||
"name": "set uncaptured error callback",
|
||||
|
@ -841,6 +856,7 @@
|
|||
},
|
||||
{
|
||||
"name": "set logging callback",
|
||||
"tags": ["dawn"],
|
||||
"args": [
|
||||
{"name": "callback", "type": "logging callback"},
|
||||
{"name": "userdata", "type": "void", "annotation": "*"}
|
||||
|
@ -879,6 +895,7 @@
|
|||
"device properties": {
|
||||
"category": "structure",
|
||||
"extensible": false,
|
||||
"tags": ["dawn"],
|
||||
"members": [
|
||||
{"name": "device ID", "type": "uint32_t"},
|
||||
{"name": "vendor ID", "type": "uint32_t"},
|
||||
|
@ -954,6 +971,7 @@
|
|||
},
|
||||
"logging callback": {
|
||||
"category": "callback",
|
||||
"tags": ["dawn"],
|
||||
"args": [
|
||||
{"name": "type", "type": "logging type"},
|
||||
{"name": "message", "type": "char", "annotation": "const*"},
|
||||
|
@ -970,6 +988,7 @@
|
|||
},
|
||||
"error type": {
|
||||
"category": "enum",
|
||||
"emscripten_no_enum_table": true,
|
||||
"values": [
|
||||
{"value": 0, "name": "no error"},
|
||||
{"value": 1, "name": "validation"},
|
||||
|
@ -980,6 +999,7 @@
|
|||
},
|
||||
"logging type": {
|
||||
"category": "enum",
|
||||
"tags": ["dawn"],
|
||||
"values": [
|
||||
{"value": 0, "name": "verbose"},
|
||||
{"value": 1, "name": "info"},
|
||||
|
@ -997,6 +1017,7 @@
|
|||
},
|
||||
"external texture": {
|
||||
"category": "object",
|
||||
"tags": ["dawn"],
|
||||
"methods": [
|
||||
{
|
||||
"name": "destroy",
|
||||
|
@ -1007,6 +1028,7 @@
|
|||
"external texture descriptor": {
|
||||
"category": "structure",
|
||||
"extensible": true,
|
||||
"tags": ["dawn"],
|
||||
"members": [
|
||||
{"name": "plane 0", "type": "texture view"},
|
||||
{"name": "format", "type": "texture format"}
|
||||
|
@ -1100,6 +1122,7 @@
|
|||
},
|
||||
"load op": {
|
||||
"category": "enum",
|
||||
"emscripten_no_enum_table": true,
|
||||
"values": [
|
||||
{"value": 0, "name": "clear"},
|
||||
{"value": 1, "name": "load"}
|
||||
|
@ -1152,6 +1175,7 @@
|
|||
},
|
||||
"present mode": {
|
||||
"category": "enum",
|
||||
"emscripten_no_enum_table": true,
|
||||
"values": [
|
||||
{"value": 0, "name": "immediate"},
|
||||
{"value": 1, "name": "mailbox"},
|
||||
|
@ -1243,6 +1267,7 @@
|
|||
{
|
||||
"name": "copy texture for browser",
|
||||
"extensible": true,
|
||||
"tags": ["dawn"],
|
||||
"args": [
|
||||
{"name": "source", "type": "image copy texture", "annotation": "const*"},
|
||||
{"name": "destination", "type": "image copy texture", "annotation": "const*"},
|
||||
|
@ -1261,6 +1286,7 @@
|
|||
},
|
||||
"queue work done status": {
|
||||
"category": "enum",
|
||||
"emscripten_no_enum_table": true,
|
||||
"values": [
|
||||
{"value": 0, "name": "success"},
|
||||
{"value": 1, "name": "error"},
|
||||
|
@ -1603,6 +1629,7 @@
|
|||
|
||||
"request device status": {
|
||||
"category": "enum",
|
||||
"emscripten_no_enum_table": true,
|
||||
"values": [
|
||||
{"value": 0, "name": "success"},
|
||||
{"value": 1, "name": "error"},
|
||||
|
@ -1734,6 +1761,7 @@
|
|||
"methods": [
|
||||
{
|
||||
"name": "get compilation info",
|
||||
"tags": ["dawn"],
|
||||
"args": [
|
||||
{"name": "callback", "type": "compilation info callback"},
|
||||
{"name": "userdata", "type": "void", "annotation": "*"}
|
||||
|
@ -1822,7 +1850,7 @@
|
|||
"surface descriptor from metal layer": {
|
||||
"category": "structure",
|
||||
"chained": true,
|
||||
"javascript": false,
|
||||
"tags": ["native"],
|
||||
"members": [
|
||||
{"name": "layer", "type": "void", "annotation": "*"}
|
||||
]
|
||||
|
@ -1830,7 +1858,7 @@
|
|||
"surface descriptor from windows HWND": {
|
||||
"category": "structure",
|
||||
"chained": true,
|
||||
"javascript": false,
|
||||
"tags": ["native"],
|
||||
"members": [
|
||||
{"name": "hinstance", "type": "void", "annotation": "*"},
|
||||
{"name": "hwnd", "type": "void", "annotation": "*"}
|
||||
|
@ -1839,7 +1867,7 @@
|
|||
"surface descriptor from xlib": {
|
||||
"category": "structure",
|
||||
"chained": true,
|
||||
"javascript": false,
|
||||
"tags": ["native"],
|
||||
"members": [
|
||||
{"name": "display", "type": "void", "annotation": "*"},
|
||||
{"name": "window", "type": "uint32_t"}
|
||||
|
@ -1848,7 +1876,7 @@
|
|||
"surface descriptor from windows core window": {
|
||||
"category": "structure",
|
||||
"chained": true,
|
||||
"javascript": false,
|
||||
"tags": ["dawn"],
|
||||
"members": [
|
||||
{"name": "core window", "type": "void", "annotation": "*"}
|
||||
]
|
||||
|
@ -1856,7 +1884,7 @@
|
|||
"surface descriptor from windows swap chain panel": {
|
||||
"category": "structure",
|
||||
"chained": true,
|
||||
"javascript": false,
|
||||
"tags": ["dawn"],
|
||||
"members": [
|
||||
{"name": "swap chain panel", "type": "void", "annotation": "*"}
|
||||
]
|
||||
|
@ -1866,6 +1894,7 @@
|
|||
"methods": [
|
||||
{
|
||||
"name": "configure",
|
||||
"tags": ["dawn"],
|
||||
"args": [
|
||||
{"name": "format", "type": "texture format"},
|
||||
{"name": "allowed usage", "type": "texture usage"},
|
||||
|
@ -1887,26 +1916,26 @@
|
|||
{"name": "width", "type": "uint32_t"},
|
||||
{"name": "height", "type": "uint32_t"},
|
||||
{"name": "present mode", "type": "present mode"},
|
||||
{"name": "implementation", "type": "uint64_t", "default": 0}
|
||||
{"name": "implementation", "type": "uint64_t", "default": 0, "tags": ["deprecated"]}
|
||||
]
|
||||
},
|
||||
"s type": {
|
||||
"category": "enum",
|
||||
"javascript": false,
|
||||
"emscripten_no_enum_table": true,
|
||||
"values": [
|
||||
{"value": 0, "name": "invalid", "valid": false},
|
||||
{"value": 1, "name": "surface descriptor from metal layer"},
|
||||
{"value": 2, "name": "surface descriptor from windows HWND"},
|
||||
{"value": 3, "name": "surface descriptor from xlib"},
|
||||
{"value": 1, "name": "surface descriptor from metal layer", "tags": ["native"]},
|
||||
{"value": 2, "name": "surface descriptor from windows HWND", "tags": ["native"]},
|
||||
{"value": 3, "name": "surface descriptor from xlib", "tags": ["native"]},
|
||||
{"value": 4, "name": "surface descriptor from canvas HTML selector"},
|
||||
{"value": 5, "name": "shader module SPIRV descriptor"},
|
||||
{"value": 6, "name": "shader module WGSL descriptor"},
|
||||
{"value": 7, "name": "primitive depth clamping state"},
|
||||
{"value": 8, "name": "surface descriptor from windows core window"},
|
||||
{"value": 9, "name": "external texture binding entry"},
|
||||
{"value": 10, "name": "external texture binding layout"},
|
||||
{"value": 11, "name": "surface descriptor from windows swap chain panel"},
|
||||
{"value": 1000, "name": "dawn texture internal usage descriptor"}
|
||||
{"value": 8, "name": "surface descriptor from windows core window", "tags": ["dawn"]},
|
||||
{"value": 9, "name": "external texture binding entry", "tags": ["dawn"]},
|
||||
{"value": 10, "name": "external texture binding layout", "tags": ["dawn"]},
|
||||
{"value": 11, "name": "surface descriptor from windows swap chain panel", "tags": ["dawn"]},
|
||||
{"value": 1000, "name": "dawn texture internal usage descriptor", "tags": ["dawn"]}
|
||||
]
|
||||
},
|
||||
"texture": {
|
||||
|
@ -1922,6 +1951,8 @@
|
|||
{
|
||||
"name": "set label",
|
||||
"returns": "void",
|
||||
"tags": ["dawn"],
|
||||
"_TODO": "needs an upstream equivalent",
|
||||
"args": [
|
||||
{"name": "label", "type": "char", "annotation": "const*", "length": "strlen"}
|
||||
]
|
||||
|
@ -1937,8 +1968,8 @@
|
|||
{"value": 0, "name": "all"},
|
||||
{"value": 1, "name": "stencil only"},
|
||||
{"value": 2, "name": "depth only"},
|
||||
{"value": 3, "name": "plane 0 only"},
|
||||
{"value": 4, "name": "plane 1 only"}
|
||||
{"value": 3, "name": "plane 0 only", "tags": ["dawn"]},
|
||||
{"value": 4, "name": "plane 1 only", "tags": ["dawn"]}
|
||||
]
|
||||
},
|
||||
"texture component type": {
|
||||
|
@ -1984,6 +2015,7 @@
|
|||
"category": "enum",
|
||||
"values": [
|
||||
{"value": 0, "name": "undefined", "valid": false, "jsrepr": "undefined"},
|
||||
|
||||
{"value": 1, "name": "R8 unorm"},
|
||||
{"value": 2, "name": "R8 snorm"},
|
||||
{"value": 3, "name": "R8 uint"},
|
||||
|
@ -2086,7 +2118,7 @@
|
|||
{"value": 92, "name": "ASTC 12x12 unorm"},
|
||||
{"value": 93, "name": "ASTC 12x12 unorm srgb"},
|
||||
|
||||
{"value": 94, "name": "R8 BG8 Biplanar 420 unorm"}
|
||||
{"value": 94, "name": "R8 BG8 Biplanar 420 unorm", "tags": ["dawn"]}
|
||||
]
|
||||
},
|
||||
"texture usage": {
|
||||
|
@ -2098,7 +2130,7 @@
|
|||
{"value": 4, "name": "texture binding"},
|
||||
{"value": 8, "name": "storage binding"},
|
||||
{"value": 16, "name": "render attachment"},
|
||||
{"value": 32, "name": "present"}
|
||||
{"value": 32, "name": "present", "tags": ["dawn"]}
|
||||
]
|
||||
},
|
||||
"texture view descriptor": {
|
||||
|
@ -2208,6 +2240,7 @@
|
|||
"dawn texture internal usage descriptor": {
|
||||
"category": "structure",
|
||||
"chained": true,
|
||||
"tags": ["dawn"],
|
||||
"members": [
|
||||
{"name": "internal usage", "type": "texture usage", "default": "none"}
|
||||
]
|
||||
|
|
|
@ -10,10 +10,10 @@ Most of the code generation is done from [`dawn.json`](../dawn.json) which is a
|
|||
|
||||
At this time it is used to generate:
|
||||
|
||||
- the `webgpu.h` C header
|
||||
- the `webgpu_cpp.cpp/h` C++ wrapper over the C header
|
||||
- the Dawn, Emscripten, and upstream webgpu-native `webgpu.h` C header
|
||||
- the Dawn and Emscripten `webgpu_cpp.cpp/h` C++ wrapper over the C header
|
||||
- libraries that implements `webgpu.h` by calling in a static or `thread_local` proc table
|
||||
- parts of the [Emscripten](https://emscripten.org/) WebGPU implementation
|
||||
- other parts of the [Emscripten](https://emscripten.org/) WebGPU implementation
|
||||
- a GMock version of the API with its proc table for testing
|
||||
- validation helper functions for dawn_native
|
||||
- the definition of dawn_native's proc table
|
||||
|
@ -25,6 +25,8 @@ Internally `dawn.json` is a dictionary from the "canonical name" of things to th
|
|||
|
||||
The basic schema is that every entry is a thing with a `"category"` key what determines the sub-schema to apply to that thing. Categories and their sub-shema are defined below. Several parts of the schema use the concept of "record" which is a list of "record members" which are a combination of a type, a name and other metadata. For example the list of arguments of a function is a record. The list of structure members is a record. This combined concept is useful for the dawn_wire generator to generate code for structure and function calls in a very similar way.
|
||||
|
||||
Most items and sub-items can include a list of `"tags"`, which, if specified, conditionally includes the item if any of its tags appears in the `enabled_tags` configuration passed to `parse_json`. This is used to include and exclude various items for Dawn, Emscripten, or upstream header variants. Tags are applied in the "parse_json" step ([rather than later](https://docs.google.com/document/d/1fBniVOxx3-hQbxHMugEPcQsaXaKBZYVO8yG9iXJp-fU/edit?usp=sharing)): this has the benefit of automatically catching when, for a particular tag configuration, an included item references an excluded item.
|
||||
|
||||
A **record** is a list of **record members**, each of which is a dictionary with the following schema:
|
||||
- `"name"` a string
|
||||
- `"type"` a string, the name of the base type for this member
|
||||
|
@ -44,6 +46,7 @@ A **record** is a list of **record members**, each of which is a dictionary with
|
|||
- `"value"` a number that can be decimal or hexadecimal
|
||||
- `"jsrepr"` (optional) a string to allow overriding how this value map to Javascript for the Emscripten bits
|
||||
- `"valid"` (defaults to true) a boolean that controls whether the dawn_native validation utilities will consider this enum value valid.
|
||||
- `"emscripten_no_enum_table"` (optional) if true, skips generating an enum table in `library_webgpu_enum_tables.js`
|
||||
|
||||
**`"bitmask"`** an `uint32_t`-based bitmask. It is similar to **`"enum"`** but can be output differently.
|
||||
|
||||
|
|
|
@ -45,6 +45,8 @@ dawn_allowed_gen_output_dirs = [
|
|||
"src/dawn_wire/server/",
|
||||
"src/dawn_wire/",
|
||||
"src/include/dawn/",
|
||||
"emscripten-bits/",
|
||||
"webgpu-headers/",
|
||||
]
|
||||
|
||||
# Template to help invoking Dawn code generators based on generator_lib
|
||||
|
|
|
@ -76,27 +76,27 @@ class Type:
|
|||
self.dict_name = name
|
||||
self.name = Name(name, native=native)
|
||||
self.category = json_data['category']
|
||||
self.javascript = self.json_data.get('javascript', True)
|
||||
|
||||
|
||||
EnumValue = namedtuple('EnumValue', ['name', 'value', 'valid', 'jsrepr'])
|
||||
EnumValue = namedtuple('EnumValue', ['name', 'value', 'valid', 'json_data'])
|
||||
|
||||
|
||||
class EnumType(Type):
|
||||
def __init__(self, name, json_data):
|
||||
def __init__(self, is_enabled, name, json_data):
|
||||
Type.__init__(self, name, json_data)
|
||||
|
||||
self.values = []
|
||||
self.contiguousFromZero = True
|
||||
lastValue = -1
|
||||
for m in self.json_data['values']:
|
||||
if not is_enabled(m):
|
||||
continue
|
||||
value = m['value']
|
||||
if value != lastValue + 1:
|
||||
self.contiguousFromZero = False
|
||||
lastValue = value
|
||||
self.values.append(
|
||||
EnumValue(Name(m['name']), value, m.get('valid', True),
|
||||
m.get('jsrepr', None)))
|
||||
EnumValue(Name(m['name']), value, m.get('valid', True), m))
|
||||
|
||||
# Assert that all values are unique in enums
|
||||
all_values = set()
|
||||
|
@ -107,15 +107,15 @@ class EnumType(Type):
|
|||
all_values.add(value.value)
|
||||
|
||||
|
||||
BitmaskValue = namedtuple('BitmaskValue', ['name', 'value'])
|
||||
BitmaskValue = namedtuple('BitmaskValue', ['name', 'value', 'json_data'])
|
||||
|
||||
|
||||
class BitmaskType(Type):
|
||||
def __init__(self, name, json_data):
|
||||
def __init__(self, is_enabled, name, json_data):
|
||||
Type.__init__(self, name, json_data)
|
||||
self.values = [
|
||||
BitmaskValue(Name(m['name']), m['value'])
|
||||
for m in self.json_data['values']
|
||||
BitmaskValue(Name(m['name']), m['value'], m)
|
||||
for m in self.json_data['values'] if is_enabled(m)
|
||||
]
|
||||
self.full_mask = 0
|
||||
for value in self.values:
|
||||
|
@ -123,19 +123,19 @@ class BitmaskType(Type):
|
|||
|
||||
|
||||
class CallbackType(Type):
|
||||
def __init__(self, name, json_data):
|
||||
def __init__(self, is_enabled, name, json_data):
|
||||
Type.__init__(self, name, json_data)
|
||||
self.arguments = []
|
||||
|
||||
|
||||
class TypedefType(Type):
|
||||
def __init__(self, name, json_data):
|
||||
def __init__(self, is_enabled, name, json_data):
|
||||
Type.__init__(self, name, json_data)
|
||||
self.type = None
|
||||
|
||||
|
||||
class NativeType(Type):
|
||||
def __init__(self, name, json_data):
|
||||
def __init__(self, is_enabled, name, json_data):
|
||||
Type.__init__(self, name, json_data, native=True)
|
||||
|
||||
|
||||
|
@ -146,6 +146,7 @@ class RecordMember:
|
|||
name,
|
||||
typ,
|
||||
annotation,
|
||||
json_data,
|
||||
optional=False,
|
||||
is_return_value=False,
|
||||
default_value=None,
|
||||
|
@ -153,6 +154,7 @@ class RecordMember:
|
|||
self.name = name
|
||||
self.type = typ
|
||||
self.annotation = annotation
|
||||
self.json_data = json_data
|
||||
self.length = None
|
||||
self.optional = optional
|
||||
self.is_return_value = is_return_value
|
||||
|
@ -165,14 +167,18 @@ class RecordMember:
|
|||
self.handle_type = handle_type
|
||||
|
||||
|
||||
Method = namedtuple('Method', ['name', 'return_type', 'arguments'])
|
||||
Method = namedtuple('Method',
|
||||
['name', 'return_type', 'arguments', 'json_data'])
|
||||
|
||||
|
||||
class ObjectType(Type):
|
||||
def __init__(self, name, json_data):
|
||||
Type.__init__(self, name, json_data)
|
||||
self.methods = []
|
||||
self.built_type = None
|
||||
def __init__(self, is_enabled, name, json_data):
|
||||
json_data_override = {'methods': []}
|
||||
if 'methods' in json_data:
|
||||
json_data_override['methods'] = [
|
||||
m for m in json_data['methods'] if is_enabled(m)
|
||||
]
|
||||
Type.__init__(self, name, dict(json_data, **json_data_override))
|
||||
|
||||
|
||||
class Record:
|
||||
|
@ -201,9 +207,14 @@ class Record:
|
|||
|
||||
|
||||
class StructureType(Record, Type):
|
||||
def __init__(self, name, json_data):
|
||||
def __init__(self, is_enabled, name, json_data):
|
||||
Record.__init__(self, name)
|
||||
Type.__init__(self, name, json_data)
|
||||
json_data_override = {}
|
||||
if 'members' in json_data:
|
||||
json_data_override['members'] = [
|
||||
m for m in json_data['members'] if is_enabled(m)
|
||||
]
|
||||
Type.__init__(self, name, dict(json_data, **json_data_override))
|
||||
self.chained = json_data.get("chained", False)
|
||||
self.extensible = json_data.get("extensible", False)
|
||||
self.output = json_data.get("output", False)
|
||||
|
@ -228,6 +239,7 @@ def linked_record_members(json_data, types):
|
|||
member = RecordMember(Name(m['name']),
|
||||
types[m['type']],
|
||||
m.get('annotation', 'value'),
|
||||
m,
|
||||
optional=m.get('optional', False),
|
||||
is_return_value=m.get('is_return_value', False),
|
||||
default_value=m.get('default', None),
|
||||
|
@ -263,7 +275,8 @@ def link_object(obj, types):
|
|||
def make_method(json_data):
|
||||
arguments = linked_record_members(json_data.get('args', []), types)
|
||||
return Method(Name(json_data['name']),
|
||||
types[json_data.get('returns', 'void')], arguments)
|
||||
types[json_data.get('returns',
|
||||
'void')], arguments, json_data)
|
||||
|
||||
obj.methods = [make_method(m) for m in obj.json_data.get('methods', [])]
|
||||
obj.methods.sort(key=lambda method: method.name.canonical_case())
|
||||
|
@ -324,7 +337,8 @@ def topo_sort_structure(structs):
|
|||
return result
|
||||
|
||||
|
||||
def parse_json(json):
|
||||
def parse_json(json, enabled_tags):
|
||||
is_enabled = lambda json_data: item_is_enabled(enabled_tags, json_data)
|
||||
category_to_parser = {
|
||||
'bitmask': BitmaskType,
|
||||
'enum': EnumType,
|
||||
|
@ -342,10 +356,10 @@ def parse_json(json):
|
|||
by_category[name] = []
|
||||
|
||||
for (name, json_data) in json.items():
|
||||
if name[0] == '_':
|
||||
if name[0] == '_' or not item_is_enabled(enabled_tags, json_data):
|
||||
continue
|
||||
category = json_data['category']
|
||||
parsed = category_to_parser[category](name, json_data)
|
||||
parsed = category_to_parser[category](is_enabled, name, json_data)
|
||||
by_category[category].append(parsed)
|
||||
types[name] = parsed
|
||||
|
||||
|
@ -370,7 +384,18 @@ def parse_json(json):
|
|||
for struct in by_category['structure']:
|
||||
struct.update_metadata()
|
||||
|
||||
return {'types': types, 'by_category': by_category}
|
||||
api_params = {
|
||||
'types': types,
|
||||
'by_category': by_category,
|
||||
'enabled_tags': enabled_tags,
|
||||
}
|
||||
return {
|
||||
'types': types,
|
||||
'by_category': by_category,
|
||||
'enabled_tags': enabled_tags,
|
||||
'c_methods': lambda typ: c_methods(api_params, typ),
|
||||
'c_methods_sorted_by_name': get_c_methods_sorted_by_name(api_params),
|
||||
}
|
||||
|
||||
|
||||
############################################################
|
||||
|
@ -411,7 +436,7 @@ def compute_wire_params(api_params, wire_json):
|
|||
# Create object method commands by prepending "self"
|
||||
members = [
|
||||
RecordMember(Name('self'), types[api_object.dict_name],
|
||||
'value')
|
||||
'value', {})
|
||||
]
|
||||
members += method.arguments
|
||||
|
||||
|
@ -420,7 +445,7 @@ def compute_wire_params(api_params, wire_json):
|
|||
if method.return_type.category == 'object':
|
||||
result = RecordMember(Name('result'),
|
||||
types['ObjectHandle'],
|
||||
'value',
|
||||
'value', {},
|
||||
is_return_value=True)
|
||||
result.set_handle_type(method.return_type)
|
||||
members.append(result)
|
||||
|
@ -490,7 +515,7 @@ def as_cppType(name):
|
|||
|
||||
|
||||
def as_jsEnumValue(value):
|
||||
if value.jsrepr: return value.jsrepr
|
||||
if 'jsrepr' in value.json_data: return value.json_data['jsrepr']
|
||||
return "'" + value.name.js_enum_case() + "'"
|
||||
|
||||
|
||||
|
@ -537,6 +562,12 @@ def annotated(typ, arg):
|
|||
return decorate(name, typ, arg)
|
||||
|
||||
|
||||
def item_is_enabled(enabled_tags, json_data):
|
||||
tags = json_data.get('tags')
|
||||
if tags is None: return True
|
||||
return any(tag in enabled_tags for tag in tags)
|
||||
|
||||
|
||||
def as_cEnum(type_name, value_name):
|
||||
assert not type_name.native and not value_name.native
|
||||
return 'WGPU' + type_name.CamelCase() + '_' + value_name.CamelCase()
|
||||
|
@ -600,17 +631,21 @@ def as_wireType(typ):
|
|||
return as_cppType(typ.name)
|
||||
|
||||
|
||||
def c_methods(types, typ):
|
||||
def c_methods(params, typ):
|
||||
return typ.methods + [
|
||||
Method(Name('reference'), types['void'], []),
|
||||
Method(Name('release'), types['void'], []),
|
||||
x for x in [
|
||||
Method(Name('reference'), params['types']['void'], [],
|
||||
{'tags': ['dawn', 'emscripten']}),
|
||||
Method(Name('release'), params['types']['void'], [],
|
||||
{'tags': ['dawn', 'emscripten']}),
|
||||
] if item_is_enabled(params['enabled_tags'], x.json_data)
|
||||
]
|
||||
|
||||
|
||||
def get_c_methods_sorted_by_name(api_params):
|
||||
unsorted = [(as_MethodSuffix(typ.name, method.name), typ, method) \
|
||||
for typ in api_params['by_category']['object'] \
|
||||
for method in c_methods(api_params['types'], typ) ]
|
||||
for method in c_methods(api_params, typ) ]
|
||||
return [(typ, method) for (_, typ, method) in sorted(unsorted)]
|
||||
|
||||
|
||||
|
@ -643,11 +678,9 @@ class MultiGeneratorFromDawnJSON(Generator):
|
|||
help=
|
||||
'Comma-separated subset of targets to output. Available targets: '
|
||||
+ ', '.join(allowed_targets))
|
||||
|
||||
def get_file_renders(self, args):
|
||||
with open(args.dawn_json) as f:
|
||||
loaded_json = json.loads(f.read())
|
||||
api_params = parse_json(loaded_json)
|
||||
|
||||
targets = args.targets.split(',')
|
||||
|
||||
|
@ -656,7 +689,9 @@ class MultiGeneratorFromDawnJSON(Generator):
|
|||
with open(args.wire_json) as f:
|
||||
wire_json = json.loads(f.read())
|
||||
|
||||
base_params = {
|
||||
renders = []
|
||||
|
||||
RENDER_PARAMS_BASE = {
|
||||
'Name': lambda name: Name(name),
|
||||
'as_annotated_cType': \
|
||||
lambda arg: annotated(as_cTypeEnumSpecialCase(arg.type), arg),
|
||||
|
@ -677,59 +712,75 @@ class MultiGeneratorFromDawnJSON(Generator):
|
|||
'convert_cType_to_cppType': convert_cType_to_cppType,
|
||||
'as_varName': as_varName,
|
||||
'decorate': decorate,
|
||||
'c_methods': lambda typ: c_methods(api_params['types'], typ),
|
||||
'c_methods_sorted_by_name': \
|
||||
get_c_methods_sorted_by_name(api_params),
|
||||
}
|
||||
|
||||
renders = []
|
||||
params_dawn = parse_json(loaded_json,
|
||||
enabled_tags=['dawn', 'native', 'deprecated'])
|
||||
|
||||
if 'dawn_headers' in targets:
|
||||
renders.append(
|
||||
FileRender('webgpu.h', 'src/include/dawn/webgpu.h',
|
||||
[base_params, api_params]))
|
||||
[RENDER_PARAMS_BASE, params_dawn]))
|
||||
renders.append(
|
||||
FileRender('dawn_proc_table.h',
|
||||
'src/include/dawn/dawn_proc_table.h',
|
||||
[base_params, api_params]))
|
||||
[RENDER_PARAMS_BASE, params_dawn]))
|
||||
|
||||
if 'dawncpp_headers' in targets:
|
||||
renders.append(
|
||||
FileRender('webgpu_cpp.h', 'src/include/dawn/webgpu_cpp.h',
|
||||
[base_params, api_params]))
|
||||
[RENDER_PARAMS_BASE, params_dawn]))
|
||||
|
||||
renders.append(
|
||||
FileRender('webgpu_cpp_print.h',
|
||||
'src/include/dawn/webgpu_cpp_print.h',
|
||||
[base_params, api_params]))
|
||||
[RENDER_PARAMS_BASE, params_dawn]))
|
||||
|
||||
if 'dawn_proc' in targets:
|
||||
renders.append(
|
||||
FileRender('dawn_proc.c', 'src/dawn/dawn_proc.c',
|
||||
[base_params, api_params]))
|
||||
[RENDER_PARAMS_BASE, params_dawn]))
|
||||
renders.append(
|
||||
FileRender('dawn_thread_dispatch_proc.cpp',
|
||||
'src/dawn/dawn_thread_dispatch_proc.cpp',
|
||||
[base_params, api_params]))
|
||||
[RENDER_PARAMS_BASE, params_dawn]))
|
||||
|
||||
if 'dawncpp' in targets:
|
||||
renders.append(
|
||||
FileRender('webgpu_cpp.cpp', 'src/dawn/webgpu_cpp.cpp',
|
||||
[base_params, api_params]))
|
||||
[RENDER_PARAMS_BASE, params_dawn]))
|
||||
|
||||
if 'webgpu_headers' in targets:
|
||||
params_upstream = parse_json(loaded_json,
|
||||
enabled_tags=['upstream', 'native'])
|
||||
renders.append(
|
||||
FileRender('webgpu.h', 'webgpu-headers/webgpu.h',
|
||||
[RENDER_PARAMS_BASE, params_upstream]))
|
||||
|
||||
if 'emscripten_bits' in targets:
|
||||
params_emscripten = parse_json(
|
||||
loaded_json, enabled_tags=['upstream', 'emscripten'])
|
||||
renders.append(
|
||||
FileRender('webgpu.h', 'emscripten-bits/webgpu.h',
|
||||
[RENDER_PARAMS_BASE, params_emscripten]))
|
||||
renders.append(
|
||||
FileRender('webgpu_cpp.h', 'emscripten-bits/webgpu_cpp.h',
|
||||
[RENDER_PARAMS_BASE, params_emscripten]))
|
||||
renders.append(
|
||||
FileRender('webgpu_cpp.cpp', 'emscripten-bits/webgpu_cpp.cpp',
|
||||
[RENDER_PARAMS_BASE, params_emscripten]))
|
||||
renders.append(
|
||||
FileRender('webgpu_struct_info.json',
|
||||
'src/dawn/webgpu_struct_info.json',
|
||||
[base_params, api_params]))
|
||||
'emscripten-bits/webgpu_struct_info.json',
|
||||
[RENDER_PARAMS_BASE, params_emscripten]))
|
||||
renders.append(
|
||||
FileRender('library_webgpu_enum_tables.js',
|
||||
'src/dawn/library_webgpu_enum_tables.js',
|
||||
[base_params, api_params]))
|
||||
'emscripten-bits/library_webgpu_enum_tables.js',
|
||||
[RENDER_PARAMS_BASE, params_emscripten]))
|
||||
|
||||
if 'mock_webgpu' in targets:
|
||||
mock_params = [
|
||||
base_params, api_params, {
|
||||
RENDER_PARAMS_BASE, params_dawn, {
|
||||
'has_callback_arguments': has_callback_arguments
|
||||
}
|
||||
]
|
||||
|
@ -742,8 +793,8 @@ class MultiGeneratorFromDawnJSON(Generator):
|
|||
|
||||
if 'dawn_native_utils' in targets:
|
||||
frontend_params = [
|
||||
base_params,
|
||||
api_params,
|
||||
RENDER_PARAMS_BASE,
|
||||
params_dawn,
|
||||
{
|
||||
# TODO: as_frontendType and co. take a Type, not a Name :(
|
||||
'as_frontendType': lambda typ: as_frontendType(typ),
|
||||
|
@ -781,10 +832,10 @@ class MultiGeneratorFromDawnJSON(Generator):
|
|||
frontend_params))
|
||||
|
||||
if 'dawn_wire' in targets:
|
||||
additional_params = compute_wire_params(api_params, wire_json)
|
||||
additional_params = compute_wire_params(params_dawn, wire_json)
|
||||
|
||||
wire_params = [
|
||||
base_params, api_params, {
|
||||
RENDER_PARAMS_BASE, params_dawn, {
|
||||
'as_wireType': as_wireType,
|
||||
'as_annotated_wireType': \
|
||||
lambda arg: annotated(as_wireType(arg.type), arg),
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
//* Emscripten's library_webgpu.js.
|
||||
//* https://github.com/emscripten-core/emscripten/blob/master/src/library_webgpu.js
|
||||
//*
|
||||
{% for type in by_category["enum"] if type.javascript %}
|
||||
{% for type in by_category["enum"] if not type.json_data.get("emscripten_no_enum_table") %}
|
||||
{{type.name.CamelCase()}}: {% if type.contiguousFromZero -%}
|
||||
[
|
||||
{% for value in type.values %}
|
||||
|
|
|
@ -74,8 +74,10 @@
|
|||
#include <stdbool.h>
|
||||
|
||||
#define WGPU_WHOLE_SIZE (0xffffffffffffffffULL)
|
||||
// TODO(crbug.com/dawn/520): Remove WGPU_STRIDE_UNDEFINED in favor of WGPU_COPY_STRIDE_UNDEFINED.
|
||||
#define WGPU_STRIDE_UNDEFINED (0xffffffffUL)
|
||||
{% if 'deprecated' in enabled_tags %}
|
||||
// TODO(crbug.com/dawn/520): Remove WGPU_STRIDE_UNDEFINED in favor of WGPU_COPY_STRIDE_UNDEFINED.
|
||||
#define WGPU_STRIDE_UNDEFINED (0xffffffffUL)
|
||||
{% endif %}
|
||||
#define WGPU_COPY_STRIDE_UNDEFINED (0xffffffffUL)
|
||||
#define WGPU_LIMIT_U32_UNDEFINED (0xffffffffUL)
|
||||
#define WGPU_LIMIT_U64_UNDEFINED (0xffffffffffffffffULL)
|
||||
|
@ -99,7 +101,7 @@ typedef uint32_t WGPUFlags;
|
|||
typedef WGPUFlags {{as_cType(type.name)}}Flags;
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
{% endfor -%}
|
||||
|
||||
typedef struct WGPUChainedStruct {
|
||||
struct WGPUChainedStruct const * next;
|
||||
|
@ -127,19 +129,19 @@ typedef struct WGPUChainedStructOut {
|
|||
} {{as_cType(type.name)}};
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{% for typeDef in by_category["typedef"] %}
|
||||
// {{as_cType(typeDef.name)}} is deprecated.
|
||||
// Use {{as_cType(typeDef.type.name)}} instead.
|
||||
typedef {{as_cType(typeDef.type.name)}} {{as_cType(typeDef.name)}};
|
||||
|
||||
{% endfor %}
|
||||
{% if 'deprecated' in enabled_tags %}
|
||||
// TODO(crbug.com/dawn/1023): Remove after the deprecation period.
|
||||
#define WGPUInputStepMode_Vertex WGPUVertexStepMode_Vertex
|
||||
#define WGPUInputStepMode_Instance WGPUVertexStepMode_Instance
|
||||
#define WGPUInputStepMode_Force32 WGPUVertexStepMode_Force32
|
||||
|
||||
// TODO(crbug.com/dawn/1023): Remove after the deprecation period.
|
||||
#define WGPUInputStepMode_Vertex WGPUVertexStepMode_Vertex
|
||||
#define WGPUInputStepMode_Instance WGPUVertexStepMode_Instance
|
||||
#define WGPUInputStepMode_Force32 WGPUVertexStepMode_Force32
|
||||
|
||||
{% endif %}
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
|
|
@ -11,7 +11,11 @@
|
|||
//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//* See the License for the specific language governing permissions and
|
||||
//* limitations under the License.
|
||||
#include "dawn/webgpu_cpp.h"
|
||||
{% if 'dawn' in enabled_tags %}
|
||||
#include "dawn/webgpu_cpp.h"
|
||||
{% else %}
|
||||
#include "webgpu/webgpu_cpp.h"
|
||||
{% endif %}
|
||||
|
||||
namespace wgpu {
|
||||
{% for type in by_category["enum"] %}
|
||||
|
|
|
@ -20,8 +20,10 @@
|
|||
namespace wgpu {
|
||||
|
||||
static constexpr uint64_t kWholeSize = WGPU_WHOLE_SIZE;
|
||||
// TODO(crbug.com/520): Remove kStrideUndefined in favor of kCopyStrideUndefined.
|
||||
static constexpr uint32_t kStrideUndefined = WGPU_STRIDE_UNDEFINED;
|
||||
{% if 'deprecated' in enabled_tags %}
|
||||
// TODO(crbug.com/520): Remove kStrideUndefined in favor of kCopyStrideUndefined.
|
||||
static constexpr uint32_t kStrideUndefined = WGPU_STRIDE_UNDEFINED;
|
||||
{% endif %}
|
||||
static constexpr uint32_t kCopyStrideUndefined = WGPU_COPY_STRIDE_UNDEFINED;
|
||||
static constexpr uint32_t kLimitU32Undefined = WGPU_LIMIT_U32_UNDEFINED;
|
||||
static constexpr uint64_t kLimitU64Undefined = WGPU_LIMIT_U64_UNDEFINED;
|
||||
|
@ -73,7 +75,6 @@ namespace wgpu {
|
|||
using {{as_cppType(typeDef.name)}} = {{as_cppType(typeDef.type.name)}};
|
||||
|
||||
{% endfor %}
|
||||
|
||||
template<typename Derived, typename CType>
|
||||
class ObjectBase {
|
||||
public:
|
||||
|
@ -237,7 +238,6 @@ namespace wgpu {
|
|||
};
|
||||
|
||||
{% endfor %}
|
||||
|
||||
} // namespace wgpu
|
||||
|
||||
#endif // WEBGPU_CPP_H_
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
"next",
|
||||
"sType"
|
||||
],
|
||||
{% for type in by_category["structure"] if type.javascript %}
|
||||
{% for type in by_category["structure"] %}
|
||||
"{{as_cType(type.name)}}": [
|
||||
{% if type.chained %}
|
||||
"chain"
|
||||
|
|
|
@ -29,14 +29,6 @@ dawn_json_generator("dawn_headers_gen") {
|
|||
]
|
||||
}
|
||||
|
||||
dawn_json_generator("emscripten_bits_gen") {
|
||||
target = "emscripten_bits"
|
||||
outputs = [
|
||||
"src/dawn/webgpu_struct_info.json",
|
||||
"src/dawn/library_webgpu_enum_tables.js",
|
||||
]
|
||||
}
|
||||
|
||||
source_set("dawn_headers") {
|
||||
all_dependent_configs = [ "${dawn_root}/src/common:dawn_public_include_dirs" ]
|
||||
public_deps = [ ":dawn_headers_gen" ]
|
||||
|
@ -107,3 +99,23 @@ dawn_component("dawn_proc") {
|
|||
"${dawn_root}/src/include/dawn/dawn_thread_dispatch_proc.h",
|
||||
]
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# Other generated files (upstream header, emscripten header, emscripten bits)
|
||||
###############################################################################
|
||||
|
||||
dawn_json_generator("webgpu_headers_gen") {
|
||||
target = "webgpu_headers"
|
||||
outputs = [ "webgpu-headers/webgpu.h" ]
|
||||
}
|
||||
|
||||
dawn_json_generator("emscripten_bits_gen") {
|
||||
target = "emscripten_bits"
|
||||
outputs = [
|
||||
"emscripten-bits/webgpu.h",
|
||||
"emscripten-bits/webgpu_cpp.h",
|
||||
"emscripten-bits/webgpu_cpp.cpp",
|
||||
"emscripten-bits/webgpu_struct_info.json",
|
||||
"emscripten-bits/library_webgpu_enum_tables.js",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -87,3 +87,29 @@ if(BUILD_SHARED_LIBS)
|
|||
endif()
|
||||
target_sources(dawn_proc PRIVATE ${DAWNPROC_GEN_SOURCES})
|
||||
target_link_libraries(dawn_proc PUBLIC dawn_headers)
|
||||
|
||||
###############################################################################
|
||||
# Other generated files (upstream header, emscripten header, emscripten bits)
|
||||
###############################################################################
|
||||
|
||||
DawnJSONGenerator(
|
||||
TARGET "webgpu_headers"
|
||||
PRINT_NAME "WebGPU headers"
|
||||
RESULT_VARIABLE "WEBGPU_HEADERS_GEN_SOURCES"
|
||||
)
|
||||
|
||||
add_library(webgpu_headers STATIC ${DAWN_DUMMY_FILE})
|
||||
target_sources(webgpu_headers PRIVATE
|
||||
${WEBGPU_HEADERS_GEN_SOURCES}
|
||||
)
|
||||
|
||||
DawnJSONGenerator(
|
||||
TARGET "emscripten_bits"
|
||||
PRINT_NAME "Emscripten WebGPU bits"
|
||||
RESULT_VARIABLE "EMSCRIPTEN_BITS_GEN_SOURCES"
|
||||
)
|
||||
|
||||
add_library(emscripten_bits STATIC ${DAWN_DUMMY_FILE})
|
||||
target_sources(emscripten_bits PRIVATE
|
||||
${EMSCRIPTEN_BITS_GEN_SOURCES}
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue