Wire: Add support from optional struct/integer const*

This will allow implementing the following part of the WebGPU IDL using
a nullable pointer instead of an extra hasDepthStencilState boolean:

partial interface GPURenderPipelineDescriptor {
    GPUDepthStencilStateDescriptor? depthStencilState;
};

BUG=dawn:102

Change-Id: Iae709831ad857fcef073f18753ab39567a8797da
Reviewed-on: https://dawn-review.googlesource.com/c/4500
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Reviewed-by: Yunchao He <yunchao.he@intel.com>
diff --git a/dawn.json b/dawn.json
index 84c8dbe..b72217f 100644
--- a/dawn.json
+++ b/dawn.json
@@ -863,7 +863,7 @@
             {"name": "primitive topology", "type": "primitive topology"},
             {"name": "attachments state", "type": "attachments state descriptor", "annotation": "const*"},
             {"name": "sample count", "type": "uint32_t"},
-            {"name": "depth stencil state", "type": "depth stencil state descriptor", "annotation": "const*"},
+            {"name": "depth stencil state", "type": "depth stencil state descriptor", "annotation": "const*", "optional": true},
             {"name": "num blend states", "type": "uint32_t"},
             {"name": "blend states", "type": "blend state descriptor", "annotation": "const*", "length": "num blend states"}
         ]
diff --git a/generator/templates/dawn_wire/WireCmd.cpp b/generator/templates/dawn_wire/WireCmd.cpp
index 75ac7f4..0b3f95c 100644
--- a/generator/templates/dawn_wire/WireCmd.cpp
+++ b/generator/templates/dawn_wire/WireCmd.cpp
@@ -105,6 +105,10 @@
         {% for member in members if member.length == "strlen" %}
             size_t {{as_varName(member.name)}}Strlen;
         {% endfor %}
+
+        {% for member in members if member.annotation != "value" and member.type.category != "object" %}
+            bool has_{{as_varName(member.name)}};
+        {% endfor %}
     };
 
     //* Returns the required transfer size for `record` in addition to the transfer structure.
@@ -120,6 +124,9 @@
 
         //* Gather how much space will be needed for pointer members.
         {% for member in members if member.annotation != "value" and member.length != "strlen" %}
+            {% if member.type.category != "object" and member.optional %}
+                if (record.{{as_varName(member.name)}} != nullptr)
+            {% endif %}
             {
                 size_t memberLength = {{member_length(member, "record.")}};
                 result += memberLength * {{member_transfer_sizeof(member)}};
@@ -176,6 +183,12 @@
         //* Allocate space and write the non-value arguments in it.
         {% for member in members if member.annotation != "value" and member.length != "strlen" %}
             {% set memberName = as_varName(member.name) %}
+
+            {% if member.type.category != "object" and member.optional %}
+                bool has_{{memberName}} = record.{{memberName}} != nullptr;
+                transfer->has_{{memberName}} = has_{{memberName}};
+                if (has_{{memberName}})
+            {% endif %}
             {
                 size_t memberLength = {{member_length(member, "record.")}};
                 auto memberBuffer = reinterpret_cast<{{member_transfer_type(member)}}*>(*buffer);
@@ -277,6 +290,12 @@
         //* Get extra buffer data, and copy pointed to values in extra allocated space.
         {% for member in members if member.annotation != "value" and member.length != "strlen" %}
             {% set memberName = as_varName(member.name) %}
+
+            {% if member.type.category != "object" and member.optional %}
+                bool has_{{memberName}} = transfer->has_{{memberName}};
+                record->{{memberName}} = nullptr;
+                if (has_{{memberName}})
+            {% endif %}
             {
                 size_t memberLength = {{member_length(member, "record->")}};
                 auto memberBuffer = reinterpret_cast<const {{member_transfer_type(member)}}*>(buffer);
diff --git a/src/tests/unittests/WireTests.cpp b/src/tests/unittests/WireTests.cpp
index ccabc40..27af06f 100644
--- a/src/tests/unittests/WireTests.cpp
+++ b/src/tests/unittests/WireTests.cpp
@@ -439,6 +439,141 @@
         FlushClient();
 }
 
+// Test that the wire is able to send optional pointers to structures
+TEST_F(WireTests, OptionalStructPointer) {
+    // Create shader module
+    dawnShaderModuleDescriptor vertexDescriptor;
+    vertexDescriptor.nextInChain = nullptr;
+    vertexDescriptor.codeSize = 0;
+    dawnShaderModule vsModule = dawnDeviceCreateShaderModule(device, &vertexDescriptor);
+    dawnShaderModule apiVsModule = api.GetNewShaderModule();
+    EXPECT_CALL(api, DeviceCreateShaderModule(apiDevice, _))
+        .WillOnce(Return(apiVsModule));
+
+    // Create the blend state descriptor
+    dawnBlendDescriptor blendDescriptor;
+    blendDescriptor.operation = DAWN_BLEND_OPERATION_ADD;
+    blendDescriptor.srcFactor = DAWN_BLEND_FACTOR_ONE;
+    blendDescriptor.dstFactor = DAWN_BLEND_FACTOR_ONE;
+    dawnBlendStateDescriptor blendStateDescriptor;
+    blendStateDescriptor.nextInChain = nullptr;
+    blendStateDescriptor.alphaBlend = blendDescriptor;
+    blendStateDescriptor.colorBlend = blendDescriptor;
+    blendStateDescriptor.colorWriteMask = DAWN_COLOR_WRITE_MASK_ALL;
+
+    // Create the input state
+    dawnInputStateBuilder inputStateBuilder = dawnDeviceCreateInputStateBuilder(device);
+    dawnInputStateBuilder apiInputStateBuilder = api.GetNewInputStateBuilder();
+    EXPECT_CALL(api, DeviceCreateInputStateBuilder(apiDevice))
+        .WillOnce(Return(apiInputStateBuilder));
+
+    dawnInputState inputState = dawnInputStateBuilderGetResult(inputStateBuilder);
+    dawnInputState apiInputState = api.GetNewInputState();
+    EXPECT_CALL(api, InputStateBuilderGetResult(apiInputStateBuilder))
+        .WillOnce(Return(apiInputState));
+
+    // Create the depth-stencil state
+    dawnStencilStateFaceDescriptor stencilFace;
+    stencilFace.compare = DAWN_COMPARE_FUNCTION_ALWAYS;
+    stencilFace.failOp = DAWN_STENCIL_OPERATION_KEEP;
+    stencilFace.depthFailOp = DAWN_STENCIL_OPERATION_KEEP;
+    stencilFace.passOp = DAWN_STENCIL_OPERATION_KEEP;
+
+    dawnDepthStencilStateDescriptor depthStencilState;
+    depthStencilState.nextInChain = nullptr;
+    depthStencilState.depthWriteEnabled = false;
+    depthStencilState.depthCompare = DAWN_COMPARE_FUNCTION_ALWAYS;
+    depthStencilState.stencilBack = stencilFace;
+    depthStencilState.stencilFront = stencilFace;
+    depthStencilState.stencilReadMask = 0xff;
+    depthStencilState.stencilWriteMask = 0xff;
+
+    // Create the pipeline layout
+    dawnPipelineLayoutDescriptor layoutDescriptor;
+    layoutDescriptor.nextInChain = nullptr;
+    layoutDescriptor.numBindGroupLayouts = 0;
+    layoutDescriptor.bindGroupLayouts = nullptr;
+    dawnPipelineLayout layout = dawnDeviceCreatePipelineLayout(device, &layoutDescriptor);
+    dawnPipelineLayout apiLayout = api.GetNewPipelineLayout();
+    EXPECT_CALL(api, DeviceCreatePipelineLayout(apiDevice, _))
+        .WillOnce(Return(apiLayout));
+
+    // Create pipeline
+    dawnRenderPipelineDescriptor pipelineDescriptor;
+    pipelineDescriptor.nextInChain = nullptr;
+
+    dawnPipelineStageDescriptor vertexStage;
+    vertexStage.nextInChain = nullptr;
+    vertexStage.module = vsModule;
+    vertexStage.entryPoint = "main";
+    pipelineDescriptor.vertexStage = &vertexStage;
+
+    dawnPipelineStageDescriptor fragmentStage;
+    fragmentStage.nextInChain = nullptr;
+    fragmentStage.module = vsModule;
+    fragmentStage.entryPoint = "main";
+    pipelineDescriptor.fragmentStage = &fragmentStage;
+
+    dawnAttachmentsStateDescriptor attachmentsState;
+    attachmentsState.nextInChain = nullptr;
+    attachmentsState.numColorAttachments = 1;
+    dawnAttachmentDescriptor colorAttachment = {nullptr, DAWN_TEXTURE_FORMAT_R8_G8_B8_A8_UNORM};
+    dawnAttachmentDescriptor* colorAttachmentPtr[] = {&colorAttachment};
+    attachmentsState.colorAttachments = colorAttachmentPtr;
+    attachmentsState.hasDepthStencilAttachment = false;
+    // Even with hasDepthStencilAttachment = false, depthStencilAttachment must point to valid
+    // data because we don't have optional substructures yet.
+    attachmentsState.depthStencilAttachment = &colorAttachment;
+    pipelineDescriptor.attachmentsState = &attachmentsState;
+
+    pipelineDescriptor.numBlendStates = 1;
+    pipelineDescriptor.blendStates = &blendStateDescriptor;
+
+    pipelineDescriptor.sampleCount = 1;
+    pipelineDescriptor.layout = layout;
+    pipelineDescriptor.inputState = inputState;
+    pipelineDescriptor.indexFormat = DAWN_INDEX_FORMAT_UINT32;
+    pipelineDescriptor.primitiveTopology = DAWN_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
+
+    // First case: depthStencilState is not null.
+    pipelineDescriptor.depthStencilState = &depthStencilState;
+    dawnDeviceCreateRenderPipeline(device, &pipelineDescriptor);
+	EXPECT_CALL(api, DeviceCreateRenderPipeline(apiDevice, MatchesLambda([](const dawnRenderPipelineDescriptor* desc) -> bool {
+        return desc->depthStencilState != nullptr &&
+            desc->depthStencilState->nextInChain == nullptr &&
+            desc->depthStencilState->depthWriteEnabled == false &&
+            desc->depthStencilState->depthCompare == DAWN_COMPARE_FUNCTION_ALWAYS &&
+            desc->depthStencilState->stencilBack.compare == DAWN_COMPARE_FUNCTION_ALWAYS &&
+            desc->depthStencilState->stencilBack.failOp == DAWN_STENCIL_OPERATION_KEEP &&
+            desc->depthStencilState->stencilBack.depthFailOp == DAWN_STENCIL_OPERATION_KEEP &&
+            desc->depthStencilState->stencilBack.passOp == DAWN_STENCIL_OPERATION_KEEP &&
+            desc->depthStencilState->stencilFront.compare == DAWN_COMPARE_FUNCTION_ALWAYS &&
+            desc->depthStencilState->stencilFront.failOp == DAWN_STENCIL_OPERATION_KEEP &&
+            desc->depthStencilState->stencilFront.depthFailOp == DAWN_STENCIL_OPERATION_KEEP &&
+            desc->depthStencilState->stencilFront.passOp == DAWN_STENCIL_OPERATION_KEEP &&
+            desc->depthStencilState->stencilReadMask == 0xff &&
+            desc->depthStencilState->stencilWriteMask == 0xff;
+    })))
+        .WillOnce(Return(nullptr));
+
+    FlushClient();
+
+    // Second case: depthStencilState is null.
+    pipelineDescriptor.depthStencilState = nullptr;
+    dawnDeviceCreateRenderPipeline(device, &pipelineDescriptor);
+	EXPECT_CALL(api, DeviceCreateRenderPipeline(apiDevice, MatchesLambda([](const dawnRenderPipelineDescriptor* desc) -> bool {
+        return desc->depthStencilState == nullptr;
+    })))
+        .WillOnce(Return(nullptr));
+
+    EXPECT_CALL(api, ShaderModuleRelease(apiVsModule));
+    EXPECT_CALL(api, InputStateBuilderRelease(apiInputStateBuilder));
+    EXPECT_CALL(api, InputStateRelease(apiInputState));
+    EXPECT_CALL(api, PipelineLayoutRelease(apiLayout));
+
+    FlushClient();
+}
+
 // Test that the wire is able to send objects as value arguments
 TEST_F(WireTests, ObjectAsValueArgument) {
     // Create a RenderPassDescriptor