Add FlatRenderPipelineDescriptor for CreateRenderPipelineAsync
This patch implements the struct FlatRenderPipelineDescriptor to save all the pointer members of RenderPipelineDescriptor in CreateRenderPipelineAsync so that the render pipeline descriptor is always valid when the render pipeline is created asynchronously. BUG=dawn:529 TEST=dawn_end2end_tests Change-Id: I99e06581f84c52d484f877ba29e8cc1ca50d68b2 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/63368 Reviewed-by: Austin Eng <enga@chromium.org> Commit-Queue: Jiawei Shao <jiawei.shao@intel.com>
This commit is contained in:
parent
f9d2873ef9
commit
d7ddfb4a91
|
@ -122,6 +122,40 @@ namespace dawn_native {
|
||||||
std::string mMessage;
|
std::string mMessage;
|
||||||
void* mUserdata;
|
void* mUserdata;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MaybeError ValidateLayoutAndSetDefaultLayout(
|
||||||
|
DeviceBase* device,
|
||||||
|
FlatComputePipelineDescriptor* appliedDescriptor) {
|
||||||
|
if (appliedDescriptor->layout == nullptr) {
|
||||||
|
Ref<PipelineLayoutBase> layoutRef;
|
||||||
|
DAWN_TRY_ASSIGN(layoutRef, PipelineLayoutBase::CreateDefault(
|
||||||
|
device, {{SingleShaderStage::Compute,
|
||||||
|
appliedDescriptor->compute.module,
|
||||||
|
appliedDescriptor->compute.entryPoint}}));
|
||||||
|
appliedDescriptor->SetLayout(std::move(layoutRef));
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultOrError<Ref<PipelineLayoutBase>>
|
||||||
|
ValidateLayoutAndGetRenderPipelineDescriptorWithDefaults(
|
||||||
|
DeviceBase* device,
|
||||||
|
const RenderPipelineDescriptor& descriptor,
|
||||||
|
RenderPipelineDescriptor* outDescriptor) {
|
||||||
|
Ref<PipelineLayoutBase> layoutRef;
|
||||||
|
*outDescriptor = descriptor;
|
||||||
|
|
||||||
|
if (descriptor.layout == nullptr) {
|
||||||
|
// Ref will keep the pipeline layout alive until the end of the function where
|
||||||
|
// the pipeline will take another reference.
|
||||||
|
DAWN_TRY_ASSIGN(layoutRef,
|
||||||
|
PipelineLayoutBase::CreateDefault(device, GetStages(&descriptor)));
|
||||||
|
outDescriptor->layout = layoutRef.Get();
|
||||||
|
}
|
||||||
|
|
||||||
|
return layoutRef;
|
||||||
|
}
|
||||||
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
||||||
// DeviceBase
|
// DeviceBase
|
||||||
|
@ -1096,7 +1130,7 @@ namespace dawn_native {
|
||||||
}
|
}
|
||||||
|
|
||||||
FlatComputePipelineDescriptor appliedDescriptor(descriptor);
|
FlatComputePipelineDescriptor appliedDescriptor(descriptor);
|
||||||
DAWN_TRY(ValidateLayoutAndSetDefaultLayout(&appliedDescriptor));
|
DAWN_TRY(ValidateLayoutAndSetDefaultLayout(this, &appliedDescriptor));
|
||||||
|
|
||||||
auto pipelineAndBlueprintFromCache = GetCachedComputePipeline(&appliedDescriptor);
|
auto pipelineAndBlueprintFromCache = GetCachedComputePipeline(&appliedDescriptor);
|
||||||
if (pipelineAndBlueprintFromCache.first.Get() != nullptr) {
|
if (pipelineAndBlueprintFromCache.first.Get() != nullptr) {
|
||||||
|
@ -1120,7 +1154,7 @@ namespace dawn_native {
|
||||||
|
|
||||||
std::unique_ptr<FlatComputePipelineDescriptor> appliedDescriptor =
|
std::unique_ptr<FlatComputePipelineDescriptor> appliedDescriptor =
|
||||||
std::make_unique<FlatComputePipelineDescriptor>(descriptor);
|
std::make_unique<FlatComputePipelineDescriptor>(descriptor);
|
||||||
DAWN_TRY(ValidateLayoutAndSetDefaultLayout(appliedDescriptor.get()));
|
DAWN_TRY(ValidateLayoutAndSetDefaultLayout(this, appliedDescriptor.get()));
|
||||||
|
|
||||||
// Call the callback directly when we can get a cached compute pipeline object.
|
// Call the callback directly when we can get a cached compute pipeline object.
|
||||||
auto pipelineAndBlueprintFromCache = GetCachedComputePipeline(appliedDescriptor.get());
|
auto pipelineAndBlueprintFromCache = GetCachedComputePipeline(appliedDescriptor.get());
|
||||||
|
@ -1140,37 +1174,6 @@ namespace dawn_native {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
MaybeError DeviceBase::ValidateLayoutAndSetDefaultLayout(
|
|
||||||
FlatComputePipelineDescriptor* outDescriptor) {
|
|
||||||
if (outDescriptor->layout == nullptr) {
|
|
||||||
Ref<PipelineLayoutBase> layoutRef;
|
|
||||||
DAWN_TRY_ASSIGN(layoutRef,
|
|
||||||
PipelineLayoutBase::CreateDefault(
|
|
||||||
this, {{SingleShaderStage::Compute, outDescriptor->compute.module,
|
|
||||||
outDescriptor->compute.entryPoint}}));
|
|
||||||
outDescriptor->SetLayout(std::move(layoutRef));
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
ResultOrError<Ref<PipelineLayoutBase>>
|
|
||||||
DeviceBase::ValidateLayoutAndGetRenderPipelineDescriptorWithDefaults(
|
|
||||||
const RenderPipelineDescriptor& descriptor,
|
|
||||||
RenderPipelineDescriptor* outDescriptor) {
|
|
||||||
Ref<PipelineLayoutBase> layoutRef;
|
|
||||||
*outDescriptor = descriptor;
|
|
||||||
|
|
||||||
if (descriptor.layout == nullptr) {
|
|
||||||
// Ref will keep the pipeline layout alive until the end of the function where
|
|
||||||
// the pipeline will take another reference.
|
|
||||||
DAWN_TRY_ASSIGN(layoutRef,
|
|
||||||
PipelineLayoutBase::CreateDefault(this, GetStages(&descriptor)));
|
|
||||||
outDescriptor->layout = layoutRef.Get();
|
|
||||||
}
|
|
||||||
|
|
||||||
return layoutRef;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function is overwritten with the async version on the backends
|
// This function is overwritten with the async version on the backends
|
||||||
// that supports creating compute pipeline asynchronously
|
// that supports creating compute pipeline asynchronously
|
||||||
void DeviceBase::CreateComputePipelineAsyncImpl(
|
void DeviceBase::CreateComputePipelineAsyncImpl(
|
||||||
|
@ -1266,7 +1269,7 @@ namespace dawn_native {
|
||||||
Ref<PipelineLayoutBase> layoutRef;
|
Ref<PipelineLayoutBase> layoutRef;
|
||||||
RenderPipelineDescriptor appliedDescriptor;
|
RenderPipelineDescriptor appliedDescriptor;
|
||||||
DAWN_TRY_ASSIGN(layoutRef, ValidateLayoutAndGetRenderPipelineDescriptorWithDefaults(
|
DAWN_TRY_ASSIGN(layoutRef, ValidateLayoutAndGetRenderPipelineDescriptorWithDefaults(
|
||||||
*descriptor, &appliedDescriptor));
|
this, *descriptor, &appliedDescriptor));
|
||||||
|
|
||||||
auto pipelineAndBlueprintFromCache = GetCachedRenderPipeline(&appliedDescriptor);
|
auto pipelineAndBlueprintFromCache = GetCachedRenderPipeline(&appliedDescriptor);
|
||||||
if (pipelineAndBlueprintFromCache.first.Get() != nullptr) {
|
if (pipelineAndBlueprintFromCache.first.Get() != nullptr) {
|
||||||
|
@ -1290,12 +1293,12 @@ namespace dawn_native {
|
||||||
// Ref will keep the pipeline layout alive until the end of the function where
|
// Ref will keep the pipeline layout alive until the end of the function where
|
||||||
// the pipeline will take another reference.
|
// the pipeline will take another reference.
|
||||||
Ref<PipelineLayoutBase> layoutRef;
|
Ref<PipelineLayoutBase> layoutRef;
|
||||||
RenderPipelineDescriptor appliedDescriptor;
|
RenderPipelineDescriptor descriptorWithPipelineLayout;
|
||||||
DAWN_TRY_ASSIGN(layoutRef, ValidateLayoutAndGetRenderPipelineDescriptorWithDefaults(
|
DAWN_TRY_ASSIGN(layoutRef, ValidateLayoutAndGetRenderPipelineDescriptorWithDefaults(
|
||||||
*descriptor, &appliedDescriptor));
|
this, *descriptor, &descriptorWithPipelineLayout));
|
||||||
|
|
||||||
// Call the callback directly when we can get a cached render pipeline object.
|
// Call the callback directly when we can get a cached render pipeline object.
|
||||||
auto pipelineAndBlueprintFromCache = GetCachedRenderPipeline(&appliedDescriptor);
|
auto pipelineAndBlueprintFromCache = GetCachedRenderPipeline(&descriptorWithPipelineLayout);
|
||||||
if (pipelineAndBlueprintFromCache.first.Get() != nullptr) {
|
if (pipelineAndBlueprintFromCache.first.Get() != nullptr) {
|
||||||
Ref<RenderPipelineBase> result = std::move(pipelineAndBlueprintFromCache.first);
|
Ref<RenderPipelineBase> result = std::move(pipelineAndBlueprintFromCache.first);
|
||||||
callback(WGPUCreatePipelineAsyncStatus_Success,
|
callback(WGPUCreatePipelineAsyncStatus_Success,
|
||||||
|
@ -1304,8 +1307,10 @@ namespace dawn_native {
|
||||||
// Otherwise we will create the pipeline object in CreateRenderPipelineAsyncImpl(),
|
// Otherwise we will create the pipeline object in CreateRenderPipelineAsyncImpl(),
|
||||||
// where the pipeline object may be created asynchronously and the result will be saved
|
// where the pipeline object may be created asynchronously and the result will be saved
|
||||||
// to mCreatePipelineAsyncTracker.
|
// to mCreatePipelineAsyncTracker.
|
||||||
|
FlatRenderPipelineDescriptor appliedFlatDescriptor(&descriptorWithPipelineLayout);
|
||||||
const size_t blueprintHash = pipelineAndBlueprintFromCache.second;
|
const size_t blueprintHash = pipelineAndBlueprintFromCache.second;
|
||||||
CreateRenderPipelineAsyncImpl(&appliedDescriptor, blueprintHash, callback, userdata);
|
CreateRenderPipelineAsyncImpl(&appliedFlatDescriptor, blueprintHash, callback,
|
||||||
|
userdata);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
|
|
|
@ -351,12 +351,6 @@ namespace dawn_native {
|
||||||
|
|
||||||
ResultOrError<Ref<BindGroupLayoutBase>> CreateEmptyBindGroupLayout();
|
ResultOrError<Ref<BindGroupLayoutBase>> CreateEmptyBindGroupLayout();
|
||||||
|
|
||||||
MaybeError ValidateLayoutAndSetDefaultLayout(
|
|
||||||
FlatComputePipelineDescriptor* appliedDescriptor);
|
|
||||||
ResultOrError<Ref<PipelineLayoutBase>>
|
|
||||||
ValidateLayoutAndGetRenderPipelineDescriptorWithDefaults(
|
|
||||||
const RenderPipelineDescriptor& descriptor,
|
|
||||||
RenderPipelineDescriptor* outDescriptor);
|
|
||||||
std::pair<Ref<ComputePipelineBase>, size_t> GetCachedComputePipeline(
|
std::pair<Ref<ComputePipelineBase>, size_t> GetCachedComputePipeline(
|
||||||
const ComputePipelineDescriptor* descriptor);
|
const ComputePipelineDescriptor* descriptor);
|
||||||
std::pair<Ref<RenderPipelineBase>, size_t> GetCachedRenderPipeline(
|
std::pair<Ref<RenderPipelineBase>, size_t> GetCachedRenderPipeline(
|
||||||
|
|
|
@ -363,6 +363,72 @@ namespace dawn_native {
|
||||||
}
|
}
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
||||||
|
// FlatRenderPipelineDescriptor
|
||||||
|
FlatRenderPipelineDescriptor::FlatRenderPipelineDescriptor(
|
||||||
|
const RenderPipelineDescriptor* descriptor)
|
||||||
|
: mLabel(descriptor->label != nullptr ? descriptor->label : ""),
|
||||||
|
mLayout(descriptor->layout),
|
||||||
|
mVertexModule(descriptor->vertex.module),
|
||||||
|
mVertexEntryPoint(descriptor->vertex.entryPoint) {
|
||||||
|
label = mLabel.c_str();
|
||||||
|
|
||||||
|
ASSERT(descriptor->layout != nullptr);
|
||||||
|
layout = mLayout.Get();
|
||||||
|
|
||||||
|
vertex.module = mVertexModule.Get();
|
||||||
|
vertex.entryPoint = mVertexEntryPoint.c_str();
|
||||||
|
vertex.bufferCount = descriptor->vertex.bufferCount;
|
||||||
|
vertex.buffers = mVertexBuffers.data();
|
||||||
|
uint32_t vertexAttributeCount = 0;
|
||||||
|
for (uint32_t vertexBufferIndex = 0; vertexBufferIndex < vertex.bufferCount;
|
||||||
|
++vertexBufferIndex) {
|
||||||
|
const VertexBufferLayout& vertexLayout = descriptor->vertex.buffers[vertexBufferIndex];
|
||||||
|
mVertexBuffers[vertexBufferIndex] = vertexLayout;
|
||||||
|
mVertexBuffers[vertexBufferIndex].attributes = &mVertexAttributes[vertexAttributeCount];
|
||||||
|
for (uint32_t attributeIndex = 0; attributeIndex < vertexLayout.attributeCount;
|
||||||
|
++attributeIndex) {
|
||||||
|
mVertexAttributes[vertexAttributeCount + attributeIndex] =
|
||||||
|
vertexLayout.attributes[attributeIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
vertexAttributeCount += vertexLayout.attributeCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
primitive = descriptor->primitive;
|
||||||
|
|
||||||
|
if (descriptor->depthStencil != nullptr) {
|
||||||
|
mDepthStencilState = *(descriptor->depthStencil);
|
||||||
|
depthStencil = &mDepthStencilState;
|
||||||
|
}
|
||||||
|
|
||||||
|
multisample = descriptor->multisample;
|
||||||
|
|
||||||
|
if (descriptor->fragment != nullptr) {
|
||||||
|
mFragmentModule = descriptor->fragment->module;
|
||||||
|
mFragmentState.module = mFragmentModule.Get();
|
||||||
|
|
||||||
|
mFragmentEntryPoint = descriptor->fragment->entryPoint;
|
||||||
|
mFragmentState.entryPoint = mFragmentEntryPoint.c_str();
|
||||||
|
|
||||||
|
mFragmentState.targetCount = descriptor->fragment->targetCount;
|
||||||
|
|
||||||
|
mFragmentState.targets = mColorTargetStates.data();
|
||||||
|
for (uint32_t colorTargetIndex = 0; colorTargetIndex < mFragmentState.targetCount;
|
||||||
|
++colorTargetIndex) {
|
||||||
|
mColorTargetStates[colorTargetIndex] =
|
||||||
|
descriptor->fragment->targets[colorTargetIndex];
|
||||||
|
|
||||||
|
if (descriptor->fragment->targets[colorTargetIndex].blend != nullptr) {
|
||||||
|
mColorTargetStates[colorTargetIndex].blend = &mBlendStates[colorTargetIndex];
|
||||||
|
mBlendStates[colorTargetIndex] =
|
||||||
|
*(descriptor->fragment->targets[colorTargetIndex].blend);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment = &mFragmentState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Helper functions
|
// Helper functions
|
||||||
size_t IndexFormatSize(wgpu::IndexFormat format) {
|
size_t IndexFormatSize(wgpu::IndexFormat format) {
|
||||||
switch (format) {
|
switch (format) {
|
||||||
|
|
|
@ -29,6 +29,31 @@ namespace dawn_native {
|
||||||
|
|
||||||
class DeviceBase;
|
class DeviceBase;
|
||||||
|
|
||||||
|
// TODO(dawn:529): Use FlatRenderPipelineDescriptor to keep all the members of
|
||||||
|
// RenderPipelineDescriptor (especially the members in pointers) valid when the creation of the
|
||||||
|
// render pipeline is executed asynchronously.
|
||||||
|
struct FlatRenderPipelineDescriptor : public RenderPipelineDescriptor, public NonMovable {
|
||||||
|
public:
|
||||||
|
explicit FlatRenderPipelineDescriptor(const RenderPipelineDescriptor* descriptor);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string mLabel;
|
||||||
|
Ref<PipelineLayoutBase> mLayout;
|
||||||
|
|
||||||
|
Ref<ShaderModuleBase> mVertexModule;
|
||||||
|
std::string mVertexEntryPoint;
|
||||||
|
std::array<VertexBufferLayout, kMaxVertexBuffers> mVertexBuffers;
|
||||||
|
std::array<VertexAttribute, kMaxVertexAttributes> mVertexAttributes;
|
||||||
|
|
||||||
|
FragmentState mFragmentState;
|
||||||
|
Ref<ShaderModuleBase> mFragmentModule;
|
||||||
|
std::string mFragmentEntryPoint;
|
||||||
|
std::array<ColorTargetState, kMaxColorAttachments> mColorTargetStates;
|
||||||
|
std::array<BlendState, kMaxColorAttachments> mBlendStates;
|
||||||
|
|
||||||
|
DepthStencilState mDepthStencilState;
|
||||||
|
};
|
||||||
|
|
||||||
MaybeError ValidateRenderPipelineDescriptor(DeviceBase* device,
|
MaybeError ValidateRenderPipelineDescriptor(DeviceBase* device,
|
||||||
const RenderPipelineDescriptor* descriptor);
|
const RenderPipelineDescriptor* descriptor);
|
||||||
|
|
||||||
|
@ -103,6 +128,7 @@ namespace dawn_native {
|
||||||
private:
|
private:
|
||||||
RenderPipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag);
|
RenderPipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag);
|
||||||
|
|
||||||
|
// TODO(dawn:529): store all the following members in a FlatRenderPipelineDescriptor object
|
||||||
// Vertex state
|
// Vertex state
|
||||||
uint32_t mVertexBufferCount;
|
uint32_t mVertexBufferCount;
|
||||||
ityp::bitset<VertexAttributeLocation, kMaxVertexAttributes> mAttributeLocationsUsed;
|
ityp::bitset<VertexAttributeLocation, kMaxVertexAttributes> mAttributeLocationsUsed;
|
||||||
|
|
|
@ -68,6 +68,23 @@ class CreatePipelineAsyncTest : public DawnTest {
|
||||||
ValidateCreateComputePipelineAsync(&task);
|
ValidateCreateComputePipelineAsync(&task);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DoCreateRenderPipelineAsync(
|
||||||
|
const utils::ComboRenderPipelineDescriptor& renderPipelineDescriptor) {
|
||||||
|
device.CreateRenderPipelineAsync(
|
||||||
|
&renderPipelineDescriptor,
|
||||||
|
[](WGPUCreatePipelineAsyncStatus status, WGPURenderPipeline returnPipeline,
|
||||||
|
const char* message, void* userdata) {
|
||||||
|
EXPECT_EQ(WGPUCreatePipelineAsyncStatus::WGPUCreatePipelineAsyncStatus_Success,
|
||||||
|
status);
|
||||||
|
|
||||||
|
CreatePipelineAsyncTask* task = static_cast<CreatePipelineAsyncTask*>(userdata);
|
||||||
|
task->renderPipeline = wgpu::RenderPipeline::Acquire(returnPipeline);
|
||||||
|
task->isCompleted = true;
|
||||||
|
task->message = message;
|
||||||
|
},
|
||||||
|
&task);
|
||||||
|
}
|
||||||
|
|
||||||
CreatePipelineAsyncTask task;
|
CreatePipelineAsyncTask task;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -193,18 +210,68 @@ TEST_P(CreatePipelineAsyncTest, BasicUseOfCreateRenderPipelineAsync) {
|
||||||
renderPipelineDescriptor.cTargets[0].format = kRenderAttachmentFormat;
|
renderPipelineDescriptor.cTargets[0].format = kRenderAttachmentFormat;
|
||||||
renderPipelineDescriptor.primitive.topology = wgpu::PrimitiveTopology::PointList;
|
renderPipelineDescriptor.primitive.topology = wgpu::PrimitiveTopology::PointList;
|
||||||
|
|
||||||
device.CreateRenderPipelineAsync(
|
DoCreateRenderPipelineAsync(renderPipelineDescriptor);
|
||||||
&renderPipelineDescriptor,
|
|
||||||
[](WGPUCreatePipelineAsyncStatus status, WGPURenderPipeline returnPipeline,
|
|
||||||
const char* message, void* userdata) {
|
|
||||||
EXPECT_EQ(WGPUCreatePipelineAsyncStatus::WGPUCreatePipelineAsyncStatus_Success, status);
|
|
||||||
|
|
||||||
CreatePipelineAsyncTask* task = static_cast<CreatePipelineAsyncTask*>(userdata);
|
wgpu::TextureDescriptor textureDescriptor;
|
||||||
task->renderPipeline = wgpu::RenderPipeline::Acquire(returnPipeline);
|
textureDescriptor.size = {1, 1, 1};
|
||||||
task->isCompleted = true;
|
textureDescriptor.format = kRenderAttachmentFormat;
|
||||||
task->message = message;
|
textureDescriptor.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
|
||||||
},
|
wgpu::Texture outputTexture = device.CreateTexture(&textureDescriptor);
|
||||||
&task);
|
|
||||||
|
utils::ComboRenderPassDescriptor renderPassDescriptor({outputTexture.CreateView()});
|
||||||
|
renderPassDescriptor.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear;
|
||||||
|
renderPassDescriptor.cColorAttachments[0].clearColor = {1.f, 0.f, 0.f, 1.f};
|
||||||
|
|
||||||
|
wgpu::CommandBuffer commands;
|
||||||
|
{
|
||||||
|
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||||
|
wgpu::RenderPassEncoder renderPassEncoder = encoder.BeginRenderPass(&renderPassDescriptor);
|
||||||
|
|
||||||
|
while (!task.isCompleted) {
|
||||||
|
WaitABit();
|
||||||
|
}
|
||||||
|
ASSERT_TRUE(task.message.empty());
|
||||||
|
ASSERT_NE(nullptr, task.renderPipeline.Get());
|
||||||
|
|
||||||
|
renderPassEncoder.SetPipeline(task.renderPipeline);
|
||||||
|
renderPassEncoder.Draw(1);
|
||||||
|
renderPassEncoder.EndPass();
|
||||||
|
commands = encoder.Finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.Submit(1, &commands);
|
||||||
|
|
||||||
|
EXPECT_PIXEL_RGBA8_EQ(RGBA8(0, 255, 0, 255), outputTexture, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the render pipeline created with CreateRenderPipelineAsync() still works when the entry
|
||||||
|
// points are released after the creation of the render pipeline.
|
||||||
|
TEST_P(CreatePipelineAsyncTest, ReleaseEntryPointsAfterCreateRenderPipelineAsync) {
|
||||||
|
constexpr wgpu::TextureFormat kRenderAttachmentFormat = wgpu::TextureFormat::RGBA8Unorm;
|
||||||
|
|
||||||
|
utils::ComboRenderPipelineDescriptor renderPipelineDescriptor;
|
||||||
|
wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"(
|
||||||
|
[[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> {
|
||||||
|
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
||||||
|
})");
|
||||||
|
wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"(
|
||||||
|
[[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32> {
|
||||||
|
return vec4<f32>(0.0, 1.0, 0.0, 1.0);
|
||||||
|
})");
|
||||||
|
renderPipelineDescriptor.vertex.module = vsModule;
|
||||||
|
renderPipelineDescriptor.cFragment.module = fsModule;
|
||||||
|
renderPipelineDescriptor.cTargets[0].format = kRenderAttachmentFormat;
|
||||||
|
renderPipelineDescriptor.primitive.topology = wgpu::PrimitiveTopology::PointList;
|
||||||
|
|
||||||
|
std::string vertexEntryPoint = "main";
|
||||||
|
std::string fragmentEntryPoint = "main";
|
||||||
|
renderPipelineDescriptor.vertex.entryPoint = vertexEntryPoint.c_str();
|
||||||
|
renderPipelineDescriptor.cFragment.entryPoint = fragmentEntryPoint.c_str();
|
||||||
|
|
||||||
|
DoCreateRenderPipelineAsync(renderPipelineDescriptor);
|
||||||
|
|
||||||
|
vertexEntryPoint = "";
|
||||||
|
fragmentEntryPoint = "";
|
||||||
|
|
||||||
wgpu::TextureDescriptor textureDescriptor;
|
wgpu::TextureDescriptor textureDescriptor;
|
||||||
textureDescriptor.size = {1, 1, 1};
|
textureDescriptor.size = {1, 1, 1};
|
||||||
|
@ -438,6 +505,342 @@ TEST_P(CreatePipelineAsyncTest, CreateSamePipelineTwiceAtSameTime) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify calling CreateRenderPipelineAsync() with valid VertexBufferLayouts works on all backends.
|
||||||
|
TEST_P(CreatePipelineAsyncTest, CreateRenderPipelineAsyncWithVertexBufferLayouts) {
|
||||||
|
wgpu::TextureDescriptor textureDescriptor;
|
||||||
|
textureDescriptor.size = {1, 1, 1};
|
||||||
|
textureDescriptor.format = wgpu::TextureFormat::RGBA8Unorm;
|
||||||
|
textureDescriptor.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
|
||||||
|
wgpu::Texture renderTarget = device.CreateTexture(&textureDescriptor);
|
||||||
|
wgpu::TextureView renderTargetView = renderTarget.CreateView();
|
||||||
|
|
||||||
|
utils::ComboRenderPassDescriptor renderPass({renderTargetView});
|
||||||
|
{
|
||||||
|
utils::ComboRenderPipelineDescriptor renderPipelineDescriptor;
|
||||||
|
renderPipelineDescriptor.vertex.module = utils::CreateShaderModule(device, R"(
|
||||||
|
struct VertexInput {
|
||||||
|
[[location(0)]] input0: u32;
|
||||||
|
[[location(1)]] input1: u32;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VertexOutput {
|
||||||
|
[[location(0)]] vertexColorOut: vec4<f32>;
|
||||||
|
[[builtin(position)]] position: vec4<f32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[stage(vertex)]]
|
||||||
|
fn main(vertexInput : VertexInput) -> VertexOutput {
|
||||||
|
var vertexOutput : VertexOutput;
|
||||||
|
vertexOutput.position = vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
||||||
|
if (vertexInput.input0 == 1u && vertexInput.input1 == 2u) {
|
||||||
|
vertexOutput.vertexColorOut = vec4<f32>(0.0, 1.0, 0.0, 1.0);
|
||||||
|
} else {
|
||||||
|
vertexOutput.vertexColorOut = vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
return vertexOutput;
|
||||||
|
})");
|
||||||
|
renderPipelineDescriptor.cFragment.module = utils::CreateShaderModule(device, R"(
|
||||||
|
[[stage(fragment)]]
|
||||||
|
fn main([[location(0)]] fragColorIn : vec4<f32>) -> [[location(0)]] vec4<f32> {
|
||||||
|
return fragColorIn;
|
||||||
|
})");
|
||||||
|
|
||||||
|
renderPipelineDescriptor.primitive.topology = wgpu::PrimitiveTopology::PointList;
|
||||||
|
renderPipelineDescriptor.cFragment.targetCount = 1;
|
||||||
|
renderPipelineDescriptor.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm;
|
||||||
|
|
||||||
|
// Create a render pipeline with two VertexBufferLayouts
|
||||||
|
renderPipelineDescriptor.vertex.buffers = renderPipelineDescriptor.cBuffers.data();
|
||||||
|
renderPipelineDescriptor.vertex.bufferCount = 2;
|
||||||
|
renderPipelineDescriptor.cBuffers[0].attributeCount = 1;
|
||||||
|
renderPipelineDescriptor.cBuffers[0].attributes = &renderPipelineDescriptor.cAttributes[0];
|
||||||
|
renderPipelineDescriptor.cAttributes[0].format = wgpu::VertexFormat::Uint32;
|
||||||
|
renderPipelineDescriptor.cAttributes[0].shaderLocation = 0;
|
||||||
|
renderPipelineDescriptor.cBuffers[1].attributeCount = 1;
|
||||||
|
renderPipelineDescriptor.cBuffers[1].attributes = &renderPipelineDescriptor.cAttributes[1];
|
||||||
|
renderPipelineDescriptor.cAttributes[1].format = wgpu::VertexFormat::Uint32;
|
||||||
|
renderPipelineDescriptor.cAttributes[1].shaderLocation = 1;
|
||||||
|
|
||||||
|
DoCreateRenderPipelineAsync(renderPipelineDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
wgpu::Buffer vertexBuffer1 = utils::CreateBufferFromData(
|
||||||
|
device, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Vertex, {1u});
|
||||||
|
wgpu::Buffer vertexBuffer2 = utils::CreateBufferFromData(
|
||||||
|
device, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Vertex, {2u});
|
||||||
|
|
||||||
|
// Do the draw call with the render pipeline
|
||||||
|
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||||
|
{
|
||||||
|
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
|
||||||
|
|
||||||
|
while (!task.isCompleted) {
|
||||||
|
WaitABit();
|
||||||
|
}
|
||||||
|
ASSERT_TRUE(task.message.empty());
|
||||||
|
ASSERT_NE(nullptr, task.renderPipeline.Get());
|
||||||
|
pass.SetPipeline(task.renderPipeline);
|
||||||
|
|
||||||
|
pass.SetVertexBuffer(0, vertexBuffer1);
|
||||||
|
pass.SetVertexBuffer(1, vertexBuffer2);
|
||||||
|
pass.Draw(1);
|
||||||
|
pass.EndPass();
|
||||||
|
}
|
||||||
|
|
||||||
|
wgpu::CommandBuffer commands = encoder.Finish();
|
||||||
|
queue.Submit(1, &commands);
|
||||||
|
|
||||||
|
// The color attachment will have the expected color when the vertex attribute values are
|
||||||
|
// fetched correctly.
|
||||||
|
EXPECT_PIXEL_RGBA8_EQ(RGBA8(0, 255, 0, 255), renderTarget, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify calling CreateRenderPipelineAsync() with valid depthStencilState works on all backends.
|
||||||
|
TEST_P(CreatePipelineAsyncTest, CreateRenderPipelineAsyncWithDepthStencilState) {
|
||||||
|
wgpu::TextureDescriptor textureDescriptor;
|
||||||
|
textureDescriptor.size = {1, 1, 1};
|
||||||
|
textureDescriptor.format = wgpu::TextureFormat::RGBA8Unorm;
|
||||||
|
textureDescriptor.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
|
||||||
|
wgpu::Texture renderTarget = device.CreateTexture(&textureDescriptor);
|
||||||
|
wgpu::TextureView renderTargetView = renderTarget.CreateView();
|
||||||
|
|
||||||
|
textureDescriptor.format = wgpu::TextureFormat::Depth24PlusStencil8;
|
||||||
|
wgpu::Texture depthStencilTarget = device.CreateTexture(&textureDescriptor);
|
||||||
|
wgpu::TextureView depthStencilView = depthStencilTarget.CreateView();
|
||||||
|
|
||||||
|
// Clear the color attachment to green and the stencil aspect of the depth stencil attachment
|
||||||
|
// to 0.
|
||||||
|
utils::ComboRenderPassDescriptor renderPass({renderTargetView}, depthStencilView);
|
||||||
|
renderPass.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear;
|
||||||
|
renderPass.cColorAttachments[0].clearColor = {0.0, 1.0, 0.0, 1.0};
|
||||||
|
renderPass.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Clear;
|
||||||
|
renderPass.cDepthStencilAttachmentInfo.clearStencil = 0u;
|
||||||
|
|
||||||
|
wgpu::RenderPipeline pipeline;
|
||||||
|
{
|
||||||
|
utils::ComboRenderPipelineDescriptor renderPipelineDescriptor;
|
||||||
|
renderPipelineDescriptor.vertex.module = utils::CreateShaderModule(device, R"(
|
||||||
|
[[stage(vertex)]]
|
||||||
|
fn main() -> [[builtin(position)]] vec4<f32> {
|
||||||
|
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
||||||
|
})");
|
||||||
|
renderPipelineDescriptor.cFragment.module = utils::CreateShaderModule(device, R"(
|
||||||
|
[[stage(fragment)]]
|
||||||
|
fn main() -> [[location(0)]] vec4<f32> {
|
||||||
|
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
||||||
|
})");
|
||||||
|
|
||||||
|
renderPipelineDescriptor.primitive.topology = wgpu::PrimitiveTopology::PointList;
|
||||||
|
renderPipelineDescriptor.cFragment.targetCount = 1;
|
||||||
|
renderPipelineDescriptor.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm;
|
||||||
|
|
||||||
|
// Create a render pipeline with stencil compare function "Equal".
|
||||||
|
renderPipelineDescriptor.depthStencil = &renderPipelineDescriptor.cDepthStencil;
|
||||||
|
renderPipelineDescriptor.cDepthStencil.stencilFront.compare = wgpu::CompareFunction::Equal;
|
||||||
|
|
||||||
|
DoCreateRenderPipelineAsync(renderPipelineDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||||
|
{
|
||||||
|
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
|
||||||
|
|
||||||
|
while (!task.isCompleted) {
|
||||||
|
WaitABit();
|
||||||
|
}
|
||||||
|
ASSERT_TRUE(task.message.empty());
|
||||||
|
ASSERT_NE(nullptr, task.renderPipeline.Get());
|
||||||
|
pass.SetPipeline(task.renderPipeline);
|
||||||
|
|
||||||
|
// The stencil reference is set to 1, so there should be no pixel that can pass the stencil
|
||||||
|
// test.
|
||||||
|
pass.SetStencilReference(1);
|
||||||
|
|
||||||
|
pass.Draw(1);
|
||||||
|
pass.EndPass();
|
||||||
|
}
|
||||||
|
|
||||||
|
wgpu::CommandBuffer commands = encoder.Finish();
|
||||||
|
queue.Submit(1, &commands);
|
||||||
|
|
||||||
|
// The color in the color attachment should not be changed after the draw call as no pixel can
|
||||||
|
// pass the stencil test.
|
||||||
|
EXPECT_PIXEL_RGBA8_EQ(RGBA8(0, 255, 0, 255), renderTarget, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify calling CreateRenderPipelineAsync() with multisample.Count > 1 works on all backends.
|
||||||
|
TEST_P(CreatePipelineAsyncTest, CreateRenderPipelineWithMultisampleState) {
|
||||||
|
wgpu::TextureDescriptor textureDescriptor;
|
||||||
|
textureDescriptor.size = {1, 1, 1};
|
||||||
|
textureDescriptor.format = wgpu::TextureFormat::RGBA8Unorm;
|
||||||
|
textureDescriptor.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
|
||||||
|
wgpu::Texture resolveTarget = device.CreateTexture(&textureDescriptor);
|
||||||
|
wgpu::TextureView resolveTargetView = resolveTarget.CreateView();
|
||||||
|
|
||||||
|
textureDescriptor.sampleCount = 4;
|
||||||
|
wgpu::Texture renderTarget = device.CreateTexture(&textureDescriptor);
|
||||||
|
wgpu::TextureView renderTargetView = renderTarget.CreateView();
|
||||||
|
|
||||||
|
// Set the multi-sampled render target, its resolve target to render pass and clear color to
|
||||||
|
// (1, 0, 0, 1).
|
||||||
|
utils::ComboRenderPassDescriptor renderPass({renderTargetView});
|
||||||
|
renderPass.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear;
|
||||||
|
renderPass.cColorAttachments[0].clearColor = {1.0, 0.0, 0.0, 1.0};
|
||||||
|
renderPass.cColorAttachments[0].resolveTarget = resolveTargetView;
|
||||||
|
|
||||||
|
wgpu::RenderPipeline pipeline;
|
||||||
|
{
|
||||||
|
utils::ComboRenderPipelineDescriptor renderPipelineDescriptor;
|
||||||
|
renderPipelineDescriptor.vertex.module = utils::CreateShaderModule(device, R"(
|
||||||
|
[[stage(vertex)]]
|
||||||
|
fn main() -> [[builtin(position)]] vec4<f32> {
|
||||||
|
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
||||||
|
})");
|
||||||
|
renderPipelineDescriptor.cFragment.module = utils::CreateShaderModule(device, R"(
|
||||||
|
[[stage(fragment)]]
|
||||||
|
fn main() -> [[location(0)]] vec4<f32> {
|
||||||
|
return vec4<f32>(0.0, 1.0, 0.0, 1.0);
|
||||||
|
})");
|
||||||
|
|
||||||
|
renderPipelineDescriptor.primitive.topology = wgpu::PrimitiveTopology::PointList;
|
||||||
|
renderPipelineDescriptor.cFragment.targetCount = 1;
|
||||||
|
renderPipelineDescriptor.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm;
|
||||||
|
|
||||||
|
// Create a render pipeline with multisample.count == 4.
|
||||||
|
renderPipelineDescriptor.multisample.count = 4;
|
||||||
|
|
||||||
|
DoCreateRenderPipelineAsync(renderPipelineDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||||
|
{
|
||||||
|
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
|
||||||
|
|
||||||
|
while (!task.isCompleted) {
|
||||||
|
WaitABit();
|
||||||
|
}
|
||||||
|
ASSERT_TRUE(task.message.empty());
|
||||||
|
ASSERT_NE(nullptr, task.renderPipeline.Get());
|
||||||
|
pass.SetPipeline(task.renderPipeline);
|
||||||
|
|
||||||
|
pass.Draw(6);
|
||||||
|
pass.EndPass();
|
||||||
|
}
|
||||||
|
|
||||||
|
wgpu::CommandBuffer commands = encoder.Finish();
|
||||||
|
queue.Submit(1, &commands);
|
||||||
|
|
||||||
|
// The color in resolveTarget should be the expected color (0, 1, 0, 1).
|
||||||
|
EXPECT_PIXEL_RGBA8_EQ(RGBA8(0, 255, 0, 255), resolveTarget, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify calling CreateRenderPipelineAsync() with valid BlendState works on all backends.
|
||||||
|
TEST_P(CreatePipelineAsyncTest, CreateRenderPipelineAsyncWithBlendState) {
|
||||||
|
DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("disable_indexed_draw_buffers"));
|
||||||
|
|
||||||
|
std::array<wgpu::Texture, 2> renderTargets;
|
||||||
|
std::array<wgpu::TextureView, 2> renderTargetViews;
|
||||||
|
|
||||||
|
{
|
||||||
|
wgpu::TextureDescriptor textureDescriptor;
|
||||||
|
textureDescriptor.size = {1, 1, 1};
|
||||||
|
textureDescriptor.format = wgpu::TextureFormat::RGBA8Unorm;
|
||||||
|
textureDescriptor.usage =
|
||||||
|
wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < renderTargets.size(); ++i) {
|
||||||
|
renderTargets[i] = device.CreateTexture(&textureDescriptor);
|
||||||
|
renderTargetViews[i] = renderTargets[i].CreateView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare two color attachments
|
||||||
|
utils::ComboRenderPassDescriptor renderPass({renderTargetViews[0], renderTargetViews[1]});
|
||||||
|
renderPass.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear;
|
||||||
|
renderPass.cColorAttachments[0].clearColor = {0.2, 0.0, 0.0, 0.2};
|
||||||
|
renderPass.cColorAttachments[1].loadOp = wgpu::LoadOp::Clear;
|
||||||
|
renderPass.cColorAttachments[1].clearColor = {0.0, 0.2, 0.0, 0.2};
|
||||||
|
|
||||||
|
{
|
||||||
|
utils::ComboRenderPipelineDescriptor renderPipelineDescriptor;
|
||||||
|
renderPipelineDescriptor.vertex.module = utils::CreateShaderModule(device, R"(
|
||||||
|
[[stage(vertex)]]
|
||||||
|
fn main() -> [[builtin(position)]] vec4<f32> {
|
||||||
|
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
||||||
|
})");
|
||||||
|
renderPipelineDescriptor.cFragment.module = utils::CreateShaderModule(device, R"(
|
||||||
|
struct FragmentOut {
|
||||||
|
[[location(0)]] fragColor0 : vec4<f32>;
|
||||||
|
[[location(1)]] fragColor1 : vec4<f32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[stage(fragment)]] fn main() -> FragmentOut {
|
||||||
|
var output : FragmentOut;
|
||||||
|
output.fragColor0 = vec4<f32>(0.4, 0.0, 0.0, 0.4);
|
||||||
|
output.fragColor1 = vec4<f32>(0.0, 1.0, 0.0, 1.0);
|
||||||
|
return output;
|
||||||
|
})");
|
||||||
|
|
||||||
|
renderPipelineDescriptor.primitive.topology = wgpu::PrimitiveTopology::PointList;
|
||||||
|
|
||||||
|
// Create a render pipeline with blending states
|
||||||
|
renderPipelineDescriptor.cFragment.targetCount = renderTargets.size();
|
||||||
|
|
||||||
|
// The blend operation for the first render target is "add".
|
||||||
|
wgpu::BlendComponent blendComponent0;
|
||||||
|
blendComponent0.operation = wgpu::BlendOperation::Add;
|
||||||
|
blendComponent0.srcFactor = wgpu::BlendFactor::One;
|
||||||
|
blendComponent0.dstFactor = wgpu::BlendFactor::One;
|
||||||
|
|
||||||
|
wgpu::BlendState blend0;
|
||||||
|
blend0.color = blendComponent0;
|
||||||
|
blend0.alpha = blendComponent0;
|
||||||
|
|
||||||
|
// The blend operation for the first render target is "subtract".
|
||||||
|
wgpu::BlendComponent blendComponent1;
|
||||||
|
blendComponent1.operation = wgpu::BlendOperation::Subtract;
|
||||||
|
blendComponent1.srcFactor = wgpu::BlendFactor::One;
|
||||||
|
blendComponent1.dstFactor = wgpu::BlendFactor::One;
|
||||||
|
|
||||||
|
wgpu::BlendState blend1;
|
||||||
|
blend1.color = blendComponent1;
|
||||||
|
blend1.alpha = blendComponent1;
|
||||||
|
|
||||||
|
renderPipelineDescriptor.cTargets[0].blend = &blend0;
|
||||||
|
renderPipelineDescriptor.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm;
|
||||||
|
renderPipelineDescriptor.cTargets[1].blend = &blend1;
|
||||||
|
renderPipelineDescriptor.cTargets[1].format = wgpu::TextureFormat::RGBA8Unorm;
|
||||||
|
|
||||||
|
DoCreateRenderPipelineAsync(renderPipelineDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||||
|
{
|
||||||
|
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
|
||||||
|
|
||||||
|
while (!task.isCompleted) {
|
||||||
|
WaitABit();
|
||||||
|
}
|
||||||
|
ASSERT_TRUE(task.message.empty());
|
||||||
|
ASSERT_NE(nullptr, task.renderPipeline.Get());
|
||||||
|
pass.SetPipeline(task.renderPipeline);
|
||||||
|
|
||||||
|
pass.Draw(1);
|
||||||
|
pass.EndPass();
|
||||||
|
}
|
||||||
|
|
||||||
|
wgpu::CommandBuffer commands = encoder.Finish();
|
||||||
|
queue.Submit(1, &commands);
|
||||||
|
|
||||||
|
// When the blend states are all set correctly, the color of renderTargets[0] should be
|
||||||
|
// (0.6, 0, 0, 0.6) = colorAttachment0.clearColor + (0.4, 0.0, 0.0, 0.4), and the color of
|
||||||
|
// renderTargets[1] should be (0.8, 0, 0, 0.8) = (1, 0, 0, 1) - colorAttachment1.clearColor.
|
||||||
|
RGBA8 expected0 = {153, 0, 0, 153};
|
||||||
|
RGBA8 expected1 = {0, 204, 0, 204};
|
||||||
|
EXPECT_PIXEL_RGBA8_EQ(expected0, renderTargets[0], 0, 0);
|
||||||
|
EXPECT_PIXEL_RGBA8_EQ(expected1, renderTargets[1], 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
DAWN_INSTANTIATE_TEST(CreatePipelineAsyncTest,
|
DAWN_INSTANTIATE_TEST(CreatePipelineAsyncTest,
|
||||||
D3D12Backend(),
|
D3D12Backend(),
|
||||||
MetalBackend(),
|
MetalBackend(),
|
||||||
|
|
Loading…
Reference in New Issue