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:
Austin Eng 2020-04-03 17:37:48 +00:00 committed by Commit Bot service account
parent 2479860e4b
commit 76a8d0b92f
9 changed files with 441 additions and 23 deletions

View File

@ -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",

View File

@ -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": {

View File

@ -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):

View File

@ -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 %}
};

View File

@ -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() %}

View File

@ -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 -%}
);

View File

@ -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 -%}
);

View File

@ -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 %}
};

View File

@ -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();
}