Support chained extension structs on the wire
This CL also adds a couple of dummy extensions in dawn.json so that the serialization/deserialization in the wire can be tested. Bug: dawn:369 Change-Id: I5ec3853c286f45d9b04e8bf9d04ebd9176dc917b Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/18520 Commit-Queue: Austin Eng <enga@chromium.org> Reviewed-by: Kai Ninomiya <kainino@chromium.org>
This commit is contained in:
parent
2479860e4b
commit
76a8d0b92f
1
BUILD.gn
1
BUILD.gn
|
@ -894,6 +894,7 @@ test("dawn_unittests") {
|
|||
"src/tests/unittests/wire/WireBasicTests.cpp",
|
||||
"src/tests/unittests/wire/WireBufferMappingTests.cpp",
|
||||
"src/tests/unittests/wire/WireErrorCallbackTests.cpp",
|
||||
"src/tests/unittests/wire/WireExtensionTests.cpp",
|
||||
"src/tests/unittests/wire/WireFenceTests.cpp",
|
||||
"src/tests/unittests/wire/WireInjectTextureTests.cpp",
|
||||
"src/tests/unittests/wire/WireMemoryTransferServiceTests.cpp",
|
||||
|
|
20
dawn.json
20
dawn.json
|
@ -1237,6 +1237,13 @@
|
|||
{"name": "alpha to coverage enabled", "type": "bool", "default": "false"}
|
||||
]
|
||||
},
|
||||
"render pipeline descriptor dummy extension": {
|
||||
"category": "structure",
|
||||
"chained": true,
|
||||
"members": [
|
||||
{"name": "dummy stage", "type": "programmable stage descriptor"}
|
||||
]
|
||||
},
|
||||
"sampler": {
|
||||
"category": "object"
|
||||
},
|
||||
|
@ -1256,6 +1263,13 @@
|
|||
{"name": "compare", "type": "compare function", "default": "never"}
|
||||
]
|
||||
},
|
||||
"sampler descriptor dummy anisotropic filtering": {
|
||||
"category": "structure",
|
||||
"chained": true,
|
||||
"members": [
|
||||
{"name": "max anisotropy", "type": "float"}
|
||||
]
|
||||
},
|
||||
"shader module": {
|
||||
"category": "object"
|
||||
},
|
||||
|
@ -1376,11 +1390,13 @@
|
|||
"category": "enum",
|
||||
"javascript": false,
|
||||
"values": [
|
||||
{"value": 0, "name": "invalid"},
|
||||
{"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": 4, "name": "surface descriptor from HTML canvas id"}
|
||||
{"value": 4, "name": "surface descriptor from HTML canvas id"},
|
||||
{"value": 5, "name": "sampler descriptor dummy anisotropic filtering"},
|
||||
{"value": 6, "name": "render pipeline descriptor dummy extension"}
|
||||
]
|
||||
},
|
||||
"texture": {
|
||||
|
|
|
@ -25,11 +25,15 @@ from generator_lib import Generator, run_generator, FileRender
|
|||
class Name:
|
||||
def __init__(self, name, native=False):
|
||||
self.native = native
|
||||
self.name = name
|
||||
if native:
|
||||
self.chunks = [name]
|
||||
else:
|
||||
self.chunks = name.split(' ')
|
||||
|
||||
def get(self):
|
||||
return self.name
|
||||
|
||||
def CamelChunk(self, chunk):
|
||||
return chunk[0].upper() + chunk[1:]
|
||||
|
||||
|
@ -145,18 +149,23 @@ class Record:
|
|||
def __init__(self, name):
|
||||
self.name = Name(name)
|
||||
self.members = []
|
||||
self.has_dawn_object = False
|
||||
self.may_have_dawn_object = False
|
||||
|
||||
def update_metadata(self):
|
||||
def has_dawn_object(member):
|
||||
def may_have_dawn_object(member):
|
||||
if isinstance(member.type, ObjectType):
|
||||
return True
|
||||
elif isinstance(member.type, StructureType):
|
||||
return member.type.has_dawn_object
|
||||
return member.type.may_have_dawn_object
|
||||
else:
|
||||
return False
|
||||
|
||||
self.has_dawn_object = any(has_dawn_object(member) for member in self.members)
|
||||
self.may_have_dawn_object = any(may_have_dawn_object(member) for member in self.members)
|
||||
|
||||
# set may_have_dawn_object to true if the type is chained or extensible. Chained structs
|
||||
# may contain a Dawn object.
|
||||
if isinstance(self, StructureType):
|
||||
self.may_have_dawn_object = self.may_have_dawn_object or self.chained or self.extensible
|
||||
|
||||
class StructureType(Record, Type):
|
||||
def __init__(self, name, json_data):
|
||||
|
|
|
@ -50,7 +50,13 @@ namespace dawn_native {
|
|||
ChainedStruct const * nextInChain = nullptr;
|
||||
{% endif %}
|
||||
{% for member in type.members %}
|
||||
{{as_annotated_frontendType(member)}} {{render_cpp_default_value(member)}};
|
||||
{% set member_declaration = as_annotated_frontendType(member) + render_cpp_default_value(member) %}
|
||||
{% if type.chained and loop.first %}
|
||||
//* Align the first member to ChainedStruct to match the C struct layout.
|
||||
alignas(ChainedStruct) {{member_declaration}};
|
||||
{% else %}
|
||||
{{member_declaration}};
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
{%- set Optional = "Optional" if member.optional else "" -%}
|
||||
{{out}} = provider.Get{{Optional}}Id({{in}});
|
||||
{% elif member.type.category == "structure"%}
|
||||
{%- set Provider = ", provider" if member.type.has_dawn_object else "" -%}
|
||||
{%- set Provider = ", provider" if member.type.may_have_dawn_object else "" -%}
|
||||
{% if member.annotation == "const*const*" %}
|
||||
{{as_cType(member.type.name)}}Serialize(*{{in}}, &{{out}}, buffer{{Provider}});
|
||||
{% else %}
|
||||
|
@ -74,7 +74,7 @@
|
|||
DESERIALIZE_TRY(resolver.Get{{Optional}}FromId({{in}}, &{{out}}));
|
||||
{%- elif member.type.category == "structure" -%}
|
||||
DESERIALIZE_TRY({{as_cType(member.type.name)}}Deserialize(&{{out}}, &{{in}}, buffer, size, allocator
|
||||
{%- if member.type.has_dawn_object -%}
|
||||
{%- if member.type.may_have_dawn_object -%}
|
||||
, resolver
|
||||
{%- endif -%}
|
||||
));
|
||||
|
@ -83,6 +83,15 @@
|
|||
{%- endif -%}
|
||||
{% endmacro %}
|
||||
|
||||
namespace {
|
||||
|
||||
struct WGPUChainedStructTransfer {
|
||||
WGPUSType sType;
|
||||
bool hasNext;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
//* The main [de]serialization macro
|
||||
//* Methods are very similar to structures that have one member corresponding to each arguments.
|
||||
//* This macro takes advantage of the similarity to output [de]serialization code for a record
|
||||
|
@ -95,9 +104,15 @@
|
|||
//* are embedded directly in the structure. Other members are assumed to be in the
|
||||
//* memory directly following the structure in the buffer.
|
||||
struct {{Return}}{{name}}Transfer {
|
||||
static_assert({{[is_cmd, record.extensible, record.chained].count(True)}} <= 1,
|
||||
"Record must be at most one of is_cmd, extensible, and chained.");
|
||||
{% if is_cmd %}
|
||||
//* Start the transfer structure with the command ID, so that casting to WireCmd gives the ID.
|
||||
{{Return}}WireCmd commandId;
|
||||
{% elif record.extensible %}
|
||||
bool hasNextInChain;
|
||||
{% elif record.chained %}
|
||||
WGPUChainedStructTransfer chain;
|
||||
{% endif %}
|
||||
|
||||
//* Value types are directly in the command, objects being replaced with their IDs.
|
||||
|
@ -115,12 +130,23 @@
|
|||
{% endfor %}
|
||||
};
|
||||
|
||||
{% if record.chained %}
|
||||
static_assert(offsetof({{Return}}{{name}}Transfer, chain) == 0, "");
|
||||
{% endif %}
|
||||
|
||||
//* Returns the required transfer size for `record` in addition to the transfer structure.
|
||||
DAWN_DECLARE_UNUSED size_t {{Return}}{{name}}GetExtraRequiredSize(const {{Return}}{{name}}{{Cmd}}& record) {
|
||||
DAWN_UNUSED(record);
|
||||
|
||||
size_t result = 0;
|
||||
|
||||
//* Gather how much space will be needed for the extension chain.
|
||||
{% if record.extensible %}
|
||||
if (record.nextInChain != nullptr) {
|
||||
result += GetChainedStructExtraRequiredSize(record.nextInChain);
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
//* Special handling of const char* that have their length embedded directly in the command
|
||||
{% for member in members if member.length == "strlen" %}
|
||||
{% set memberName = as_varName(member.name) %}
|
||||
|
@ -170,7 +196,7 @@
|
|||
//* and `provider` to serialize objects.
|
||||
DAWN_DECLARE_UNUSED void {{Return}}{{name}}Serialize(const {{Return}}{{name}}{{Cmd}}& record, {{Return}}{{name}}Transfer* transfer,
|
||||
char** buffer
|
||||
{%- if record.has_dawn_object -%}
|
||||
{%- if record.may_have_dawn_object -%}
|
||||
, const ObjectIdProvider& provider
|
||||
{%- endif -%}
|
||||
) {
|
||||
|
@ -187,6 +213,21 @@
|
|||
{{serialize_member(member, "record." + memberName, "transfer->" + memberName)}}
|
||||
{% endfor %}
|
||||
|
||||
{% if record.extensible %}
|
||||
if (record.nextInChain != nullptr) {
|
||||
transfer->hasNextInChain = true;
|
||||
SerializeChainedStruct(record.nextInChain, buffer, provider);
|
||||
} else {
|
||||
transfer->hasNextInChain = false;
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
{% if record.chained %}
|
||||
//* Should be set by the root descriptor's call to SerializeChainedStruct.
|
||||
ASSERT(transfer->chain.sType == {{as_cEnum(types["s type"].name, record.name)}});
|
||||
ASSERT(transfer->chain.hasNext == (record.chain.next != nullptr));
|
||||
{% endif %}
|
||||
|
||||
//* Special handling of const char* that have their length embedded directly in the command
|
||||
{% for member in members if member.length == "strlen" %}
|
||||
{% set memberName = as_varName(member.name) %}
|
||||
|
@ -231,7 +272,7 @@
|
|||
//* Ids to actual objects.
|
||||
DAWN_DECLARE_UNUSED DeserializeResult {{Return}}{{name}}Deserialize({{Return}}{{name}}{{Cmd}}* record, const volatile {{Return}}{{name}}Transfer* transfer,
|
||||
const volatile char** buffer, size_t* size, DeserializeAllocator* allocator
|
||||
{%- if record.has_dawn_object -%}
|
||||
{%- if record.may_have_dawn_object -%}
|
||||
, const ObjectIdResolver& resolver
|
||||
{%- endif -%}
|
||||
) {
|
||||
|
@ -243,10 +284,6 @@
|
|||
ASSERT(transfer->commandId == {{Return}}WireCmd::{{name}});
|
||||
{% endif %}
|
||||
|
||||
{% if record.extensible %}
|
||||
record->nextInChain = nullptr;
|
||||
{% endif %}
|
||||
|
||||
{% if record.derived_method %}
|
||||
record->selfId = transfer->self;
|
||||
{% endif %}
|
||||
|
@ -257,6 +294,21 @@
|
|||
{{deserialize_member(member, "transfer->" + memberName, "record->" + memberName)}}
|
||||
{% endfor %}
|
||||
|
||||
{% if record.extensible %}
|
||||
record->nextInChain = nullptr;
|
||||
if (transfer->hasNextInChain) {
|
||||
DESERIALIZE_TRY(DeserializeChainedStruct(&record->nextInChain, buffer, size, allocator, resolver));
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
{% if record.chained %}
|
||||
//* Should be set by the root descriptor's call to DeserializeChainedStruct.
|
||||
//* Don't check |record->chain.next| matches because it is not set until the
|
||||
//* next iteration inside DeserializeChainedStruct.
|
||||
ASSERT(record->chain.sType == {{as_cEnum(types["s type"].name, record.name)}});
|
||||
ASSERT(record->chain.next == nullptr);
|
||||
{% endif %}
|
||||
|
||||
//* Special handling of const char* that have their length embedded directly in the command
|
||||
{% for member in members if member.length == "strlen" %}
|
||||
{% set memberName = as_varName(member.name) %}
|
||||
|
@ -328,7 +380,7 @@
|
|||
}
|
||||
|
||||
void {{Cmd}}::Serialize(char* buffer
|
||||
{%- if command.has_dawn_object -%}
|
||||
{%- if command.may_have_dawn_object -%}
|
||||
, const ObjectIdProvider& objectIdProvider
|
||||
{%- endif -%}
|
||||
) const {
|
||||
|
@ -336,14 +388,14 @@
|
|||
buffer += sizeof({{Name}}Transfer);
|
||||
|
||||
{{Name}}Serialize(*this, transfer, &buffer
|
||||
{%- if command.has_dawn_object -%}
|
||||
{%- if command.may_have_dawn_object -%}
|
||||
, objectIdProvider
|
||||
{%- endif -%}
|
||||
);
|
||||
}
|
||||
|
||||
DeserializeResult {{Cmd}}::Deserialize(const volatile char** buffer, size_t* size, DeserializeAllocator* allocator
|
||||
{%- if command.has_dawn_object -%}
|
||||
{%- if command.may_have_dawn_object -%}
|
||||
, const ObjectIdResolver& resolver
|
||||
{%- endif -%}
|
||||
) {
|
||||
|
@ -351,7 +403,7 @@
|
|||
DESERIALIZE_TRY(GetPtrFromBuffer(buffer, size, 1, &transfer));
|
||||
|
||||
return {{Name}}Deserialize(this, transfer, buffer, size, allocator
|
||||
{%- if command.has_dawn_object -%}
|
||||
{%- if command.may_have_dawn_object -%}
|
||||
, resolver
|
||||
{%- endif -%}
|
||||
);
|
||||
|
@ -424,6 +476,16 @@ namespace dawn_wire {
|
|||
return DeserializeResult::Success;
|
||||
}
|
||||
|
||||
size_t GetChainedStructExtraRequiredSize(const WGPUChainedStruct* chainedStruct);
|
||||
void SerializeChainedStruct(WGPUChainedStruct const* chainedStruct,
|
||||
char** buffer,
|
||||
const ObjectIdProvider& provider);
|
||||
DeserializeResult DeserializeChainedStruct(const WGPUChainedStruct** outChainNext,
|
||||
const volatile char** buffer,
|
||||
size_t* size,
|
||||
DeserializeAllocator* allocator,
|
||||
const ObjectIdResolver& resolver);
|
||||
|
||||
//* Output structure [de]serialization first because it is used by commands.
|
||||
{% for type in by_category["structure"] %}
|
||||
{% set name = as_cType(type.name) %}
|
||||
|
@ -433,6 +495,116 @@ namespace dawn_wire {
|
|||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
size_t GetChainedStructExtraRequiredSize(const WGPUChainedStruct* chainedStruct) {
|
||||
ASSERT(chainedStruct != nullptr);
|
||||
size_t result = 0;
|
||||
while (chainedStruct != nullptr) {
|
||||
switch (chainedStruct->sType) {
|
||||
{% for sType in types["s type"].values if sType.valid and sType.name.CamelCase() not in client_side_structures %}
|
||||
case {{as_cEnum(types["s type"].name, sType.name)}}: {
|
||||
const auto& typedStruct = *reinterpret_cast<{{as_cType(sType.name)}} const *>(chainedStruct);
|
||||
result += sizeof({{as_cType(sType.name)}}Transfer);
|
||||
result += {{as_cType(sType.name)}}GetExtraRequiredSize(typedStruct);
|
||||
chainedStruct = typedStruct.chain.next;
|
||||
break;
|
||||
}
|
||||
{% endfor %}
|
||||
default:
|
||||
// Invalid enum. Reserve space just for the transfer header (sType and hasNext).
|
||||
// Stop iterating because this is an error.
|
||||
// TODO(crbug.com/dawn/369): Unknown sTypes are silently discarded.
|
||||
ASSERT(chainedStruct->sType == WGPUSType_Invalid);
|
||||
result += sizeof(WGPUChainedStructTransfer);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void SerializeChainedStruct(WGPUChainedStruct const* chainedStruct,
|
||||
char** buffer,
|
||||
const ObjectIdProvider& provider) {
|
||||
ASSERT(chainedStruct != nullptr);
|
||||
ASSERT(buffer != nullptr);
|
||||
do {
|
||||
switch (chainedStruct->sType) {
|
||||
{% for sType in types["s type"].values if sType.valid and sType.name.CamelCase() not in client_side_structures %}
|
||||
{% set CType = as_cType(sType.name) %}
|
||||
case {{as_cEnum(types["s type"].name, sType.name)}}: {
|
||||
|
||||
auto* transfer = reinterpret_cast<{{CType}}Transfer*>(*buffer);
|
||||
transfer->chain.sType = chainedStruct->sType;
|
||||
transfer->chain.hasNext = chainedStruct->next != nullptr;
|
||||
|
||||
*buffer += sizeof({{CType}}Transfer);
|
||||
{{CType}}Serialize(*reinterpret_cast<{{CType}} const*>(chainedStruct), transfer, buffer
|
||||
{%- if types[sType.name.get()].may_have_dawn_object -%}
|
||||
, provider
|
||||
{%- endif -%}
|
||||
);
|
||||
|
||||
chainedStruct = chainedStruct->next;
|
||||
} break;
|
||||
{% endfor %}
|
||||
default: {
|
||||
// Invalid enum. Serialize just the transfer header with Invalid as the sType.
|
||||
// TODO(crbug.com/dawn/369): Unknown sTypes are silently discarded.
|
||||
ASSERT(chainedStruct->sType == WGPUSType_Invalid);
|
||||
WGPUChainedStructTransfer* transfer = reinterpret_cast<WGPUChainedStructTransfer*>(*buffer);
|
||||
transfer->sType = WGPUSType_Invalid;
|
||||
transfer->hasNext = false;
|
||||
|
||||
*buffer += sizeof(WGPUChainedStructTransfer);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} while (chainedStruct != nullptr);
|
||||
}
|
||||
|
||||
DeserializeResult DeserializeChainedStruct(const WGPUChainedStruct** outChainNext,
|
||||
const volatile char** buffer,
|
||||
size_t* size,
|
||||
DeserializeAllocator* allocator,
|
||||
const ObjectIdResolver& resolver) {
|
||||
bool hasNext;
|
||||
do {
|
||||
if (*size < sizeof(WGPUChainedStructTransfer)) {
|
||||
return DeserializeResult::FatalError;
|
||||
}
|
||||
WGPUSType sType =
|
||||
reinterpret_cast<const volatile WGPUChainedStructTransfer*>(*buffer)->sType;
|
||||
switch (sType) {
|
||||
{% for sType in types["s type"].values if sType.valid and sType.name.CamelCase() not in client_side_structures %}
|
||||
{% set CType = as_cType(sType.name) %}
|
||||
case {{as_cEnum(types["s type"].name, sType.name)}}: {
|
||||
const volatile {{CType}}Transfer* transfer = nullptr;
|
||||
DESERIALIZE_TRY(GetPtrFromBuffer(buffer, size, 1, &transfer));
|
||||
|
||||
{{CType}}* outStruct = nullptr;
|
||||
DESERIALIZE_TRY(GetSpace(allocator, sizeof({{CType}}), &outStruct));
|
||||
outStruct->chain.sType = sType;
|
||||
outStruct->chain.next = nullptr;
|
||||
|
||||
*outChainNext = &outStruct->chain;
|
||||
outChainNext = &outStruct->chain.next;
|
||||
|
||||
DESERIALIZE_TRY({{CType}}Deserialize(outStruct, transfer, buffer, size, allocator
|
||||
{%- if types[sType.name.get()].may_have_dawn_object -%}
|
||||
, resolver
|
||||
{%- endif -%}
|
||||
));
|
||||
|
||||
hasNext = transfer->chain.hasNext;
|
||||
} break;
|
||||
{% endfor %}
|
||||
default:
|
||||
return DeserializeResult::FatalError;
|
||||
}
|
||||
} while (hasNext);
|
||||
|
||||
return DeserializeResult::Success;
|
||||
}
|
||||
|
||||
//* Output [de]serialization helpers for commands
|
||||
{% for command in cmd_records["command"] %}
|
||||
{% set name = command.name.CamelCase() %}
|
||||
|
|
|
@ -100,7 +100,7 @@ namespace dawn_wire {
|
|||
//* Serialize the structure and everything it points to into serializeBuffer which must be
|
||||
//* big enough to contain all the data (as queried from GetRequiredSize).
|
||||
void Serialize(char* serializeBuffer
|
||||
{%- if command.has_dawn_object -%}
|
||||
{%- if command.may_have_dawn_object -%}
|
||||
, const ObjectIdProvider& objectIdProvider
|
||||
{%- endif -%}
|
||||
) const;
|
||||
|
@ -113,7 +113,7 @@ namespace dawn_wire {
|
|||
//* - Success if everything went well (yay!)
|
||||
//* - FatalError is something bad happened (buffer too small for example)
|
||||
DeserializeResult Deserialize(const volatile char** buffer, size_t* size, DeserializeAllocator* allocator
|
||||
{%- if command.has_dawn_object -%}
|
||||
{%- if command.may_have_dawn_object -%}
|
||||
, const ObjectIdResolver& resolver
|
||||
{%- endif -%}
|
||||
);
|
||||
|
|
|
@ -27,7 +27,7 @@ namespace dawn_wire { namespace server {
|
|||
bool Server::Handle{{Suffix}}(const volatile char** commands, size_t* size) {
|
||||
{{Suffix}}Cmd cmd;
|
||||
DeserializeResult deserializeResult = cmd.Deserialize(commands, size, &mAllocator
|
||||
{%- if command.has_dawn_object -%}
|
||||
{%- if command.may_have_dawn_object -%}
|
||||
, *this
|
||||
{%- endif -%}
|
||||
);
|
||||
|
|
|
@ -204,7 +204,13 @@ namespace wgpu {
|
|||
ChainedStruct const * nextInChain = nullptr;
|
||||
{% endif %}
|
||||
{% for member in type.members %}
|
||||
{{as_annotated_cppType(member)}}{{render_cpp_default_value(member)}};
|
||||
{% set member_declaration = as_annotated_cppType(member) + render_cpp_default_value(member) %}
|
||||
{% if type.chained and loop.first %}
|
||||
//* Align the first member to ChainedStruct to match the C struct layout.
|
||||
alignas(ChainedStruct) {{member_declaration}};
|
||||
{% else %}
|
||||
{{member_declaration}};
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,208 @@
|
|||
// Copyright 2020 The Dawn Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// 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 "tests/unittests/wire/WireTest.h"
|
||||
|
||||
using namespace testing;
|
||||
using namespace dawn_wire;
|
||||
|
||||
class WireExtensionTests : public WireTest {
|
||||
public:
|
||||
WireExtensionTests() {
|
||||
}
|
||||
~WireExtensionTests() override = default;
|
||||
};
|
||||
|
||||
// Serialize/Deserializes a chained struct correctly.
|
||||
TEST_F(WireExtensionTests, ChainedStruct) {
|
||||
WGPUSamplerDescriptorDummyAnisotropicFiltering clientExt = {};
|
||||
clientExt.chain.sType = WGPUSType_SamplerDescriptorDummyAnisotropicFiltering;
|
||||
clientExt.chain.next = nullptr;
|
||||
clientExt.maxAnisotropy = 3.14;
|
||||
|
||||
WGPUSamplerDescriptor clientDesc = {};
|
||||
clientDesc.nextInChain = &clientExt.chain;
|
||||
clientDesc.label = "sampler with anisotropic filtering";
|
||||
|
||||
wgpuDeviceCreateSampler(device, &clientDesc);
|
||||
EXPECT_CALL(api, DeviceCreateSampler(apiDevice, NotNull()))
|
||||
.WillOnce(Invoke([&](Unused, const WGPUSamplerDescriptor* serverDesc) -> WGPUSampler {
|
||||
EXPECT_STREQ(serverDesc->label, clientDesc.label);
|
||||
|
||||
const auto* ext = reinterpret_cast<const WGPUSamplerDescriptorDummyAnisotropicFiltering*>(
|
||||
serverDesc->nextInChain);
|
||||
EXPECT_EQ(ext->chain.sType, clientExt.chain.sType);
|
||||
EXPECT_EQ(ext->maxAnisotropy, clientExt.maxAnisotropy);
|
||||
|
||||
EXPECT_EQ(ext->chain.next, nullptr);
|
||||
|
||||
return api.GetNewSampler();
|
||||
}));
|
||||
FlushClient();
|
||||
}
|
||||
|
||||
// Serialize/Deserializes multiple chained structs correctly.
|
||||
TEST_F(WireExtensionTests, MutlipleChainedStructs) {
|
||||
WGPUSamplerDescriptorDummyAnisotropicFiltering clientExt2 = {};
|
||||
clientExt2.chain.sType = WGPUSType_SamplerDescriptorDummyAnisotropicFiltering;
|
||||
clientExt2.chain.next = nullptr;
|
||||
clientExt2.maxAnisotropy = 2.71828;
|
||||
|
||||
WGPUSamplerDescriptorDummyAnisotropicFiltering clientExt1 = {};
|
||||
clientExt1.chain.sType = WGPUSType_SamplerDescriptorDummyAnisotropicFiltering;
|
||||
clientExt1.chain.next = &clientExt2.chain;
|
||||
clientExt1.maxAnisotropy = 3.14;
|
||||
|
||||
WGPUSamplerDescriptor clientDesc = {};
|
||||
clientDesc.nextInChain = &clientExt1.chain;
|
||||
clientDesc.label = "sampler with anisotropic filtering";
|
||||
|
||||
wgpuDeviceCreateSampler(device, &clientDesc);
|
||||
EXPECT_CALL(api, DeviceCreateSampler(apiDevice, NotNull()))
|
||||
.WillOnce(Invoke([&](Unused, const WGPUSamplerDescriptor* serverDesc) -> WGPUSampler {
|
||||
EXPECT_STREQ(serverDesc->label, clientDesc.label);
|
||||
|
||||
const auto* ext1 = reinterpret_cast<const WGPUSamplerDescriptorDummyAnisotropicFiltering*>(
|
||||
serverDesc->nextInChain);
|
||||
EXPECT_EQ(ext1->chain.sType, clientExt1.chain.sType);
|
||||
EXPECT_EQ(ext1->maxAnisotropy, clientExt1.maxAnisotropy);
|
||||
|
||||
const auto* ext2 = reinterpret_cast<const WGPUSamplerDescriptorDummyAnisotropicFiltering*>(
|
||||
ext1->chain.next);
|
||||
EXPECT_EQ(ext2->chain.sType, clientExt2.chain.sType);
|
||||
EXPECT_EQ(ext2->maxAnisotropy, clientExt2.maxAnisotropy);
|
||||
|
||||
EXPECT_EQ(ext2->chain.next, nullptr);
|
||||
|
||||
return api.GetNewSampler();
|
||||
}));
|
||||
FlushClient();
|
||||
|
||||
// Swap the order of the chained structs.
|
||||
clientDesc.nextInChain = &clientExt2.chain;
|
||||
clientExt2.chain.next = &clientExt1.chain;
|
||||
clientExt1.chain.next = nullptr;
|
||||
|
||||
wgpuDeviceCreateSampler(device, &clientDesc);
|
||||
EXPECT_CALL(api, DeviceCreateSampler(apiDevice, NotNull()))
|
||||
.WillOnce(Invoke([&](Unused, const WGPUSamplerDescriptor* serverDesc) -> WGPUSampler {
|
||||
EXPECT_STREQ(serverDesc->label, clientDesc.label);
|
||||
|
||||
const auto* ext2 = reinterpret_cast<const WGPUSamplerDescriptorDummyAnisotropicFiltering*>(
|
||||
serverDesc->nextInChain);
|
||||
EXPECT_EQ(ext2->chain.sType, clientExt2.chain.sType);
|
||||
EXPECT_EQ(ext2->maxAnisotropy, clientExt2.maxAnisotropy);
|
||||
|
||||
const auto* ext1 = reinterpret_cast<const WGPUSamplerDescriptorDummyAnisotropicFiltering*>(
|
||||
ext2->chain.next);
|
||||
EXPECT_EQ(ext1->chain.sType, clientExt1.chain.sType);
|
||||
EXPECT_EQ(ext1->maxAnisotropy, clientExt1.maxAnisotropy);
|
||||
|
||||
EXPECT_EQ(ext1->chain.next, nullptr);
|
||||
|
||||
return api.GetNewSampler();
|
||||
}));
|
||||
FlushClient();
|
||||
}
|
||||
|
||||
// Test that a chained struct with Invalid sType is an error.
|
||||
TEST_F(WireExtensionTests, InvalidSType) {
|
||||
WGPUSamplerDescriptorDummyAnisotropicFiltering clientExt = {};
|
||||
clientExt.chain.sType = WGPUSType_Invalid;
|
||||
clientExt.chain.next = nullptr;
|
||||
|
||||
WGPUSamplerDescriptor clientDesc = {};
|
||||
clientDesc.nextInChain = &clientExt.chain;
|
||||
clientDesc.label = "sampler with anisotropic filtering";
|
||||
|
||||
wgpuDeviceCreateSampler(device, &clientDesc);
|
||||
FlushClient(false);
|
||||
}
|
||||
|
||||
// Test that if both an invalid and valid stype are passed on the chain, it is an error.
|
||||
TEST_F(WireExtensionTests, ValidAndInvalidSTypeInChain) {
|
||||
WGPUSamplerDescriptorDummyAnisotropicFiltering clientExt2 = {};
|
||||
clientExt2.chain.sType = WGPUSType_Invalid;
|
||||
clientExt2.chain.next = nullptr;
|
||||
clientExt2.maxAnisotropy = 2.71828;
|
||||
|
||||
WGPUSamplerDescriptorDummyAnisotropicFiltering clientExt1 = {};
|
||||
clientExt1.chain.sType = WGPUSType_SamplerDescriptorDummyAnisotropicFiltering;
|
||||
clientExt1.chain.next = &clientExt2.chain;
|
||||
clientExt1.maxAnisotropy = 3.14;
|
||||
|
||||
WGPUSamplerDescriptor clientDesc = {};
|
||||
clientDesc.nextInChain = &clientExt1.chain;
|
||||
clientDesc.label = "sampler with anisotropic filtering";
|
||||
|
||||
wgpuDeviceCreateSampler(device, &clientDesc);
|
||||
FlushClient(false);
|
||||
|
||||
// Swap the order of the chained structs.
|
||||
clientDesc.nextInChain = &clientExt2.chain;
|
||||
clientExt2.chain.next = &clientExt1.chain;
|
||||
clientExt1.chain.next = nullptr;
|
||||
|
||||
wgpuDeviceCreateSampler(device, &clientDesc);
|
||||
FlushClient(false);
|
||||
}
|
||||
|
||||
// Test that (de)?serializing a chained struct with subdescriptors works.
|
||||
TEST_F(WireExtensionTests, ChainedStructWithSubdescriptor) {
|
||||
WGPUShaderModuleDescriptor shaderModuleDesc = {};
|
||||
|
||||
WGPUShaderModule apiShaderModule1 = api.GetNewShaderModule();
|
||||
WGPUShaderModule shaderModule1 = wgpuDeviceCreateShaderModule(device, &shaderModuleDesc);
|
||||
EXPECT_CALL(api, DeviceCreateShaderModule(apiDevice, _)).WillOnce(Return(apiShaderModule1));
|
||||
FlushClient();
|
||||
|
||||
WGPUShaderModule apiShaderModule2 = api.GetNewShaderModule();
|
||||
WGPUShaderModule shaderModule2 = wgpuDeviceCreateShaderModule(device, &shaderModuleDesc);
|
||||
EXPECT_CALL(api, DeviceCreateShaderModule(apiDevice, _)).WillOnce(Return(apiShaderModule2));
|
||||
FlushClient();
|
||||
|
||||
WGPUProgrammableStageDescriptor extraStageDesc = {};
|
||||
extraStageDesc.module = shaderModule1;
|
||||
extraStageDesc.entryPoint = "my other module";
|
||||
|
||||
WGPURenderPipelineDescriptorDummyExtension clientExt = {};
|
||||
clientExt.chain.sType = WGPUSType_RenderPipelineDescriptorDummyExtension;
|
||||
clientExt.chain.next = nullptr;
|
||||
clientExt.dummyStage = extraStageDesc;
|
||||
|
||||
WGPURenderPipelineDescriptor renderPipelineDesc = {};
|
||||
renderPipelineDesc.nextInChain = &clientExt.chain;
|
||||
renderPipelineDesc.vertexStage.module = shaderModule2;
|
||||
renderPipelineDesc.vertexStage.entryPoint = "my vertex module";
|
||||
|
||||
wgpuDeviceCreateRenderPipeline(device, &renderPipelineDesc);
|
||||
EXPECT_CALL(api, DeviceCreateRenderPipeline(apiDevice, NotNull()))
|
||||
.WillOnce(Invoke([&](Unused,
|
||||
const WGPURenderPipelineDescriptor* serverDesc) -> WGPURenderPipeline {
|
||||
EXPECT_EQ(serverDesc->vertexStage.module, apiShaderModule2);
|
||||
EXPECT_STREQ(serverDesc->vertexStage.entryPoint,
|
||||
renderPipelineDesc.vertexStage.entryPoint);
|
||||
|
||||
const auto* ext = reinterpret_cast<const WGPURenderPipelineDescriptorDummyExtension*>(
|
||||
serverDesc->nextInChain);
|
||||
EXPECT_EQ(ext->chain.sType, clientExt.chain.sType);
|
||||
EXPECT_EQ(ext->dummyStage.module, apiShaderModule1);
|
||||
EXPECT_STREQ(ext->dummyStage.entryPoint, extraStageDesc.entryPoint);
|
||||
|
||||
EXPECT_EQ(ext->chain.next, nullptr);
|
||||
|
||||
return api.GetNewRenderPipeline();
|
||||
}));
|
||||
FlushClient();
|
||||
}
|
Loading…
Reference in New Issue