blob: cc9e1c76dedebfed397ecc86d4b197b748af35ce [file] [log] [blame] [edit]
// Copyright 2023 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "dawn/tests/white_box/SharedTextureMemoryTests.h"
#include <memory>
#include <utility>
#include <vector>
#include "dawn/tests/MockCallback.h"
#include "dawn/utils/ComboRenderPipelineDescriptor.h"
#include "dawn/utils/TextureUtils.h"
#include "dawn/utils/WGPUHelpers.h"
namespace dawn {
namespace {
struct BackendBeginStateVk : public SharedTextureMemoryTestBackend::BackendBeginState {
wgpu::SharedTextureMemoryVkImageLayoutBeginState imageLayouts{};
};
struct BackendEndStateVk : public SharedTextureMemoryTestBackend::BackendEndState {
wgpu::SharedTextureMemoryVkImageLayoutEndState imageLayouts{};
};
} // anonymous namespace
std::unique_ptr<SharedTextureMemoryTestBackend::BackendBeginState>
SharedTextureMemoryTestVulkanBackend::ChainInitialBeginState(
wgpu::SharedTextureMemoryBeginAccessDescriptor* beginDesc) {
auto state = std::make_unique<BackendBeginStateVk>();
beginDesc->nextInChain = &state->imageLayouts;
return state;
}
std::unique_ptr<SharedTextureMemoryTestBackend::BackendEndState>
SharedTextureMemoryTestVulkanBackend::ChainEndState(
wgpu::SharedTextureMemoryEndAccessState* endState) {
auto state = std::make_unique<BackendEndStateVk>();
endState->nextInChain = &state->imageLayouts;
return state;
}
std::unique_ptr<SharedTextureMemoryTestBackend::BackendBeginState>
SharedTextureMemoryTestVulkanBackend::ChainBeginState(
wgpu::SharedTextureMemoryBeginAccessDescriptor* beginDesc,
const wgpu::SharedTextureMemoryEndAccessState& endState) {
DAWN_ASSERT(endState.nextInChain != nullptr);
DAWN_ASSERT(endState.nextInChain->sType ==
wgpu::SType::SharedTextureMemoryVkImageLayoutEndState);
auto* vkEndState =
static_cast<wgpu::SharedTextureMemoryVkImageLayoutEndState*>(endState.nextInChain);
auto state = std::make_unique<BackendBeginStateVk>();
state->imageLayouts.oldLayout = vkEndState->oldLayout;
state->imageLayouts.newLayout = vkEndState->newLayout;
beginDesc->nextInChain = &state->imageLayouts;
return state;
}
void SharedTextureMemoryNoFeatureTests::SetUp() {
DAWN_TEST_UNSUPPORTED_IF(UsesWire());
DawnTestWithParams<SharedTextureMemoryTestParams>::SetUp();
GetParam().mBackend->SetUp();
}
std::vector<wgpu::FeatureName> SharedTextureMemoryTests::GetRequiredFeatures() {
auto features = GetParam().mBackend->RequiredFeatures(GetAdapter().Get());
if (!SupportsFeatures(features)) {
return {};
}
const wgpu::FeatureName kOptionalFeatures[] = {
wgpu::FeatureName::MultiPlanarFormatExtendedUsages,
wgpu::FeatureName::MultiPlanarRenderTargets,
wgpu::FeatureName::TransientAttachments,
wgpu::FeatureName::Norm16TextureFormats,
wgpu::FeatureName::BGRA8UnormStorage,
};
for (auto feature : kOptionalFeatures) {
if (SupportsFeatures({feature})) {
features.push_back(feature);
}
}
return features;
}
void SharedTextureMemoryTests::SetUp() {
DAWN_TEST_UNSUPPORTED_IF(UsesWire());
DawnTestWithParams<SharedTextureMemoryTestParams>::SetUp();
DAWN_TEST_UNSUPPORTED_IF(
!SupportsFeatures(GetParam().mBackend->RequiredFeatures(GetAdapter().Get())));
GetParam().mBackend->SetUp();
}
void SharedTextureMemoryNoFeatureTests::TearDown() {
DawnTestWithParams<SharedTextureMemoryTestParams>::TearDown();
GetParam().mBackend->TearDown();
}
void SharedTextureMemoryTests::TearDown() {
DawnTestWithParams<SharedTextureMemoryTestParams>::TearDown();
GetParam().mBackend->TearDown();
}
wgpu::SharedFence SharedTextureMemoryTestBackend::ImportFenceTo(const wgpu::Device& importingDevice,
const wgpu::SharedFence& fence) {
wgpu::SharedFenceExportInfo exportInfo;
fence.ExportInfo(&exportInfo);
switch (exportInfo.type) {
case wgpu::SharedFenceType::VkSemaphoreOpaqueFD: {
wgpu::SharedFenceVkSemaphoreOpaqueFDExportInfo vkExportInfo;
exportInfo.nextInChain = &vkExportInfo;
fence.ExportInfo(&exportInfo);
wgpu::SharedFenceVkSemaphoreOpaqueFDDescriptor vkDesc;
vkDesc.handle = vkExportInfo.handle;
wgpu::SharedFenceDescriptor fenceDesc;
fenceDesc.nextInChain = &vkDesc;
return importingDevice.ImportSharedFence(&fenceDesc);
}
case wgpu::SharedFenceType::VkSemaphoreSyncFD: {
wgpu::SharedFenceVkSemaphoreSyncFDExportInfo vkExportInfo;
exportInfo.nextInChain = &vkExportInfo;
fence.ExportInfo(&exportInfo);
wgpu::SharedFenceVkSemaphoreSyncFDDescriptor vkDesc;
vkDesc.handle = vkExportInfo.handle;
wgpu::SharedFenceDescriptor fenceDesc;
fenceDesc.nextInChain = &vkDesc;
return importingDevice.ImportSharedFence(&fenceDesc);
}
case wgpu::SharedFenceType::VkSemaphoreZirconHandle: {
wgpu::SharedFenceVkSemaphoreZirconHandleExportInfo vkExportInfo;
exportInfo.nextInChain = &vkExportInfo;
fence.ExportInfo(&exportInfo);
wgpu::SharedFenceVkSemaphoreZirconHandleDescriptor vkDesc;
vkDesc.handle = vkExportInfo.handle;
wgpu::SharedFenceDescriptor fenceDesc;
fenceDesc.nextInChain = &vkDesc;
return importingDevice.ImportSharedFence(&fenceDesc);
}
case wgpu::SharedFenceType::DXGISharedHandle: {
wgpu::SharedFenceDXGISharedHandleExportInfo dxgiExportInfo;
exportInfo.nextInChain = &dxgiExportInfo;
fence.ExportInfo(&exportInfo);
wgpu::SharedFenceDXGISharedHandleDescriptor dxgiDesc;
dxgiDesc.handle = dxgiExportInfo.handle;
wgpu::SharedFenceDescriptor fenceDesc;
fenceDesc.nextInChain = &dxgiDesc;
return importingDevice.ImportSharedFence(&fenceDesc);
}
case wgpu::SharedFenceType::MTLSharedEvent: {
wgpu::SharedFenceMTLSharedEventExportInfo sharedEventInfo;
exportInfo.nextInChain = &sharedEventInfo;
fence.ExportInfo(&exportInfo);
wgpu::SharedFenceMTLSharedEventDescriptor sharedEventDesc;
sharedEventDesc.sharedEvent = sharedEventInfo.sharedEvent;
wgpu::SharedFenceDescriptor fenceDesc;
fenceDesc.nextInChain = &sharedEventDesc;
return importingDevice.ImportSharedFence(&fenceDesc);
}
default:
DAWN_UNREACHABLE();
}
}
std::vector<wgpu::SharedTextureMemory> SharedTextureMemoryTestBackend::CreateSharedTextureMemories(
wgpu::Device& device) {
std::vector<wgpu::SharedTextureMemory> memories;
for (auto& memory : CreatePerDeviceSharedTextureMemories({device})) {
DAWN_ASSERT(memory.size() == 1u);
memories.push_back(std::move(memory[0]));
}
// There should be at least one memory to test.
DAWN_ASSERT(!memories.empty());
return memories;
}
wgpu::Texture CreateWriteTexture(wgpu::SharedTextureMemory memory) {
wgpu::SharedTextureMemoryProperties properties;
memory.GetProperties(&properties);
wgpu::TextureDescriptor writeTextureDesc = {};
writeTextureDesc.format = properties.format;
writeTextureDesc.size = properties.size;
writeTextureDesc.usage = wgpu::TextureUsage::RenderAttachment;
writeTextureDesc.label = "write texture";
return memory.CreateTexture(&writeTextureDesc);
}
wgpu::Texture CreateReadTexture(wgpu::SharedTextureMemory memory) {
wgpu::SharedTextureMemoryProperties properties;
memory.GetProperties(&properties);
wgpu::TextureDescriptor readTextureDesc = {};
readTextureDesc.format = properties.format;
readTextureDesc.size = properties.size;
readTextureDesc.usage = wgpu::TextureUsage::TextureBinding;
readTextureDesc.label = "read texture";
return memory.CreateTexture(&readTextureDesc);
}
std::vector<wgpu::SharedTextureMemory>
SharedTextureMemoryTestBackend::CreateSinglePlanarSharedTextureMemories(wgpu::Device& device) {
std::vector<wgpu::SharedTextureMemory> out;
for (auto& memory : CreateSharedTextureMemories(device)) {
wgpu::SharedTextureMemoryProperties properties;
memory.GetProperties(&properties);
if (utils::IsMultiPlanarFormat(properties.format)) {
continue;
}
out.push_back(std::move(memory));
}
return out;
}
std::vector<std::vector<wgpu::SharedTextureMemory>>
SharedTextureMemoryTestBackend::CreatePerDeviceSharedTextureMemoriesFilterByUsage(
const std::vector<wgpu::Device>& devices,
wgpu::TextureUsage requiredUsage) {
std::vector<std::vector<wgpu::SharedTextureMemory>> out;
for (auto& memories : CreatePerDeviceSharedTextureMemories(devices)) {
wgpu::SharedTextureMemoryProperties properties;
memories[0].GetProperties(&properties);
if ((properties.usage & requiredUsage) == requiredUsage) {
// Tests using RenderAttachment will get a TextureView from the
// texture. This currently doesn't work with multiplanar textures. The
// superficial problem is that the plane would need to be passed for
// multiplanar formats, and the deep problem is that the tests fail to
// create valid backing textures for some multiplanar formats (e.g.,
// on Apple), which results in a crash when accessing plane 0.
// TODO(crbug.com/dawn/2263): Fix this and remove this short-circuit.
if (utils::IsMultiPlanarFormat(properties.format) &&
(requiredUsage &
(wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding))) {
continue;
}
out.push_back(std::move(memories));
}
}
return out;
}
wgpu::Device SharedTextureMemoryTests::CreateDevice() {
if (GetParam().mBackend->UseSameDevice()) {
return device;
}
return DawnTestBase::CreateDevice();
}
void SharedTextureMemoryTests::UseInRenderPass(wgpu::Device& deviceObj, wgpu::Texture& texture) {
wgpu::CommandEncoder encoder = deviceObj.CreateCommandEncoder();
utils::ComboRenderPassDescriptor passDescriptor({texture.CreateView()});
passDescriptor.cColorAttachments[0].loadOp = wgpu::LoadOp::Load;
passDescriptor.cColorAttachments[0].storeOp = wgpu::StoreOp::Store;
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor);
pass.End();
wgpu::CommandBuffer commandBuffer = encoder.Finish();
deviceObj.GetQueue().Submit(1, &commandBuffer);
}
void SharedTextureMemoryTests::UseInCopy(wgpu::Device& deviceObj, wgpu::Texture& texture) {
wgpu::CommandEncoder encoder = deviceObj.CreateCommandEncoder();
wgpu::ImageCopyTexture source;
source.texture = texture;
// Create a destination buffer, large enough for 1 texel of any format.
wgpu::BufferDescriptor bufferDesc;
bufferDesc.size = 128;
bufferDesc.usage = wgpu::BufferUsage::CopyDst;
wgpu::ImageCopyBuffer destination;
destination.buffer = deviceObj.CreateBuffer(&bufferDesc);
wgpu::Extent3D size = {1, 1, 1};
encoder.CopyTextureToBuffer(&source, &destination, &size);
wgpu::CommandBuffer commandBuffer = encoder.Finish();
deviceObj.GetQueue().Submit(1, &commandBuffer);
}
// Make a command buffer that clears the texture to four different colors in each quadrant.
wgpu::CommandBuffer SharedTextureMemoryTests::MakeFourColorsClearCommandBuffer(
wgpu::Device& deviceObj,
wgpu::Texture& texture) {
wgpu::ShaderModule module = utils::CreateShaderModule(deviceObj, R"(
struct VertexOut {
@builtin(position) position : vec4f,
@location(0) uv : vec2f,
}
struct FragmentIn {
@location(0) uv : vec2f,
}
@vertex fn vert_main(@builtin(vertex_index) VertexIndex : u32) -> VertexOut {
let pos = array(
vec2( 1.0, 1.0),
vec2( 1.0, -1.0),
vec2(-1.0, -1.0),
vec2( 1.0, 1.0),
vec2(-1.0, -1.0),
vec2(-1.0, 1.0),
);
let uv = array(
vec2(1.0, 0.0),
vec2(1.0, 1.0),
vec2(0.0, 1.0),
vec2(1.0, 0.0),
vec2(0.0, 1.0),
vec2(0.0, 0.0),
);
return VertexOut(vec4f(pos[VertexIndex], 0.0, 1.0), uv[VertexIndex]);
}
@fragment fn frag_main(in: FragmentIn) -> @location(0) vec4f {
if (in.uv.x < 0.5) {
if (in.uv.y < 0.5) {
return vec4f(0.0, 1.0, 0.0, 0.501);
} else {
return vec4f(1.0, 0.0, 0.0, 0.501);
}
} else {
if (in.uv.y < 0.5) {
return vec4f(0.0, 0.0, 1.0, 0.501);
} else {
return vec4f(1.0, 1.0, 0.0, 0.501);
}
}
}
)");
utils::ComboRenderPipelineDescriptor pipelineDesc;
pipelineDesc.vertex.module = module;
pipelineDesc.cFragment.module = module;
pipelineDesc.cTargets[0].format = texture.GetFormat();
wgpu::RenderPipeline pipeline = deviceObj.CreateRenderPipeline(&pipelineDesc);
wgpu::CommandEncoder encoder = deviceObj.CreateCommandEncoder();
utils::ComboRenderPassDescriptor passDescriptor({texture.CreateView()});
passDescriptor.cColorAttachments[0].storeOp = wgpu::StoreOp::Store;
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor);
pass.SetPipeline(pipeline);
pass.Draw(6);
pass.End();
return encoder.Finish();
}
// Make a command buffer that clears the texture to four different colors in each quadrant.
wgpu::CommandBuffer SharedTextureMemoryTests::MakeFourColorsComputeCommandBuffer(
wgpu::Device& deviceObj,
wgpu::Texture& texture) {
std::string wgslFormat = utils::GetWGSLImageFormatQualifier(texture.GetFormat());
std::string shader = R"(
@group(0) @binding(0) var storageImage : texture_storage_2d<)" +
wgslFormat + R"(, write>;
@workgroup_size(1)
@compute fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
let dims = textureDimensions(storageImage);
if (global_id.x < dims.x / 2) {
if (global_id.y < dims.y / 2) {
textureStore(storageImage, global_id.xy, vec4f(0.0, 1.0, 0.0, 0.501));
} else {
textureStore(storageImage, global_id.xy, vec4f(1.0, 0.0, 0.0, 0.501));
}
} else {
if (global_id.y < dims.y / 2) {
textureStore(storageImage, global_id.xy, vec4f(0.0, 0.0, 1.0, 0.501));
} else {
textureStore(storageImage, global_id.xy, vec4f(1.0, 1.0, 0.0, 0.501));
}
}
}
)";
wgpu::ComputePipelineDescriptor pipelineDesc;
pipelineDesc.compute.module = utils::CreateShaderModule(deviceObj, shader.c_str());
wgpu::ComputePipeline pipeline = deviceObj.CreateComputePipeline(&pipelineDesc);
wgpu::CommandEncoder encoder = deviceObj.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, utils::MakeBindGroup(deviceObj, pipeline.GetBindGroupLayout(0),
{{0, texture.CreateView()}}));
pass.DispatchWorkgroups(texture.GetWidth(), texture.GetHeight());
pass.End();
return encoder.Finish();
}
// Use queue.writeTexture to write four different colors in each quadrant to the texture.
void SharedTextureMemoryTests::WriteFourColorsToRGBA8Texture(wgpu::Device& deviceObj,
wgpu::Texture& texture) {
DAWN_ASSERT(texture.GetFormat() == wgpu::TextureFormat::RGBA8Unorm);
uint32_t width = texture.GetWidth();
uint32_t height = texture.GetHeight();
uint32_t bytesPerBlock = utils::GetTexelBlockSizeInBytes(texture.GetFormat());
uint32_t bytesPerRow = width * bytesPerBlock;
uint32_t size = bytesPerRow * height;
std::vector<uint8_t> pixels(size);
constexpr utils::RGBA8 kTopLeft(0, 0xFF, 0, 0x80);
constexpr utils::RGBA8 kBottomLeft(0xFF, 0, 0, 0x80);
constexpr utils::RGBA8 kTopRight(0, 0, 0xFF, 0x80);
constexpr utils::RGBA8 kBottomRight(0xFF, 0xFF, 0, 0x80);
for (uint32_t y = 0; y < height; y++) {
for (uint32_t x = 0; x < width; x++) {
utils::RGBA8* pixel =
reinterpret_cast<utils::RGBA8*>(&pixels[y * bytesPerRow + x * bytesPerBlock]);
if (x < width / 2) {
if (y < height / 2) {
*pixel = kTopLeft;
} else {
*pixel = kBottomLeft;
}
} else {
if (y < height / 2) {
*pixel = kTopRight;
} else {
*pixel = kBottomRight;
}
}
}
}
wgpu::Extent3D writeSize = {width, height, 1};
wgpu::ImageCopyTexture dest;
dest.texture = texture;
wgpu::TextureDataLayout dataLayout = {
.offset = 0, .bytesPerRow = bytesPerRow, .rowsPerImage = height};
device.GetQueue().WriteTexture(&dest, pixels.data(), pixels.size(), &dataLayout, &writeSize);
}
// Make a command buffer that samples the contents of the input texture into an RGBA8Unorm texture.
std::pair<wgpu::CommandBuffer, wgpu::Texture>
SharedTextureMemoryTests::MakeCheckBySamplingCommandBuffer(wgpu::Device& deviceObj,
wgpu::Texture& texture) {
wgpu::ShaderModule module = utils::CreateShaderModule(deviceObj, R"(
@vertex fn vert_main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f {
let pos = array(
vec2( 1.0, 1.0),
vec2( 1.0, -1.0),
vec2(-1.0, -1.0),
vec2( 1.0, 1.0),
vec2(-1.0, -1.0),
vec2(-1.0, 1.0),
);
return vec4f(pos[VertexIndex], 0.0, 1.0);
}
@group(0) @binding(0) var t: texture_2d<f32>;
@fragment fn frag_main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4f {
return textureLoad(t, vec2u(coord_in.xy), 0);
}
)");
wgpu::TextureDescriptor textureDesc = {};
textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
textureDesc.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
textureDesc.size = {texture.GetWidth(), texture.GetHeight(), texture.GetDepthOrArrayLayers()};
textureDesc.label = "intermediate check texture";
wgpu::Texture colorTarget = deviceObj.CreateTexture(&textureDesc);
utils::ComboRenderPipelineDescriptor pipelineDesc;
pipelineDesc.vertex.module = module;
pipelineDesc.cFragment.module = module;
pipelineDesc.cTargets[0].format = colorTarget.GetFormat();
wgpu::RenderPipeline pipeline = deviceObj.CreateRenderPipeline(&pipelineDesc);
wgpu::BindGroup bindGroup = utils::MakeBindGroup(deviceObj, pipeline.GetBindGroupLayout(0),
{{0, texture.CreateView()}});
wgpu::CommandEncoder encoder = deviceObj.CreateCommandEncoder();
utils::ComboRenderPassDescriptor passDescriptor({colorTarget.CreateView()});
passDescriptor.cColorAttachments[0].storeOp = wgpu::StoreOp::Store;
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor);
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, bindGroup);
pass.Draw(6);
pass.End();
return {encoder.Finish(), colorTarget};
}
// Make a command buffer that samples the contents of the input texture into an RGBA8Unorm texture.
std::pair<wgpu::CommandBuffer, wgpu::Texture>
SharedTextureMemoryTests::MakeCheckBySamplingTwoTexturesCommandBuffer(wgpu::Texture& texture0,
wgpu::Texture& texture1) {
wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
@vertex fn vert_main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f {
let pos = array(
vec2( 1.0, 1.0),
vec2( 1.0, -1.0),
vec2(-1.0, -1.0),
vec2( 1.0, 1.0),
vec2(-1.0, -1.0),
vec2(-1.0, 1.0),
);
return vec4f(pos[VertexIndex], 0.0, 1.0);
}
@group(0) @binding(0) var t0: texture_2d<f32>;
@group(0) @binding(1) var t1: texture_2d<f32>;
@fragment fn frag_main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4f {
return (textureLoad(t0, vec2u(coord_in.xy), 0) / 2) +
(textureLoad(t1, vec2u(coord_in.xy), 0) / 2);
}
)");
wgpu::TextureDescriptor textureDesc = {};
textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
textureDesc.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
textureDesc.size = {texture0.GetWidth(), texture0.GetHeight(),
texture0.GetDepthOrArrayLayers()};
textureDesc.label = "intermediate check texture";
wgpu::Texture colorTarget = device.CreateTexture(&textureDesc);
utils::ComboRenderPipelineDescriptor pipelineDesc;
pipelineDesc.vertex.module = module;
pipelineDesc.cFragment.module = module;
pipelineDesc.cTargets[0].format = colorTarget.GetFormat();
wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDesc);
wgpu::BindGroup bindGroup =
utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
{{0, texture0.CreateView()}, {1, texture1.CreateView()}});
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
utils::ComboRenderPassDescriptor passDescriptor({colorTarget.CreateView()});
passDescriptor.cColorAttachments[0].storeOp = wgpu::StoreOp::Store;
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor);
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, bindGroup);
pass.Draw(6);
pass.End();
return {encoder.Finish(), colorTarget};
}
// Check that the contents of colorTarget are RGBA8Unorm texels that match those written by
// MakeFourColorsClearCommandBuffer.
void SharedTextureMemoryTests::CheckFourColors(wgpu::Device& deviceObj,
wgpu::TextureFormat format,
wgpu::Texture& colorTarget) {
wgpu::Origin3D tl = {colorTarget.GetWidth() / 4, colorTarget.GetHeight() / 4};
wgpu::Origin3D bl = {colorTarget.GetWidth() / 4, 3 * colorTarget.GetHeight() / 4};
wgpu::Origin3D tr = {3 * colorTarget.GetWidth() / 4, colorTarget.GetHeight() / 4};
wgpu::Origin3D br = {3 * colorTarget.GetWidth() / 4, 3 * colorTarget.GetHeight() / 4};
std::array<utils::RGBA8, 4> expectedColors;
uint8_t expectedAlpha;
switch (format) {
case wgpu::TextureFormat::RGB10A2Unorm:
expectedColors = {
utils::RGBA8::kGreen,
utils::RGBA8::kRed,
utils::RGBA8::kBlue,
utils::RGBA8::kYellow,
};
expectedAlpha = 0xAA;
break;
case wgpu::TextureFormat::RGBA8Unorm:
case wgpu::TextureFormat::BGRA8Unorm:
case wgpu::TextureFormat::RGBA16Float:
expectedColors = {
utils::RGBA8::kGreen,
utils::RGBA8::kRed,
utils::RGBA8::kBlue,
utils::RGBA8::kYellow,
};
expectedAlpha = 0x80;
break;
case wgpu::TextureFormat::RG16Float:
case wgpu::TextureFormat::RG16Unorm:
case wgpu::TextureFormat::RG8Unorm:
expectedColors = {
utils::RGBA8::kGreen,
utils::RGBA8::kRed,
utils::RGBA8::kBlack,
utils::RGBA8::kYellow,
};
expectedAlpha = 0xFF;
break;
case wgpu::TextureFormat::R16Float:
case wgpu::TextureFormat::R16Unorm:
case wgpu::TextureFormat::R8Unorm:
expectedColors = {
utils::RGBA8::kBlack,
utils::RGBA8::kRed,
utils::RGBA8::kBlack,
utils::RGBA8::kRed,
};
expectedAlpha = 0xFF;
break;
default:
DAWN_UNREACHABLE();
}
expectedColors[0].a = expectedAlpha;
expectedColors[1].a = expectedAlpha;
expectedColors[2].a = expectedAlpha;
expectedColors[3].a = expectedAlpha;
EXPECT_TEXTURE_EQ(deviceObj, &expectedColors[0], colorTarget, tl, {1, 1});
EXPECT_TEXTURE_EQ(deviceObj, &expectedColors[1], colorTarget, bl, {1, 1});
EXPECT_TEXTURE_EQ(deviceObj, &expectedColors[2], colorTarget, tr, {1, 1});
EXPECT_TEXTURE_EQ(deviceObj, &expectedColors[3], colorTarget, br, {1, 1});
}
// Allow tests to be uninstantiated since it's possible no backends are available.
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SharedTextureMemoryNoFeatureTests);
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SharedTextureMemoryTests);
namespace {
using testing::HasSubstr;
using testing::MockCallback;
template <typename T>
T& AsNonConst(const T& rhs) {
return const_cast<T&>(rhs);
}
// Test that creating shared texture memory without the required features is an error.
// Using the memory thereafter produces errors.
TEST_P(SharedTextureMemoryNoFeatureTests, CreationWithoutFeature) {
// Create external texture memories with an error filter.
// We should see a message that the feature is not enabled.
device.PushErrorScope(wgpu::ErrorFilter::Validation);
const auto& memories = GetParam().mBackend->CreateSharedTextureMemories(device);
MockCallback<WGPUErrorCallback> popErrorScopeCallback;
EXPECT_CALL(popErrorScopeCallback,
Call(WGPUErrorType_Validation, HasSubstr("is not enabled"), this));
device.PopErrorScope(popErrorScopeCallback.Callback(),
popErrorScopeCallback.MakeUserdata(this));
for (wgpu::SharedTextureMemory memory : memories) {
ASSERT_DEVICE_ERROR_MSG(wgpu::Texture texture = memory.CreateTexture(),
HasSubstr("is invalid"));
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
beginDesc.concurrentRead = false;
beginDesc.initialized = true;
ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.BeginAccess(texture, &beginDesc)),
HasSubstr("is invalid"));
wgpu::SharedTextureMemoryEndAccessState endState = {};
ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.EndAccess(texture, &endState)),
HasSubstr("is invalid"));
}
}
// Test that it is an error to import a shared texture memory with no chained struct.
TEST_P(SharedTextureMemoryTests, ImportSharedTextureMemoryNoChain) {
wgpu::SharedTextureMemoryDescriptor desc;
ASSERT_DEVICE_ERROR_MSG(
wgpu::SharedTextureMemory memory = device.ImportSharedTextureMemory(&desc),
HasSubstr("chain"));
}
// Test that it is an error to import a shared fence with no chained struct.
// Also test that ExportInfo reports an Undefined type for the error fence.
TEST_P(SharedTextureMemoryTests, ImportSharedFenceNoChain) {
wgpu::SharedFenceDescriptor desc;
ASSERT_DEVICE_ERROR_MSG(wgpu::SharedFence fence = device.ImportSharedFence(&desc),
HasSubstr("chain"));
wgpu::SharedFenceExportInfo exportInfo;
exportInfo.type = static_cast<wgpu::SharedFenceType>(1234); // should be overrwritten
// Expect that exporting the fence info writes Undefined, and generates an error.
ASSERT_DEVICE_ERROR(fence.ExportInfo(&exportInfo));
EXPECT_EQ(exportInfo.type, wgpu::SharedFenceType::Undefined);
}
// Test that it is an error to import a shared texture memory when the device is destroyed
TEST_P(SharedTextureMemoryTests, ImportSharedTextureMemoryDeviceDestroyed) {
device.Destroy();
wgpu::SharedTextureMemoryDescriptor desc;
ASSERT_DEVICE_ERROR_MSG(
wgpu::SharedTextureMemory memory = device.ImportSharedTextureMemory(&desc),
HasSubstr("lost"));
}
// Test that SharedTextureMemory::IsDeviceLost() returns the expected value before and
// after destroying the device.
TEST_P(SharedTextureMemoryTests, CheckIsDeviceLostBeforeAndAfterDestroyingDevice) {
wgpu::SharedTextureMemory memory = GetParam().mBackend->CreateSharedTextureMemory(device);
EXPECT_FALSE(memory.IsDeviceLost());
device.Destroy();
EXPECT_TRUE(memory.IsDeviceLost());
}
// Test that SharedTextureMemory::IsDeviceLost() returns the expected value before and
// after losing the device.
TEST_P(SharedTextureMemoryTests, CheckIsDeviceLostBeforeAndAfterLosingDevice) {
wgpu::SharedTextureMemory memory = GetParam().mBackend->CreateSharedTextureMemory(device);
EXPECT_FALSE(memory.IsDeviceLost());
LoseDeviceForTesting(device);
EXPECT_TRUE(memory.IsDeviceLost());
}
// Test that it is an error to import a shared fence when the device is destroyed
TEST_P(SharedTextureMemoryTests, ImportSharedFenceDeviceDestroyed) {
device.Destroy();
wgpu::SharedFenceDescriptor desc;
ASSERT_DEVICE_ERROR_MSG(wgpu::SharedFence fence = device.ImportSharedFence(&desc),
HasSubstr("lost"));
}
// Test calling GetProperties with an error memory. The properties are filled with 0/None/Undefined.
TEST_P(SharedTextureMemoryTests, GetPropertiesErrorMemory) {
wgpu::SharedTextureMemoryDescriptor desc;
ASSERT_DEVICE_ERROR(wgpu::SharedTextureMemory memory = device.ImportSharedTextureMemory(&desc));
wgpu::SharedTextureMemoryProperties properties;
memory.GetProperties(&properties);
EXPECT_EQ(properties.usage, wgpu::TextureUsage::None);
EXPECT_EQ(properties.size.width, 0u);
EXPECT_EQ(properties.size.height, 0u);
EXPECT_EQ(properties.size.depthOrArrayLayers, 0u);
EXPECT_EQ(properties.format, wgpu::TextureFormat::Undefined);
}
// Tests that a SharedTextureMemory supports expected texture usages.
TEST_P(SharedTextureMemoryTests, TextureUsages) {
for (wgpu::SharedTextureMemory memory :
GetParam().mBackend->CreateSharedTextureMemories(device)) {
wgpu::SharedTextureMemoryProperties properties;
memory.GetProperties(&properties);
// CopySrc and TextureBinding should always be supported.
// TODO(crbug.com/dawn/2262): TextureBinding support on D3D11/D3D12/Vulkan is actually
// dependent on the flags passed to the underlying texture (the relevant
// flag is currently always passed in the test context). Add tests where
// the D3D/Vulkan texture is not created with the relevant flag.
wgpu::TextureUsage expectedUsage =
wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding;
bool isSinglePlanar = !utils::IsMultiPlanarFormat(properties.format);
if (isSinglePlanar ||
device.HasFeature(wgpu::FeatureName::MultiPlanarFormatExtendedUsages)) {
expectedUsage |= wgpu::TextureUsage::CopyDst;
}
// TODO(crbug.com/dawn/2262): RenderAttachment support on D3D11/D3D12/Vulkan is
// additionally dependent on the flags passed to the underlying
// texture (the relevant flag is currently always passed in the test
// context). Add tests where the D3D/Vulkan texture is not created with the
// relevant flag.
if ((isSinglePlanar || device.HasFeature(wgpu::FeatureName::MultiPlanarRenderTargets)) &&
utils::IsRenderableFormat(device, properties.format)) {
expectedUsage |= wgpu::TextureUsage::RenderAttachment;
}
// TODO(crbug.com/dawn/2262): StorageBinding support on D3D11/D3D12/Vulkan is
// additionally dependent on the flags passed to the underlying
// texture (the relevant flag is currently always passed in the test
// context). Add tests where the D3D/Vulkan texture is not created with the
// relevant flag.
if (isSinglePlanar && utils::TextureFormatSupportsStorageTexture(properties.format, device,
IsCompatibilityMode())) {
expectedUsage |= wgpu::TextureUsage::StorageBinding;
}
EXPECT_EQ(properties.usage, expectedUsage) << properties.format;
}
}
// Test calling GetProperties with an invalid chained struct. An error is
// generated, but the properties are still populated.
TEST_P(SharedTextureMemoryTests, GetPropertiesInvalidChain) {
wgpu::SharedTextureMemory memory = GetParam().mBackend->CreateSharedTextureMemory(device);
wgpu::ChainedStructOut otherStruct;
wgpu::SharedTextureMemoryProperties properties1;
properties1.nextInChain = &otherStruct;
ASSERT_DEVICE_ERROR(memory.GetProperties(&properties1));
wgpu::SharedTextureMemoryProperties properties2;
memory.GetProperties(&properties2);
EXPECT_EQ(properties1.usage, properties2.usage);
EXPECT_EQ(properties1.size.width, properties2.size.width);
EXPECT_EQ(properties1.size.height, properties2.size.height);
EXPECT_EQ(properties1.size.depthOrArrayLayers, properties2.size.depthOrArrayLayers);
EXPECT_EQ(properties1.format, properties2.format);
}
// Test that texture usages must be a subset of the shared texture memory's usage.
TEST_P(SharedTextureMemoryTests, UsageValidation) {
for (wgpu::SharedTextureMemory memory :
GetParam().mBackend->CreateSharedTextureMemories(device)) {
wgpu::SharedTextureMemoryProperties properties;
memory.GetProperties(&properties);
// SharedTextureMemory should never support TransientAttachment.
ASSERT_EQ(properties.usage & wgpu::TextureUsage::TransientAttachment, 0);
wgpu::TextureDescriptor textureDesc = {};
textureDesc.format = properties.format;
textureDesc.size = properties.size;
for (wgpu::TextureUsage usage : {
wgpu::TextureUsage::CopySrc,
wgpu::TextureUsage::CopyDst,
wgpu::TextureUsage::TextureBinding,
wgpu::TextureUsage::StorageBinding,
wgpu::TextureUsage::RenderAttachment,
}) {
textureDesc.usage = usage;
// `usage` is valid if it is in the shared texture memory properties.
if (usage & properties.usage) {
wgpu::Texture t = memory.CreateTexture(&textureDesc);
EXPECT_EQ(t.GetUsage(), usage);
} else {
ASSERT_DEVICE_ERROR(memory.CreateTexture(&textureDesc));
}
}
}
}
// Test that it is an error if the texture format doesn't match the shared texture memory.
TEST_P(SharedTextureMemoryTests, FormatValidation) {
for (wgpu::SharedTextureMemory memory :
GetParam().mBackend->CreateSharedTextureMemories(device)) {
wgpu::SharedTextureMemoryProperties properties;
memory.GetProperties(&properties);
wgpu::TextureDescriptor textureDesc = {};
textureDesc.format = properties.format != wgpu::TextureFormat::RGBA8Unorm
? wgpu::TextureFormat::RGBA8Unorm
: wgpu::TextureFormat::RGBA16Float;
textureDesc.size = properties.size;
textureDesc.usage = properties.usage;
ASSERT_DEVICE_ERROR_MSG(memory.CreateTexture(&textureDesc),
HasSubstr("doesn't match descriptor format"));
}
}
// Test that it is an error if the texture size doesn't match the shared texture memory.
TEST_P(SharedTextureMemoryTests, SizeValidation) {
for (wgpu::SharedTextureMemory memory :
GetParam().mBackend->CreateSharedTextureMemories(device)) {
wgpu::SharedTextureMemoryProperties properties;
memory.GetProperties(&properties);
wgpu::TextureDescriptor textureDesc = {};
textureDesc.format = properties.format;
textureDesc.usage = properties.usage;
textureDesc.size = {properties.size.width + 1, properties.size.height,
properties.size.depthOrArrayLayers};
ASSERT_DEVICE_ERROR_MSG(memory.CreateTexture(&textureDesc),
HasSubstr("doesn't match descriptor size"));
textureDesc.size = {properties.size.width, properties.size.height + 1,
properties.size.depthOrArrayLayers};
ASSERT_DEVICE_ERROR_MSG(memory.CreateTexture(&textureDesc),
HasSubstr("doesn't match descriptor size"));
textureDesc.size = {properties.size.width, properties.size.height,
properties.size.depthOrArrayLayers + 1};
ASSERT_DEVICE_ERROR_MSG(memory.CreateTexture(&textureDesc), HasSubstr("is not 1"));
}
}
// Test that it is an error if the texture mip level count is not 1.
TEST_P(SharedTextureMemoryTests, MipLevelValidation) {
for (wgpu::SharedTextureMemory memory :
GetParam().mBackend->CreateSharedTextureMemories(device)) {
wgpu::SharedTextureMemoryProperties properties;
memory.GetProperties(&properties);
wgpu::TextureDescriptor textureDesc = {};
textureDesc.format = properties.format;
textureDesc.usage = properties.usage;
textureDesc.size = properties.size;
textureDesc.mipLevelCount = 1u;
memory.CreateTexture(&textureDesc);
textureDesc.mipLevelCount = 2u;
ASSERT_DEVICE_ERROR_MSG(memory.CreateTexture(&textureDesc), HasSubstr("(2) is not 1"));
}
}
// Test that it is an error if the texture sample count is not 1.
TEST_P(SharedTextureMemoryTests, SampleCountValidation) {
for (wgpu::SharedTextureMemory memory :
GetParam().mBackend->CreateSharedTextureMemories(device)) {
wgpu::SharedTextureMemoryProperties properties;
memory.GetProperties(&properties);
wgpu::TextureDescriptor textureDesc = {};
textureDesc.format = properties.format;
textureDesc.usage = properties.usage;
textureDesc.size = properties.size;
textureDesc.sampleCount = 1u;
memory.CreateTexture(&textureDesc);
textureDesc.sampleCount = 4u;
ASSERT_DEVICE_ERROR_MSG(memory.CreateTexture(&textureDesc), HasSubstr("(4) is not 1"));
}
}
// Test that it is an error if the texture dimension is not 2D.
TEST_P(SharedTextureMemoryTests, DimensionValidation) {
for (wgpu::SharedTextureMemory memory :
GetParam().mBackend->CreateSharedTextureMemories(device)) {
wgpu::SharedTextureMemoryProperties properties;
memory.GetProperties(&properties);
wgpu::TextureDescriptor textureDesc = {};
textureDesc.format = properties.format;
textureDesc.usage = properties.usage;
textureDesc.size = properties.size;
textureDesc.dimension = wgpu::TextureDimension::e1D;
ASSERT_DEVICE_ERROR_MSG(memory.CreateTexture(&textureDesc),
HasSubstr("is not TextureDimension::e2D"));
textureDesc.dimension = wgpu::TextureDimension::e3D;
ASSERT_DEVICE_ERROR_MSG(memory.CreateTexture(&textureDesc),
HasSubstr("is not TextureDimension::e2D"));
}
}
// Test that it is an error to call BeginAccess twice in a row on the same texture and memory.
TEST_P(SharedTextureMemoryTests, DoubleBeginAccess) {
wgpu::SharedTextureMemory memory = GetParam().mBackend->CreateSharedTextureMemory(device);
wgpu::Texture texture = memory.CreateTexture();
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
beginDesc.concurrentRead = false;
beginDesc.initialized = true;
auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
// It should be an error to BeginAccess twice in a row.
EXPECT_TRUE(memory.BeginAccess(texture, &beginDesc));
ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.BeginAccess(texture, &beginDesc)),
HasSubstr("is already used to access"));
}
// Test that it is an error to call BeginAccess concurrently on a write texture
// followed by a read texture on a single SharedTextureMemory.
TEST_P(SharedTextureMemoryTests, DoubleBeginAccessSeparateTexturesWriteRead) {
wgpu::SharedTextureMemory memory = GetParam().mBackend->CreateSharedTextureMemory(device);
wgpu::Texture writeTexture = CreateWriteTexture(memory);
wgpu::Texture readTexture = CreateReadTexture(memory);
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
beginDesc.concurrentRead = false;
beginDesc.initialized = true;
auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
EXPECT_TRUE(memory.BeginAccess(writeTexture, &beginDesc));
ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.BeginAccess(readTexture, &beginDesc)),
HasSubstr("is currently accessed for writing"));
}
// Test that it is an error to call BeginAccess concurrently on a write texture
// followed by a read texture on a single SharedTextureMemory.
TEST_P(SharedTextureMemoryTests, DoubleBeginAccessSeparateTexturesWriteConcurrentRead) {
// TODO(dawn/2276): support concurrent read access.
DAWN_TEST_UNSUPPORTED_IF(IsD3D12() || IsVulkan());
wgpu::SharedTextureMemory memory = GetParam().mBackend->CreateSharedTextureMemory(device);
wgpu::Texture writeTexture = CreateWriteTexture(memory);
wgpu::Texture readTexture = CreateReadTexture(memory);
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
beginDesc.concurrentRead = false;
beginDesc.initialized = true;
auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
EXPECT_TRUE(memory.BeginAccess(writeTexture, &beginDesc));
beginDesc.concurrentRead = true;
ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.BeginAccess(readTexture, &beginDesc)),
HasSubstr("is currently accessed for writing"));
}
// Test that it is an error to call BeginAccess concurrently on a read texture
// followed by a write texture on a single SharedTextureMemory.
TEST_P(SharedTextureMemoryTests, DoubleBeginAccessSeparateTexturesReadWrite) {
wgpu::SharedTextureMemory memory = GetParam().mBackend->CreateSharedTextureMemory(device);
wgpu::Texture writeTexture = CreateWriteTexture(memory);
wgpu::Texture readTexture = CreateReadTexture(memory);
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
beginDesc.concurrentRead = false;
beginDesc.initialized = true;
auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
EXPECT_TRUE(memory.BeginAccess(readTexture, &beginDesc));
ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.BeginAccess(writeTexture, &beginDesc)),
HasSubstr("is currently accessed for exclusive reading"));
}
// Test that it is an error to call BeginAccess concurrently on a read texture
// followed by a write texture on a single SharedTextureMemory.
TEST_P(SharedTextureMemoryTests, DoubleBeginAccessSeparateTexturesConcurrentReadWrite) {
// TODO(dawn/2276): support concurrent read access.
DAWN_TEST_UNSUPPORTED_IF(IsD3D12() || IsVulkan());
wgpu::SharedTextureMemory memory = GetParam().mBackend->CreateSharedTextureMemory(device);
wgpu::Texture writeTexture = CreateWriteTexture(memory);
wgpu::Texture readTexture = CreateReadTexture(memory);
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
beginDesc.concurrentRead = true;
beginDesc.initialized = true;
auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
EXPECT_TRUE(memory.BeginAccess(readTexture, &beginDesc));
beginDesc.concurrentRead = false;
ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.BeginAccess(writeTexture, &beginDesc)),
HasSubstr("is currently accessed for reading."));
}
// Test that it is an error to call BeginAccess concurrently on two write textures on a single
// SharedTextureMemory.
TEST_P(SharedTextureMemoryTests, DoubleBeginAccessSeparateTexturesWriteWrite) {
wgpu::SharedTextureMemory memory = GetParam().mBackend->CreateSharedTextureMemory(device);
wgpu::Texture writeTexture1 = CreateWriteTexture(memory);
wgpu::Texture writeTexture2 = CreateWriteTexture(memory);
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
beginDesc.concurrentRead = false;
beginDesc.initialized = true;
auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
EXPECT_TRUE(memory.BeginAccess(writeTexture1, &beginDesc));
ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.BeginAccess(writeTexture2, &beginDesc)),
HasSubstr("is currently accessed for writing"));
}
// Test that it is valid to call BeginAccess concurrently on two read textures on a single
// SharedTextureMemory.
TEST_P(SharedTextureMemoryTests, DoubleBeginAccessSeparateTexturesReadRead) {
wgpu::SharedTextureMemory memory = GetParam().mBackend->CreateSharedTextureMemory(device);
wgpu::Texture readTexture1 = CreateReadTexture(memory);
wgpu::Texture readTexture2 = CreateReadTexture(memory);
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
beginDesc.concurrentRead = false;
beginDesc.initialized = true;
auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
EXPECT_TRUE(memory.BeginAccess(readTexture1, &beginDesc));
ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.BeginAccess(readTexture2, &beginDesc)),
HasSubstr("is currently accessed for exclusive reading"));
}
// Test that it is valid to call BeginAccess concurrently on two read textures on a single
// SharedTextureMemory.
TEST_P(SharedTextureMemoryTests, DoubleBeginAccessSeparateTexturesConcurrentReadConcurrentRead) {
// TODO(dawn/2276): support concurrent read access.
DAWN_TEST_UNSUPPORTED_IF(IsD3D12() || IsVulkan());
wgpu::SharedTextureMemory memory = GetParam().mBackend->CreateSharedTextureMemory(device);
wgpu::Texture readTexture1 = CreateReadTexture(memory);
wgpu::Texture readTexture2 = CreateReadTexture(memory);
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
beginDesc.concurrentRead = true;
beginDesc.initialized = true;
auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
EXPECT_TRUE(memory.BeginAccess(readTexture1, &beginDesc));
EXPECT_TRUE(memory.BeginAccess(readTexture2, &beginDesc));
wgpu::SharedTextureMemoryEndAccessState endState1 = {};
EXPECT_TRUE(memory.EndAccess(readTexture1, &endState1));
wgpu::SharedTextureMemoryEndAccessState endState2 = {};
EXPECT_TRUE(memory.EndAccess(readTexture2, &endState2));
}
// Test that it is valid to call BeginAccess concurrently on read textures on a single
// SharedTextureMemory.
TEST_P(SharedTextureMemoryTests, DoubleBeginAccessSeparateTexturesConcurrentReadRead) {
// TODO(dawn/2276): support concurrent read access.
DAWN_TEST_UNSUPPORTED_IF(IsD3D12() || IsVulkan());
wgpu::SharedTextureMemory memory = GetParam().mBackend->CreateSharedTextureMemory(device);
wgpu::Texture readTexture1 = CreateReadTexture(memory);
wgpu::Texture readTexture2 = CreateReadTexture(memory);
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
beginDesc.initialized = true;
auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
beginDesc.concurrentRead = true;
EXPECT_TRUE(memory.BeginAccess(readTexture1, &beginDesc));
beginDesc.concurrentRead = false;
ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.BeginAccess(readTexture2, &beginDesc)),
HasSubstr("is currently accessed for reading."));
}
// Test that it is valid to call BeginAccess concurrently on read textures on a single
// SharedTextureMemory.
TEST_P(SharedTextureMemoryTests, DoubleBeginAccessSeparateTexturesReadConcurrentRead) {
// TODO(dawn/2276): support concurrent read access.
DAWN_TEST_UNSUPPORTED_IF(IsD3D12() || IsVulkan());
wgpu::SharedTextureMemory memory = GetParam().mBackend->CreateSharedTextureMemory(device);
wgpu::Texture readTexture1 = CreateReadTexture(memory);
wgpu::Texture readTexture2 = CreateReadTexture(memory);
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
beginDesc.initialized = true;
auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
beginDesc.concurrentRead = false;
EXPECT_TRUE(memory.BeginAccess(readTexture1, &beginDesc));
beginDesc.concurrentRead = true;
ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.BeginAccess(readTexture2, &beginDesc)),
HasSubstr("is currently accessed for exclusive reading."));
}
// Test that it is valid to call BeginAccess concurrently on write textures with concurrentRead is
// true.
TEST_P(SharedTextureMemoryTests, ConcurrentWrite) {
// TODO(dawn/2276): support concurrent read access.
DAWN_TEST_UNSUPPORTED_IF(IsD3D12() || IsVulkan());
wgpu::SharedTextureMemory memory = GetParam().mBackend->CreateSharedTextureMemory(device);
wgpu::Texture writeTexture = CreateWriteTexture(memory);
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
beginDesc.initialized = true;
auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
beginDesc.concurrentRead = true;
ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.BeginAccess(writeTexture, &beginDesc)),
HasSubstr("Concurrent reading read-write"));
}
// Test that it is an error to call EndAccess twice in a row on the same memory.
TEST_P(SharedTextureMemoryTests, DoubleEndAccess) {
wgpu::SharedTextureMemory memory = GetParam().mBackend->CreateSharedTextureMemory(device);
wgpu::Texture texture = memory.CreateTexture();
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
beginDesc.concurrentRead = false;
beginDesc.initialized = true;
auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
EXPECT_TRUE(memory.BeginAccess(texture, &beginDesc));
wgpu::SharedTextureMemoryEndAccessState endState = {};
auto backendEndState = GetParam().mBackend->ChainEndState(&endState);
EXPECT_TRUE(memory.EndAccess(texture, &endState));
// Invalid to end access a second time.
ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.EndAccess(texture, &endState)),
HasSubstr("is not currently being accessed"));
}
// Test that it is an error to call EndAccess on a texture that was not the one BeginAccess was
// called on.
TEST_P(SharedTextureMemoryTests, BeginThenEndOnDifferentTexture) {
wgpu::SharedTextureMemory memory = GetParam().mBackend->CreateSharedTextureMemory(device);
wgpu::Texture texture1 = memory.CreateTexture();
wgpu::Texture texture2 = memory.CreateTexture();
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
beginDesc.concurrentRead = false;
beginDesc.initialized = true;
auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
EXPECT_TRUE(memory.BeginAccess(texture1, &beginDesc));
wgpu::SharedTextureMemoryEndAccessState endState = {};
auto backendEndState = GetParam().mBackend->ChainEndState(&endState);
ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.EndAccess(texture2, &endState)),
HasSubstr("is not currently being accessed"));
}
// Test that it is an error to call EndAccess without a preceding BeginAccess.
TEST_P(SharedTextureMemoryTests, EndAccessWithoutBegin) {
wgpu::SharedTextureMemory memory = GetParam().mBackend->CreateSharedTextureMemory(device);
wgpu::Texture texture = memory.CreateTexture();
wgpu::SharedTextureMemoryEndAccessState endState = {};
auto backendEndState = GetParam().mBackend->ChainEndState(&endState);
ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.EndAccess(texture, &endState)),
HasSubstr("is not currently being accessed"));
}
// Test that it is an error to use the texture on the queue without a preceding BeginAccess.
TEST_P(SharedTextureMemoryTests, UseWithoutBegin) {
DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("skip_validation"));
wgpu::SharedTextureMemory memory = GetParam().mBackend->CreateSharedTextureMemory(device);
wgpu::SharedTextureMemoryProperties properties;
memory.GetProperties(&properties);
wgpu::Texture texture = memory.CreateTexture();
if (properties.usage & wgpu::TextureUsage::RenderAttachment) {
ASSERT_DEVICE_ERROR_MSG(UseInRenderPass(device, texture),
HasSubstr("without current access"));
} else if (properties.format != wgpu::TextureFormat::R8BG8Biplanar420Unorm &&
properties.format != wgpu::TextureFormat::R10X6BG10X6Biplanar420Unorm &&
properties.format != wgpu::TextureFormat::R8BG8A8Triplanar420Unorm) {
if (properties.usage & wgpu::TextureUsage::CopySrc) {
ASSERT_DEVICE_ERROR_MSG(UseInCopy(device, texture),
HasSubstr("without current access"));
}
if (properties.usage & wgpu::TextureUsage::CopyDst) {
wgpu::Extent3D writeSize = {1, 1, 1};
wgpu::ImageCopyTexture dest = {};
dest.texture = texture;
wgpu::TextureDataLayout dataLayout = {};
uint64_t data[2];
ASSERT_DEVICE_ERROR_MSG(
device.GetQueue().WriteTexture(&dest, &data, sizeof(data), &dataLayout, &writeSize),
HasSubstr("without current access"));
}
}
}
// Test that it is valid (does not crash) if the memory is dropped while a texture access has begun.
TEST_P(SharedTextureMemoryTests, TextureAccessOutlivesMemory) {
// NOTE: UseInRenderPass()/UseInCopy() do not currently support multiplanar
// formats.
for (wgpu::SharedTextureMemory memory :
GetParam().mBackend->CreateSinglePlanarSharedTextureMemories(device)) {
wgpu::SharedTextureMemoryProperties properties;
memory.GetProperties(&properties);
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
beginDesc.concurrentRead = false;
beginDesc.initialized = true;
auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
// Begin access on a texture, and drop the memory.
wgpu::Texture texture = memory.CreateTexture();
memory.BeginAccess(texture, &beginDesc);
memory = nullptr;
// Use the texture on the GPU; it should not crash.
if (properties.usage & wgpu::TextureUsage::RenderAttachment) {
UseInRenderPass(device, texture);
} else {
DAWN_ASSERT(properties.usage & wgpu::TextureUsage::CopySrc);
UseInCopy(device, texture);
}
}
}
// Test that if the texture is uninitialized, it is cleared on first use.
TEST_P(SharedTextureMemoryTests, UninitializedTextureIsCleared) {
for (wgpu::SharedTextureMemory memory :
GetParam().mBackend->CreateSharedTextureMemories(device)) {
wgpu::SharedTextureMemoryProperties properties;
memory.GetProperties(&properties);
// Skipped for multiplanar formats because those must be initialized on import.
// We also need render attachment usage to initially populate the texture.
if (utils::IsMultiPlanarFormat(properties.format) ||
(properties.usage & wgpu::TextureUsage::RenderAttachment) == 0) {
continue;
}
// Helper function to test that unintialized textures are lazy cleared upon use.
auto DoTest = [&](auto UseTexture) {
wgpu::Texture texture = memory.CreateTexture();
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
wgpu::SharedTextureMemoryEndAccessState endState = {};
auto backendEndState = GetParam().mBackend->ChainEndState(&endState);
// First fill the texture with data, so we can check that using it uninitialized
// makes it black.
{
wgpu::CommandBuffer commandBuffer =
MakeFourColorsClearCommandBuffer(device, texture);
beginDesc.concurrentRead = false;
beginDesc.initialized = true;
memory.BeginAccess(texture, &beginDesc);
device.GetQueue().Submit(1, &commandBuffer);
memory.EndAccess(texture, &endState);
}
// Now, BeginAccess on the texture as uninitialized.
beginDesc.fenceCount = endState.fenceCount;
beginDesc.fences = endState.fences;
beginDesc.signaledValues = endState.signaledValues;
beginDesc.concurrentRead = false;
beginDesc.initialized = false;
backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState);
memory.BeginAccess(texture, &beginDesc);
// Use the texture on the GPU which should lazy clear it.
UseTexture(texture);
AsNonConst(endState.initialized) = false; // should be overrwritten
memory.EndAccess(texture, &endState);
// The texture should be initialized now.
EXPECT_TRUE(endState.initialized);
// Begin access again - and check that the texture contents are zero.
{
auto [commandBuffer, colorTarget] =
MakeCheckBySamplingCommandBuffer(device, texture);
beginDesc.fenceCount = endState.fenceCount;
beginDesc.fences = endState.fences;
beginDesc.signaledValues = endState.signaledValues;
beginDesc.concurrentRead = false;
beginDesc.initialized = endState.initialized;
backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState);
memory.BeginAccess(texture, &beginDesc);
device.GetQueue().Submit(1, &commandBuffer);
memory.EndAccess(texture, &endState);
uint8_t alphaVal;
switch (properties.format) {
case wgpu::TextureFormat::RGBA8Unorm:
case wgpu::TextureFormat::BGRA8Unorm:
case wgpu::TextureFormat::RGB10A2Unorm:
case wgpu::TextureFormat::RGBA16Float:
alphaVal = 0;
break;
default:
// The test checks by sampling. Formats that don't
// have alpha return 1 for alpha when sampled in a shader.
alphaVal = 255;
break;
}
std::vector<utils::RGBA8> expected(texture.GetWidth() * texture.GetHeight(),
utils::RGBA8{0, 0, 0, alphaVal});
EXPECT_TEXTURE_EQ(device, expected.data(), colorTarget, {0, 0},
{colorTarget.GetWidth(), colorTarget.GetHeight()})
<< "format: " << static_cast<uint32_t>(properties.format);
}
};
// Test that using a texture in a render pass lazy clears it.
if (properties.usage & wgpu::TextureUsage::RenderAttachment) {
DoTest([&](wgpu::Texture& texture) { UseInRenderPass(device, texture); });
}
// Teset that using a texture in a copy lazy clears it.
if (properties.usage & wgpu::TextureUsage::CopySrc) {
DoTest([&](wgpu::Texture& texture) { UseInCopy(device, texture); });
}
}
}
// Test that if the texture is uninitialized, EndAccess writes the state out as uninitialized.
TEST_P(SharedTextureMemoryTests, UninitializedOnEndAccess) {
// It is not possible to run these tests for multiplanar formats for
// multiple reasons:
// * Test basic begin+end access exports the state as uninitialized
// if it starts as uninitialized. Multiplanar formats must be initialized on import.
// * RenderAttachment gets a TextureView from the texture. This has a
// superficial problem and a deep problem: The superficial problem is that
// the plane would need to be passed for multiplanar formats, and the deep
// problem is that the tests fail to create valid backing textures for some multiplanar
// formats (e.g., on Apple), which results in a crash when accessing plane
// 0.
// TODO(crbug.com/dawn/2263): Fix this and change the below to
// CreateSharedTextureMemories().
for (wgpu::SharedTextureMemory memory :
GetParam().mBackend->CreateSinglePlanarSharedTextureMemories(device)) {
wgpu::SharedTextureMemoryProperties properties;
memory.GetProperties(&properties);
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
wgpu::SharedTextureMemoryEndAccessState endState = {};
auto backendEndState = GetParam().mBackend->ChainEndState(&endState);
{
wgpu::Texture texture = memory.CreateTexture();
beginDesc.concurrentRead = false;
beginDesc.initialized = false;
memory.BeginAccess(texture, &beginDesc);
AsNonConst(endState.initialized) = true; // should be overrwritten
memory.EndAccess(texture, &endState);
EXPECT_FALSE(endState.initialized);
}
// Test begin access as initialized, then uninitializing the texture
// exports the state as uninitialized on end access. Requires render
// attachment usage to uninitialize.
if (properties.usage & wgpu::TextureUsage::RenderAttachment) {
wgpu::Texture texture = memory.CreateTexture();
beginDesc = {};
beginDesc.concurrentRead = false;
beginDesc.initialized = true;
backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState);
memory.BeginAccess(texture, &beginDesc);
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
utils::ComboRenderPassDescriptor passDescriptor({texture.CreateView()});
passDescriptor.cColorAttachments[0].storeOp = wgpu::StoreOp::Discard;
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor);
pass.End();
wgpu::CommandBuffer commandBuffer = encoder.Finish();
device.GetQueue().Submit(1, &commandBuffer);
endState = {};
AsNonConst(endState.initialized) = true; // should be overrwritten
memory.EndAccess(texture, &endState);
EXPECT_FALSE(endState.initialized);
}
}
}
// Test copying to texture memory on one device, then sampling it using another device.
TEST_P(SharedTextureMemoryTests, CopyToTextureThenSample) {
std::vector<wgpu::Device> devices = {device, CreateDevice()};
for (const auto& memories :
GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage(
devices, wgpu::TextureUsage::TextureBinding)) {
wgpu::Texture texture = memories[0].CreateTexture();
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
beginDesc.concurrentRead = false;
beginDesc.initialized = false;
auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
memories[0].BeginAccess(texture, &beginDesc);
// Create a texture of the same size to use as the source content.
wgpu::TextureDescriptor texDesc;
texDesc.format = texture.GetFormat();
texDesc.size = {texture.GetWidth(), texture.GetHeight()};
texDesc.usage =
texture.GetUsage() | wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment;
wgpu::Texture srcTex = devices[0].CreateTexture(&texDesc);
// Populate the source texture.
wgpu::CommandBuffer commandBuffer = MakeFourColorsClearCommandBuffer(devices[0], srcTex);
devices[0].GetQueue().Submit(1, &commandBuffer);
// Copy from the source texture into `texture`.
{
wgpu::CommandEncoder encoder = devices[0].CreateCommandEncoder();
auto src = utils::CreateImageCopyTexture(srcTex);
auto dst = utils::CreateImageCopyTexture(texture);
encoder.CopyTextureToTexture(&src, &dst, &texDesc.size);
commandBuffer = encoder.Finish();
}
devices[0].GetQueue().Submit(1, &commandBuffer);
wgpu::SharedTextureMemoryEndAccessState endState = {};
auto backendEndState = GetParam().mBackend->ChainEndState(&endState);
memories[0].EndAccess(texture, &endState);
// Sample from the texture
std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount);
for (size_t i = 0; i < endState.fenceCount; ++i) {
sharedFences[i] = GetParam().mBackend->ImportFenceTo(devices[1], endState.fences[i]);
}
beginDesc.fenceCount = endState.fenceCount;
beginDesc.fences = sharedFences.data();
beginDesc.signaledValues = endState.signaledValues;
beginDesc.concurrentRead = false;
beginDesc.initialized = endState.initialized;
backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState);
texture = memories[1].CreateTexture();
memories[1].BeginAccess(texture, &beginDesc);
wgpu::Texture colorTarget;
std::tie(commandBuffer, colorTarget) =
MakeCheckBySamplingCommandBuffer(devices[1], texture);
devices[1].GetQueue().Submit(1, &commandBuffer);
memories[1].EndAccess(texture, &endState);
CheckFourColors(devices[1], texture.GetFormat(), colorTarget);
}
}
// Test rendering to a texture memory on one device, then sampling it using another device.
// Encode the commands after performing BeginAccess.
TEST_P(SharedTextureMemoryTests, RenderThenSampleEncodeAfterBeginAccess) {
std::vector<wgpu::Device> devices = {device, CreateDevice()};
for (const auto& memories :
GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage(
devices, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding)) {
wgpu::Texture texture = memories[0].CreateTexture();
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
beginDesc.concurrentRead = false;
beginDesc.initialized = false;
auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
memories[0].BeginAccess(texture, &beginDesc);
// Clear the texture
wgpu::CommandBuffer commandBuffer = MakeFourColorsClearCommandBuffer(devices[0], texture);
devices[0].GetQueue().Submit(1, &commandBuffer);
wgpu::SharedTextureMemoryEndAccessState endState = {};
auto backendEndState = GetParam().mBackend->ChainEndState(&endState);
memories[0].EndAccess(texture, &endState);
// Sample from the texture
std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount);
for (size_t i = 0; i < endState.fenceCount; ++i) {
sharedFences[i] = GetParam().mBackend->ImportFenceTo(devices[1], endState.fences[i]);
}
beginDesc.fenceCount = endState.fenceCount;
beginDesc.fences = sharedFences.data();
beginDesc.signaledValues = endState.signaledValues;
beginDesc.concurrentRead = false;
beginDesc.initialized = endState.initialized;
backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState);
texture = memories[1].CreateTexture();
memories[1].BeginAccess(texture, &beginDesc);
wgpu::Texture colorTarget;
std::tie(commandBuffer, colorTarget) =
MakeCheckBySamplingCommandBuffer(devices[1], texture);
devices[1].GetQueue().Submit(1, &commandBuffer);
memories[1].EndAccess(texture, &endState);
CheckFourColors(devices[1], texture.GetFormat(), colorTarget);
}
}
// Test rendering to a texture memory on one device, then sampling it using another device.
// Encode the commands before performing BeginAccess (the access is only held during) QueueSubmit.
TEST_P(SharedTextureMemoryTests, RenderThenSampleEncodeBeforeBeginAccess) {
std::vector<wgpu::Device> devices = {device, CreateDevice()};
for (const auto& memories :
GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage(
devices, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding)) {
// Create two textures from each memory.
wgpu::Texture textures[] = {memories[0].CreateTexture(), memories[1].CreateTexture()};
// Make two command buffers, one that clears the texture, another that samples.
wgpu::CommandBuffer commandBuffer0 =
MakeFourColorsClearCommandBuffer(devices[0], textures[0]);
auto [commandBuffer1, colorTarget] =
MakeCheckBySamplingCommandBuffer(devices[1], textures[1]);
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
beginDesc.concurrentRead = false;
beginDesc.initialized = false;
auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
memories[0].BeginAccess(textures[0], &beginDesc);
devices[0].GetQueue().Submit(1, &commandBuffer0);
wgpu::SharedTextureMemoryEndAccessState endState = {};
auto backendEndState = GetParam().mBackend->ChainEndState(&endState);
memories[0].EndAccess(textures[0], &endState);
std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount);
for (size_t i = 0; i < endState.fenceCount; ++i) {
sharedFences[i] = GetParam().mBackend->ImportFenceTo(devices[1], endState.fences[i]);
}
beginDesc.fenceCount = endState.fenceCount;
beginDesc.fences = sharedFences.data();
beginDesc.signaledValues = endState.signaledValues;
beginDesc.concurrentRead = false;
beginDesc.initialized = endState.initialized;
backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState);
memories[1].BeginAccess(textures[1], &beginDesc);
devices[1].GetQueue().Submit(1, &commandBuffer1);
memories[1].EndAccess(textures[1], &endState);
CheckFourColors(devices[1], textures[1].GetFormat(), colorTarget);
}
}
// Test rendering to a texture memory on one device, then sampling it using another device.
// Destroy the texture from the first device after submitting the commands, but before performing
// EndAccess. The second device should still be able to wait on the first device and see the
// results.
TEST_P(SharedTextureMemoryTests, RenderThenTextureDestroyBeforeEndAccessThenSample) {
std::vector<wgpu::Device> devices = {device, CreateDevice()};
for (const auto& memories :
GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage(
devices, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding)) {
// Create two textures from each memory.
wgpu::Texture textures[] = {memories[0].CreateTexture(), memories[1].CreateTexture()};
// Make two command buffers, one that clears the texture, another that samples.
wgpu::CommandBuffer commandBuffer0 =
MakeFourColorsClearCommandBuffer(devices[0], textures[0]);
auto [commandBuffer1, colorTarget] =
MakeCheckBySamplingCommandBuffer(devices[1], textures[1]);
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
beginDesc.concurrentRead = false;
beginDesc.initialized = false;
auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
memories[0].BeginAccess(textures[0], &beginDesc);
devices[0].GetQueue().Submit(1, &commandBuffer0);
// Destroy the texture before performing EndAccess.
textures[0].Destroy();
wgpu::SharedTextureMemoryEndAccessState endState = {};
auto backendEndState = GetParam().mBackend->ChainEndState(&endState);
memories[0].EndAccess(textures[0], &endState);
std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount);
for (size_t i = 0; i < endState.fenceCount; ++i) {
sharedFences[i] = GetParam().mBackend->ImportFenceTo(devices[1], endState.fences[i]);
}
beginDesc.fenceCount = endState.fenceCount;
beginDesc.fences = sharedFences.data();
beginDesc.signaledValues = endState.signaledValues;
beginDesc.concurrentRead = false;
beginDesc.initialized = endState.initialized;
backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState);
memories[1].BeginAccess(textures[1], &beginDesc);
devices[1].GetQueue().Submit(1, &commandBuffer1);
memories[1].EndAccess(textures[1], &endState);
CheckFourColors(devices[1], textures[1].GetFormat(), colorTarget);
}
}
// Test accessing the memory on one device, dropping all memories, then
// accessing on the second device. Operations on the second device must
// still wait for the preceding operations to complete.
TEST_P(SharedTextureMemoryTests, RenderThenDropAllMemoriesThenSample) {
std::vector<wgpu::Device> devices = {device, CreateDevice()};
for (auto memories : GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage(
devices, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding)) {
// Create two textures from each memory.
wgpu::Texture textures[] = {memories[0].CreateTexture(), memories[1].CreateTexture()};
// Make two command buffers, one that clears the texture, another that samples.
wgpu::CommandBuffer commandBuffer0 =
MakeFourColorsClearCommandBuffer(devices[0], textures[0]);
auto [commandBuffer1, colorTarget] =
MakeCheckBySamplingCommandBuffer(devices[1], textures[1]);
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
beginDesc.concurrentRead = false;
beginDesc.initialized = false;
auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
wgpu::SharedTextureMemoryEndAccessState endState = {};
auto backendEndState = GetParam().mBackend->ChainEndState(&endState);
// Render to the texture.
{
memories[0].BeginAccess(textures[0], &beginDesc);
devices[0].GetQueue().Submit(1, &commandBuffer0);
memories[0].EndAccess(textures[0], &endState);
}
std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount);
for (size_t i = 0; i < endState.fenceCount; ++i) {
sharedFences[i] = GetParam().mBackend->ImportFenceTo(devices[1], endState.fences[i]);
}
beginDesc.fenceCount = endState.fenceCount;
beginDesc.fences = sharedFences.data();
beginDesc.signaledValues = endState.signaledValues;
beginDesc.concurrentRead = false;
beginDesc.initialized = endState.initialized;
backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState);
// Begin access, then drop all memories.
memories[1].BeginAccess(textures[1], &beginDesc);
memories.clear();
// Sample from the texture and check the contents.
devices[1].GetQueue().Submit(1, &commandBuffer1);
CheckFourColors(devices[1], textures[1].GetFormat(), colorTarget);
}
}
// Test rendering to a texture memory on one device, then sampling it using another device.
// Destroy or destroy the first device after submitting the commands, but before performing
// EndAccess. The second device should still be able to wait on the first device and see the
// results.
// This tests both cases where the device is destroyed, and where the device is lost.
TEST_P(SharedTextureMemoryTests, RenderThenLoseOrDestroyDeviceBeforeEndAccessThenSample) {
// Not supported if using the same device. Not possible to lose one without losing the other.
DAWN_TEST_UNSUPPORTED_IF(GetParam().mBackend->UseSameDevice());
auto DoTest = [&](auto DestroyOrLoseDevice) {
std::vector<wgpu::Device> devices = {CreateDevice(), CreateDevice()};
auto perDeviceMemories =
GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage(
devices, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding);
DAWN_TEST_UNSUPPORTED_IF(perDeviceMemories.empty());
const auto& memories = perDeviceMemories[0];
// Create two textures from each memory.
wgpu::Texture textures[] = {memories[0].CreateTexture(), memories[1].CreateTexture()};
// Make two command buffers, one that clears the texture, another that samples.
wgpu::CommandBuffer commandBuffer0 =
MakeFourColorsClearCommandBuffer(devices[0], textures[0]);
auto [commandBuffer1, colorTarget] =
MakeCheckBySamplingCommandBuffer(devices[1], textures[1]);
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
beginDesc.concurrentRead = false;
beginDesc.initialized = false;
auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
memories[0].BeginAccess(textures[0], &beginDesc);
devices[0].GetQueue().Submit(1, &commandBuffer0);
// Destroy or lose the device before performing EndAccess.
DestroyOrLoseDevice(devices[0]);
wgpu::SharedTextureMemoryEndAccessState endState = {};
auto backendEndState = GetParam().mBackend->ChainEndState(&endState);
memories[0].EndAccess(textures[0], &endState);
EXPECT_GT(endState.fenceCount, 0u);
std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount);
for (size_t i = 0; i < endState.fenceCount; ++i) {
sharedFences[i] = GetParam().mBackend->ImportFenceTo(devices[1], endState.fences[i]);
}
beginDesc.fenceCount = endState.fenceCount;
beginDesc.fences = sharedFences.data();
beginDesc.signaledValues = endState.signaledValues;
beginDesc.concurrentRead = false;
beginDesc.initialized = endState.initialized;
backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState);
memories[1].BeginAccess(textures[1], &beginDesc);
devices[1].GetQueue().Submit(1, &commandBuffer1);
memories[1].EndAccess(textures[1], &endState);
CheckFourColors(devices[1], textures[1].GetFormat(), colorTarget);
};
DoTest([](wgpu::Device d) { d.Destroy(); });
DoTest([this](wgpu::Device d) { LoseDeviceForTesting(d); });
}
// Test a shared texture memory created on separate devices but wrapping the same underyling data.
// Write to the texture, then read from two separate devices concurrently, then write again.
// Reads should happen strictly after the writes. The final write should wait for the reads.
TEST_P(SharedTextureMemoryTests, SeparateDevicesWriteThenConcurrentReadThenWrite) {
DAWN_TEST_UNSUPPORTED_IF(!GetParam().mBackend->SupportsConcurrentRead());
std::vector<wgpu::Device> devices = {device, CreateDevice(), CreateDevice()};
for (const auto& memories :
GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage(
devices, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding)) {
wgpu::SharedTextureMemoryProperties properties;
memories[0].GetProperties(&properties);
wgpu::TextureDescriptor writeTextureDesc = {};
writeTextureDesc.format = properties.format;
writeTextureDesc.size = properties.size;
writeTextureDesc.usage =
wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding;
writeTextureDesc.label = "write texture";
wgpu::TextureDescriptor readTextureDesc = {};
readTextureDesc.format = properties.format;
readTextureDesc.size = properties.size;
readTextureDesc.usage = wgpu::TextureUsage::TextureBinding;
readTextureDesc.label = "read texture";
// Create three textures from each memory.
// The first one will be written to.
// The second two will be concurrently read after the write.
// Then the first one will be written to again.
wgpu::Texture textures[] = {memories[0].CreateTexture(&writeTextureDesc),
memories[1].CreateTexture(&readTextureDesc),
memories[2].CreateTexture(&readTextureDesc)};
// Build command buffers for the test.
wgpu::CommandBuffer writeCommandBuffer0 =
MakeFourColorsClearCommandBuffer(devices[0], textures[0]);
auto [checkCommandBuffer1, colorTarget1] =
MakeCheckBySamplingCommandBuffer(devices[1], textures[1]);
auto [checkCommandBuffer2, colorTarget2] =
MakeCheckBySamplingCommandBuffer(devices[2], textures[2]);
wgpu::CommandBuffer clearToGrayCommandBuffer0;
{
wgpu::CommandEncoder encoder = devices[0].CreateCommandEncoder();
utils::ComboRenderPassDescriptor passDescriptor({textures[0].CreateView()});
passDescriptor.cColorAttachments[0].storeOp = wgpu::StoreOp::Store;
passDescriptor.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear;
passDescriptor.cColorAttachments[0].clearValue = {0.5, 0.5, 0.5, 1.0};
encoder.BeginRenderPass(&passDescriptor).End();
clearToGrayCommandBuffer0 = encoder.Finish();
}
// Begin access on texture 0
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
beginDesc.concurrentRead = false;
beginDesc.initialized = false;
auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
memories[0].BeginAccess(textures[0], &beginDesc);
// Write
devices[0].GetQueue().Submit(1, &writeCommandBuffer0);
// End access on texture 0
wgpu::SharedTextureMemoryEndAccessState endState = {};
auto backendEndState = GetParam().mBackend->ChainEndState(&endState);
memories[0].EndAccess(textures[0], &endState);
EXPECT_TRUE(endState.initialized);
// Import fences to devices[1] and begin access.
std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount);
for (size_t i = 0; i < endState.fenceCount; ++i) {
sharedFences[i] = GetParam().mBackend->ImportFenceTo(devices[1], endState.fences[i]);
}
beginDesc.fenceCount = sharedFences.size();
beginDesc.fences = sharedFences.data();
beginDesc.signaledValues = endState.signaledValues;
beginDesc.concurrentRead = false;
beginDesc.initialized = true;
backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState);
memories[1].BeginAccess(textures[1], &beginDesc);
// Import fences to devices[2] and begin access.
for (size_t i = 0; i < endState.fenceCount; ++i) {
sharedFences[i] = GetParam().mBackend->ImportFenceTo(devices[2], endState.fences[i]);
}
memories[2].BeginAccess(textures[2], &beginDesc);
// Check contents
devices[1].GetQueue().Submit(1, &checkCommandBuffer1);
devices[2].GetQueue().Submit(1, &checkCommandBuffer2);
CheckFourColors(devices[1], textures[1].GetFormat(), colorTarget1);
CheckFourColors(devices[2], textures[2].GetFormat(), colorTarget2);
// End access on texture 1
wgpu::SharedTextureMemoryEndAccessState endState1;
auto backendEndState1 = GetParam().mBackend->ChainEndState(&endState1);
memories[1].EndAccess(textures[1], &endState1);
EXPECT_TRUE(endState1.initialized);
// End access on texture 2
wgpu::SharedTextureMemoryEndAccessState endState2;
auto backendEndState2 = GetParam().mBackend->ChainEndState(&endState2);
memories[2].EndAccess(textures[2], &endState2);
EXPECT_TRUE(endState2.initialized);
// Import fences back to devices[0]
sharedFences.resize(endState1.fenceCount + endState2.fenceCount);
std::vector<uint64_t> signaledValues(sharedFences.size());
for (size_t i = 0; i < endState1.fenceCount; ++i) {
sharedFences[i] = GetParam().mBackend->ImportFenceTo(devices[0], endState1.fences[i]);
signaledValues[i] = endState1.signaledValues[i];
}
for (size_t i = 0; i < endState2.fenceCount; ++i) {
sharedFences[i + endState1.fenceCount] =
GetParam().mBackend->ImportFenceTo(devices[0], endState2.fences[i]);
signaledValues[i + endState1.fenceCount] = endState2.signaledValues[i];
}
beginDesc.fenceCount = sharedFences.size();
beginDesc.fences = sharedFences.data();
beginDesc.signaledValues = signaledValues.data();
beginDesc.concurrentRead = false;
beginDesc.initialized = true;
backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState2);
// Begin access on texture 0
memories[0].BeginAccess(textures[0], &beginDesc);
// Submit a clear to gray.
devices[0].GetQueue().Submit(1, &clearToGrayCommandBuffer0);
}
}
// Test a shared texture memory created on one device. Create three textures from the memory,
// Write to one texture, then read from two separate textures `concurrently`, then write again.
// Reads should happen strictly after the writes. The final write should wait for the reads.
TEST_P(SharedTextureMemoryTests, SameDeviceWriteThenConcurrentReadThenWrite) {
// TODO(dawn/2276): support concurrent read access.
DAWN_TEST_UNSUPPORTED_IF(IsD3D12() || IsVulkan());
DAWN_TEST_UNSUPPORTED_IF(!GetParam().mBackend->SupportsConcurrentRead());
for (const auto& memories :
GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage(
{device}, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding)) {
auto memory = memories[0];
wgpu::SharedTextureMemoryProperties properties;
memory.GetProperties(&properties);
wgpu::TextureDescriptor writeTextureDesc = {};
writeTextureDesc.format = properties.format;
writeTextureDesc.size = properties.size;
writeTextureDesc.usage =
wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding;
writeTextureDesc.label = "write texture";
wgpu::TextureDescriptor readTextureDesc = {};
readTextureDesc.format = properties.format;
readTextureDesc.size = properties.size;
readTextureDesc.usage = wgpu::TextureUsage::TextureBinding;
readTextureDesc.label = "read texture";
// Create three textures from each memory.
// The first one will be written to.
// The second two will be concurrently read after the write.
// Then the first one will be written to again.
wgpu::Texture textures[] = {memory.CreateTexture(&writeTextureDesc),
memory.CreateTexture(&readTextureDesc),
memory.CreateTexture(&readTextureDesc)};
// Build command buffers for the test.
wgpu::CommandBuffer writeCommandBuffer0 =
MakeFourColorsClearCommandBuffer(device, textures[0]);
auto [checkCommandBuffer, colorTarget] =
MakeCheckBySamplingTwoTexturesCommandBuffer(textures[1], textures[2]);
wgpu::CommandBuffer clearToGrayCommandBuffer0;
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
utils::ComboRenderPassDescriptor passDescriptor({textures[0].CreateView()});
passDescriptor.cColorAttachments[0].storeOp = wgpu::StoreOp::Store;
passDescriptor.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear;
passDescriptor.cColorAttachments[0].clearValue = {0.5, 0.5, 0.5, 1.0};
encoder.BeginRenderPass(&passDescriptor).End();
clearToGrayCommandBuffer0 = encoder.Finish();
}
// Begin access on texture 0
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
beginDesc.concurrentRead = false;
beginDesc.initialized = false;
auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
memory.BeginAccess(textures[0], &beginDesc);
// Write
device.GetQueue().Submit(1, &writeCommandBuffer0);
// End access on texture 0
wgpu::SharedTextureMemoryEndAccessState endState = {};
auto backendEndState = GetParam().mBackend->ChainEndState(&endState);
memory.EndAccess(textures[0], &endState);
EXPECT_TRUE(endState.initialized);
// Import fences to device and begin access.
std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount);
for (size_t i = 0; i < endState.fenceCount; ++i) {
sharedFences[i] = GetParam().mBackend->ImportFenceTo(device, endState.fences[i]);
}
beginDesc.fenceCount = sharedFences.size();
beginDesc.fences = sharedFences.data();
beginDesc.signaledValues = endState.signaledValues;
beginDesc.concurrentRead = true;
beginDesc.initialized = true;
backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState);
memory.BeginAccess(textures[1], &beginDesc);
// Import fences to device and begin access.
for (size_t i = 0; i < endState.fenceCount; ++i) {
sharedFences[i] = GetParam().mBackend->ImportFenceTo(device, endState.fences[i]);
}
memory.BeginAccess(textures[2], &beginDesc);
// Check contents
device.GetQueue().Submit(1, &checkCommandBuffer);
CheckFourColors(device, textures[1].GetFormat(), colorTarget);
// End access on texture 1
wgpu::SharedTextureMemoryEndAccessState endState1;
auto backendEndState1 = GetParam().mBackend->ChainEndState(&endState1);
memory.EndAccess(textures[1], &endState1);
EXPECT_TRUE(endState1.initialized);
// End access on texture 2
wgpu::SharedTextureMemoryEndAccessState endState2;
auto backendEndState2 = GetParam().mBackend->ChainEndState(&endState2);
memory.EndAccess(textures[2], &endState2);
EXPECT_TRUE(endState2.initialized);
// Import fences back to devices[0]
sharedFences.resize(endState1.fenceCount + endState2.fenceCount);
std::vector<uint64_t> signaledValues(sharedFences.size());
for (size_t i = 0; i < endState1.fenceCount; ++i) {
sharedFences[i] = GetParam().mBackend->ImportFenceTo(device, endState1.fences[i]);
signaledValues[i] = endState1.signaledValues[i];
}
for (size_t i = 0; i < endState2.fenceCount; ++i) {
sharedFences[i + endState1.fenceCount] =
GetParam().mBackend->ImportFenceTo(device, endState2.fences[i]);
signaledValues[i + endState1.fenceCount] = endState2.signaledValues[i];
}
beginDesc.fenceCount = sharedFences.size();
beginDesc.fences = sharedFences.data();
beginDesc.signaledValues = signaledValues.data();
beginDesc.concurrentRead = false;
beginDesc.initialized = true;
backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState2);
// Begin access on texture 0
memory.BeginAccess(textures[0], &beginDesc);
// Submit a clear to gray.
device.GetQueue().Submit(1, &clearToGrayCommandBuffer0);
}
}
// Test that textures created from SharedTextureMemory may perform sRGB reinterpretation.
TEST_P(SharedTextureMemoryTests, SRGBReinterpretation) {
// TODO(crbug.com/dawn/2304): Investigate if the VVL is wrong here.
DAWN_SUPPRESS_TEST_IF(GetParam().mBackend->Name().find("dma buf") != std::string::npos &&
IsBackendValidationEnabled());
std::vector<wgpu::Device> devices = {device, CreateDevice()};
for (const auto& memories :
GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage(
devices, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc)) {
wgpu::SharedTextureMemoryProperties properties;
memories[1].GetProperties(&properties);
wgpu::TextureDescriptor textureDesc = {};
textureDesc.format = properties.format;
textureDesc.size = properties.size;
textureDesc.usage = wgpu::TextureUsage::RenderAttachment;
wgpu::TextureViewDescriptor viewDesc = {};
if (properties.format == wgpu::TextureFormat::RGBA8Unorm) {
viewDesc.format = wgpu::TextureFormat::RGBA8UnormSrgb;
} else if (properties.format == wgpu::TextureFormat::BGRA8Unorm) {
viewDesc.format = wgpu::TextureFormat::BGRA8UnormSrgb;
} else {
continue;
}
textureDesc.viewFormatCount = 1;
textureDesc.viewFormats = &viewDesc.format;
// Create the texture on device 1.
wgpu::Texture texture = memories[1].CreateTexture(&textureDesc);
// Submit a clear operation to sRGB value rgb(234, 51, 35).
utils::ComboRenderPassDescriptor renderPassDescriptor({texture.CreateView(&viewDesc)}, {});
renderPassDescriptor.cColorAttachments[0].clearValue = {234.0 / 255.0, 51.0 / 255.0,
35.0 / 255.0, 1.0};
wgpu::CommandEncoder encoder = devices[1].CreateCommandEncoder();
encoder.BeginRenderPass(&renderPassDescriptor).End();
wgpu::CommandBuffer commands = encoder.Finish();
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
beginDesc.concurrentRead = false;
beginDesc.initialized = false;
auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
wgpu::SharedTextureMemoryEndAccessState endState = {};
auto backendEndState = GetParam().mBackend->ChainEndState(&endState);
memories[1].BeginAccess(texture, &beginDesc);
devices[1].GetQueue().Submit(1, &commands);
memories[1].EndAccess(texture, &endState);
// Create the texture on device 0.
texture = memories[0].CreateTexture();
std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount);
for (size_t i = 0; i < endState.fenceCount; ++i) {
sharedFences[i] = GetParam().mBackend->ImportFenceTo(devices[0], endState.fences[i]);
}
beginDesc.fenceCount = endState.fenceCount;
beginDesc.fences = sharedFences.data();
beginDesc.signaledValues = endState.signaledValues;
beginDesc.concurrentRead = false;
beginDesc.initialized = endState.initialized;
backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState);
memories[0].BeginAccess(texture, &beginDesc);
// Expect the contents to be approximately rgb(246 124 104)
if (properties.format == wgpu::TextureFormat::RGBA8Unorm) {
EXPECT_PIXEL_RGBA8_BETWEEN( //
utils::RGBA8(245, 123, 103, 255), //
utils::RGBA8(247, 125, 105, 255), texture, 0, 0);
} else {
EXPECT_PIXEL_RGBA8_BETWEEN( //
utils::RGBA8(103, 123, 245, 255), //
utils::RGBA8(105, 125, 247, 255), texture, 0, 0);
}
}
}
// Test writing to texture memory in compute pass on one device, then sampling it using another
// device.
TEST_P(SharedTextureMemoryTests, WriteStorageThenReadSample) {
std::vector<wgpu::Device> devices = {device, CreateDevice()};
for (const auto& memories :
GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage(
devices, wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::TextureBinding)) {
// Create the textures on each SharedTextureMemory.
wgpu::Texture texture0 = memories[0].CreateTexture();
wgpu::Texture texture1 = memories[1].CreateTexture();
// Make a command buffer to populate the texture contents in a compute shader.
wgpu::CommandBuffer commandBuffer0 =
MakeFourColorsComputeCommandBuffer(devices[0], texture0);
// Make a command buffer to sample and check the texture contents.
wgpu::Texture resultTarget;
wgpu::CommandBuffer commandBuffer1;
std::tie(commandBuffer1, resultTarget) =
MakeCheckBySamplingCommandBuffer(devices[1], texture1);
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
beginDesc.concurrentRead = false;
beginDesc.initialized = false;
auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
wgpu::SharedTextureMemoryEndAccessState endState = {};
auto backendEndState = GetParam().mBackend->ChainEndState(&endState);
// Begin access on memory 0, submit the compute pass, end access.
memories[0].BeginAccess(texture0, &beginDesc);
devices[0].GetQueue().Submit(1, &commandBuffer0);
memories[0].EndAccess(texture0, &endState);
// Import fences to device 1.
std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount);
for (size_t i = 0; i < endState.fenceCount; ++i) {
sharedFences[i] = GetParam().mBackend->ImportFenceTo(devices[1], endState.fences[i]);
}
beginDesc.fenceCount = endState.fenceCount;
beginDesc.fences = sharedFences.data();
beginDesc.signaledValues = endState.signaledValues;
beginDesc.concurrentRead = false;
beginDesc.initialized = endState.initialized;
backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState);
// Begin access on memory 1, check the contents, end access.
memories[1].BeginAccess(texture1, &beginDesc);
devices[1].GetQueue().Submit(1, &commandBuffer1);
memories[1].EndAccess(texture1, &endState);
// Check all the sampled colors are correct.
CheckFourColors(devices[1], texture1.GetFormat(), resultTarget);
}
}
// Test writing to texture memory using queue.writeTexture, then sampling it using another device.
TEST_P(SharedTextureMemoryTests, WriteTextureThenReadSample) {
std::vector<wgpu::Device> devices = {device, CreateDevice()};
for (const auto& memories :
GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage(
devices, wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::TextureBinding)) {
wgpu::SharedTextureMemoryProperties properties;
memories[0].GetProperties(&properties);
if (properties.format != wgpu::TextureFormat::RGBA8Unorm) {
continue;
}
// Create the textures on each SharedTextureMemory.
wgpu::Texture texture0 = memories[0].CreateTexture();
wgpu::Texture texture1 = memories[1].CreateTexture();
// Make a command buffer to sample and check the texture contents.
wgpu::Texture resultTarget;
wgpu::CommandBuffer commandBuffer1;
std::tie(commandBuffer1, resultTarget) =
MakeCheckBySamplingCommandBuffer(devices[1], texture1);
wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
beginDesc.initialized = false;
auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
wgpu::SharedTextureMemoryEndAccessState endState = {};
auto backendEndState = GetParam().mBackend->ChainEndState(&endState);
// Begin access on memory 0, use queue.writeTexture to populate the contents, end access.
memories[0].BeginAccess(texture0, &beginDesc);
WriteFourColorsToRGBA8Texture(devices[0], texture0);
memories[0].EndAccess(texture0, &endState);
// Import fences to device 1.
std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount);
for (size_t i = 0; i < endState.fenceCount; ++i) {
sharedFences[i] = GetParam().mBackend->ImportFenceTo(devices[1], endState.fences[i]);
}
beginDesc.fenceCount = endState.fenceCount;
beginDesc.fences = sharedFences.data();
beginDesc.signaledValues = endState.signaledValues;
beginDesc.initialized = endState.initialized;
backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState);
// Begin access on memory 1, check the contents, end access.
memories[1].BeginAccess(texture1, &beginDesc);
devices[1].GetQueue().Submit(1, &commandBuffer1);
memories[1].EndAccess(texture1, &endState);
// Check all the sampled colors are correct.
CheckFourColors(devices[1], texture1.GetFormat(), resultTarget);
}
}
class SharedTextureMemoryVulkanTests : public DawnTest {};
// Test that only a single Vulkan fence feature may be enabled at once.
TEST_P(SharedTextureMemoryVulkanTests, SingleFenceFeature) {
DAWN_TEST_UNSUPPORTED_IF(UsesWire());
std::vector<wgpu::FeatureName> fenceFeatures;
wgpu::Adapter adapter(GetAdapter().Get());
for (wgpu::FeatureName f : {
wgpu::FeatureName::SharedFenceVkSemaphoreOpaqueFD,
wgpu::FeatureName::SharedFenceVkSemaphoreSyncFD,
wgpu::FeatureName::SharedFenceVkSemaphoreZirconHandle,
}) {
if (adapter.HasFeature(f)) {
fenceFeatures.push_back(f);
}
}
// Test that creating a device with each feature is valid.
for (wgpu::FeatureName f : fenceFeatures) {
wgpu::DeviceDescriptor deviceDesc;
deviceDesc.requiredFeatureCount = 1;
deviceDesc.requiredFeatures = &f;
EXPECT_NE(adapter.CreateDevice(&deviceDesc), nullptr);
}
// Test that any combination of two features is invalid.
for (size_t i = 0; i < fenceFeatures.size(); ++i) {
for (size_t j = i + 1; j < fenceFeatures.size(); ++j) {
wgpu::FeatureName features[] = {fenceFeatures[i], fenceFeatures[j]};
wgpu::DeviceDescriptor deviceDesc;
deviceDesc.requiredFeatureCount = 2;
deviceDesc.requiredFeatures = features;
EXPECT_EQ(adapter.CreateDevice(&deviceDesc), nullptr);
}
}
}
DAWN_INSTANTIATE_TEST(SharedTextureMemoryVulkanTests, VulkanBackend());
} // anonymous namespace
} // namespace dawn