blob: c2ac26f2ac2bc0daefb58ada4d2ba20a6bb287e0 [file] [log] [blame]
// Copyright 2019 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "dawn_native/CommandValidation.h"
#include "common/BitSetIterator.h"
#include "dawn_native/BindGroup.h"
#include "dawn_native/Buffer.h"
#include "dawn_native/CommandBufferStateTracker.h"
#include "dawn_native/Commands.h"
#include "dawn_native/Device.h"
#include "dawn_native/PassResourceUsage.h"
#include "dawn_native/QuerySet.h"
#include "dawn_native/RenderBundle.h"
#include "dawn_native/RenderPipeline.h"
namespace dawn_native {
namespace {
inline MaybeError ValidateRenderBundleCommand(CommandIterator* commands,
Command type,
CommandBufferStateTracker* commandBufferState,
const AttachmentState* attachmentState,
uint64_t* debugGroupStackSize,
const char* disallowedMessage) {
switch (type) {
case Command::Draw: {
commands->NextCommand<DrawCmd>();
DAWN_TRY(commandBufferState->ValidateCanDraw());
break;
}
case Command::DrawIndexed: {
commands->NextCommand<DrawIndexedCmd>();
DAWN_TRY(commandBufferState->ValidateCanDrawIndexed());
break;
}
case Command::DrawIndirect: {
commands->NextCommand<DrawIndirectCmd>();
DAWN_TRY(commandBufferState->ValidateCanDraw());
break;
}
case Command::DrawIndexedIndirect: {
commands->NextCommand<DrawIndexedIndirectCmd>();
DAWN_TRY(commandBufferState->ValidateCanDrawIndexed());
break;
}
case Command::InsertDebugMarker: {
InsertDebugMarkerCmd* cmd = commands->NextCommand<InsertDebugMarkerCmd>();
commands->NextData<char>(cmd->length + 1);
break;
}
case Command::PopDebugGroup: {
commands->NextCommand<PopDebugGroupCmd>();
DAWN_TRY(ValidateCanPopDebugGroup(*debugGroupStackSize));
*debugGroupStackSize -= 1;
break;
}
case Command::PushDebugGroup: {
PushDebugGroupCmd* cmd = commands->NextCommand<PushDebugGroupCmd>();
commands->NextData<char>(cmd->length + 1);
*debugGroupStackSize += 1;
break;
}
case Command::SetRenderPipeline: {
SetRenderPipelineCmd* cmd = commands->NextCommand<SetRenderPipelineCmd>();
RenderPipelineBase* pipeline = cmd->pipeline.Get();
if (DAWN_UNLIKELY(pipeline->GetAttachmentState() != attachmentState)) {
return DAWN_VALIDATION_ERROR("Pipeline attachment state is not compatible");
}
commandBufferState->SetRenderPipeline(pipeline);
break;
}
case Command::SetBindGroup: {
SetBindGroupCmd* cmd = commands->NextCommand<SetBindGroupCmd>();
if (cmd->dynamicOffsetCount > 0) {
commands->NextData<uint32_t>(cmd->dynamicOffsetCount);
}
commandBufferState->SetBindGroup(cmd->index, cmd->group.Get());
break;
}
case Command::SetIndexBuffer: {
SetIndexBufferCmd* cmd = commands->NextCommand<SetIndexBufferCmd>();
commandBufferState->SetIndexBuffer(cmd->format);
break;
}
case Command::SetVertexBuffer: {
SetVertexBufferCmd* cmd = commands->NextCommand<SetVertexBufferCmd>();
commandBufferState->SetVertexBuffer(cmd->slot);
break;
}
default:
return DAWN_VALIDATION_ERROR(disallowedMessage);
}
return {};
}
} // namespace
MaybeError ValidateCanPopDebugGroup(uint64_t debugGroupStackSize) {
if (debugGroupStackSize == 0) {
return DAWN_VALIDATION_ERROR("Pop must be balanced by a corresponding Push.");
}
return {};
}
MaybeError ValidateFinalDebugGroupStackSize(uint64_t debugGroupStackSize) {
if (debugGroupStackSize != 0) {
return DAWN_VALIDATION_ERROR("Each Push must be balanced by a corresponding Pop.");
}
return {};
}
MaybeError ValidateRenderBundle(CommandIterator* commands,
const AttachmentState* attachmentState) {
CommandBufferStateTracker commandBufferState;
uint64_t debugGroupStackSize = 0;
Command type;
while (commands->NextCommandId(&type)) {
DAWN_TRY(ValidateRenderBundleCommand(commands, type, &commandBufferState,
attachmentState, &debugGroupStackSize,
"Command disallowed inside a render bundle"));
}
DAWN_TRY(ValidateFinalDebugGroupStackSize(debugGroupStackSize));
return {};
}
MaybeError ValidateRenderPass(CommandIterator* commands, const BeginRenderPassCmd* renderPass) {
CommandBufferStateTracker commandBufferState;
uint64_t debugGroupStackSize = 0;
Command type;
while (commands->NextCommandId(&type)) {
switch (type) {
case Command::EndRenderPass: {
commands->NextCommand<EndRenderPassCmd>();
DAWN_TRY(ValidateFinalDebugGroupStackSize(debugGroupStackSize));
return {};
}
case Command::ExecuteBundles: {
ExecuteBundlesCmd* cmd = commands->NextCommand<ExecuteBundlesCmd>();
auto bundles = commands->NextData<Ref<RenderBundleBase>>(cmd->count);
for (uint32_t i = 0; i < cmd->count; ++i) {
if (DAWN_UNLIKELY(renderPass->attachmentState.Get() !=
bundles[i]->GetAttachmentState())) {
return DAWN_VALIDATION_ERROR(
"Render bundle is not compatible with render pass");
}
}
if (cmd->count > 0) {
// Reset state. It is invalidated after render bundle execution.
commandBufferState = CommandBufferStateTracker{};
}
break;
}
case Command::SetStencilReference: {
commands->NextCommand<SetStencilReferenceCmd>();
break;
}
case Command::SetBlendColor: {
commands->NextCommand<SetBlendColorCmd>();
break;
}
case Command::SetViewport: {
commands->NextCommand<SetViewportCmd>();
break;
}
case Command::SetScissorRect: {
commands->NextCommand<SetScissorRectCmd>();
break;
}
case Command::WriteTimestamp: {
commands->NextCommand<WriteTimestampCmd>();
break;
}
default:
DAWN_TRY(ValidateRenderBundleCommand(
commands, type, &commandBufferState, renderPass->attachmentState.Get(),
&debugGroupStackSize, "Command disallowed inside a render pass"));
}
}
UNREACHABLE();
return DAWN_VALIDATION_ERROR("Unfinished render pass");
}
MaybeError ValidateComputePass(CommandIterator* commands) {
CommandBufferStateTracker commandBufferState;
uint64_t debugGroupStackSize = 0;
Command type;
while (commands->NextCommandId(&type)) {
switch (type) {
case Command::EndComputePass: {
commands->NextCommand<EndComputePassCmd>();
DAWN_TRY(ValidateFinalDebugGroupStackSize(debugGroupStackSize));
return {};
}
case Command::Dispatch: {
commands->NextCommand<DispatchCmd>();
DAWN_TRY(commandBufferState.ValidateCanDispatch());
break;
}
case Command::DispatchIndirect: {
commands->NextCommand<DispatchIndirectCmd>();
DAWN_TRY(commandBufferState.ValidateCanDispatch());
break;
}
case Command::InsertDebugMarker: {
InsertDebugMarkerCmd* cmd = commands->NextCommand<InsertDebugMarkerCmd>();
commands->NextData<char>(cmd->length + 1);
break;
}
case Command::PopDebugGroup: {
commands->NextCommand<PopDebugGroupCmd>();
DAWN_TRY(ValidateCanPopDebugGroup(debugGroupStackSize));
debugGroupStackSize--;
break;
}
case Command::PushDebugGroup: {
PushDebugGroupCmd* cmd = commands->NextCommand<PushDebugGroupCmd>();
commands->NextData<char>(cmd->length + 1);
debugGroupStackSize++;
break;
}
case Command::SetComputePipeline: {
SetComputePipelineCmd* cmd = commands->NextCommand<SetComputePipelineCmd>();
ComputePipelineBase* pipeline = cmd->pipeline.Get();
commandBufferState.SetComputePipeline(pipeline);
break;
}
case Command::SetBindGroup: {
SetBindGroupCmd* cmd = commands->NextCommand<SetBindGroupCmd>();
if (cmd->dynamicOffsetCount > 0) {
commands->NextData<uint32_t>(cmd->dynamicOffsetCount);
}
commandBufferState.SetBindGroup(cmd->index, cmd->group.Get());
break;
}
case Command::WriteTimestamp: {
commands->NextCommand<WriteTimestampCmd>();
break;
}
default:
return DAWN_VALIDATION_ERROR("Command disallowed inside a compute pass");
}
}
UNREACHABLE();
return DAWN_VALIDATION_ERROR("Unfinished compute pass");
}
// Performs the per-pass usage validation checks
// This will eventually need to differentiate between render and compute passes.
// It will be valid to use a buffer both as uniform and storage in the same compute pass.
MaybeError ValidatePassResourceUsage(const PassResourceUsage& pass) {
// Buffers can only be used as single-write or multiple read.
for (size_t i = 0; i < pass.buffers.size(); ++i) {
const BufferBase* buffer = pass.buffers[i];
wgpu::BufferUsage usage = pass.bufferUsages[i];
if (usage & ~buffer->GetUsage()) {
return DAWN_VALIDATION_ERROR("Buffer missing usage for the pass");
}
bool readOnly = (usage & kReadOnlyBufferUsages) == usage;
bool singleUse = wgpu::HasZeroOrOneBits(usage);
if (pass.passType == PassType::Render && !readOnly && !singleUse) {
return DAWN_VALIDATION_ERROR(
"Buffer used as writable usage and another usage in pass");
}
}
// Textures can only be used as single-write or multiple read.
for (size_t i = 0; i < pass.textures.size(); ++i) {
const TextureBase* texture = pass.textures[i];
const PassTextureUsage& textureUsage = pass.textureUsages[i];
wgpu::TextureUsage usage = textureUsage.usage;
if (usage & ~texture->GetUsage()) {
return DAWN_VALIDATION_ERROR("Texture missing usage for the pass");
}
// TODO (yunchao.he@intel.com): add read/write usage tracking for compute
// The usage variable for the whole texture is a fast path for texture usage tracking.
// Because in most cases a texture (with or without subresources) is used as
// single-write or multiple read, then we can skip iterating the subresources' usages.
bool readOnly = (usage & kReadOnlyTextureUsages) == usage;
bool singleUse = wgpu::HasZeroOrOneBits(usage);
if (pass.passType != PassType::Render || readOnly || singleUse) {
continue;
}
// Inspect the subresources if the usage of the whole texture violates usage validation.
// Every single subresource can only be used as single-write or multiple read.
for (wgpu::TextureUsage subresourceUsage : textureUsage.subresourceUsages) {
bool readOnly = (subresourceUsage & kReadOnlyTextureUsages) == subresourceUsage;
bool singleUse = wgpu::HasZeroOrOneBits(subresourceUsage);
if (!readOnly && !singleUse) {
return DAWN_VALIDATION_ERROR(
"Texture used as writable usage and another usage in render pass");
}
}
}
return {};
}
MaybeError ValidateTimestampQuery(QuerySetBase* querySet, uint32_t queryIndex) {
if (querySet->GetQueryType() != wgpu::QueryType::Timestamp) {
return DAWN_VALIDATION_ERROR("The query type of query set must be Timestamp");
}
if (queryIndex >= querySet->GetQueryCount()) {
return DAWN_VALIDATION_ERROR("Query index exceeds the number of queries in query set");
}
return {};
}
bool IsRangeOverlapped(uint32_t startA, uint32_t startB, uint32_t length) {
uint32_t maxStart = std::max(startA, startB);
uint32_t minStart = std::min(startA, startB);
return static_cast<uint64_t>(minStart) + static_cast<uint64_t>(length) >
static_cast<uint64_t>(maxStart);
}
ResultOrError<uint64_t> ComputeRequiredBytesInCopy(const TexelBlockInfo& blockInfo,
const Extent3D& copySize,
uint32_t bytesPerRow,
uint32_t rowsPerImage) {
// Default value for rowsPerImage
if (rowsPerImage == 0) {
rowsPerImage = copySize.height;
}
ASSERT(rowsPerImage >= copySize.height);
if (copySize.height > 1 || copySize.depth > 1) {
ASSERT(bytesPerRow >= copySize.width / blockInfo.blockWidth * blockInfo.blockByteSize);
}
if (copySize.width == 0 || copySize.height == 0 || copySize.depth == 0) {
return 0;
}
ASSERT(copySize.height >= 1);
ASSERT(copySize.depth >= 1);
uint32_t texelBlockRowsPerImage = rowsPerImage / blockInfo.blockHeight;
// bytesPerImage won't overflow since we're multiplying two uint32_t numbers
uint64_t bytesPerImage = uint64_t(texelBlockRowsPerImage) * bytesPerRow;
// Provided that copySize.height > 1: bytesInLastSlice won't overflow since it's at most
// bytesPerImage. Otherwise the result is a multiplication of two uint32_t numbers.
uint64_t bytesInLastSlice =
uint64_t(bytesPerRow) * (copySize.height / blockInfo.blockHeight - 1) +
(uint64_t(copySize.width) / blockInfo.blockWidth * blockInfo.blockByteSize);
// This error cannot be thrown for copySize.depth = 1.
// For copySize.depth > 1 we know that:
// requiredBytesInCopy >= (copySize.depth * bytesPerImage) / 2, so if
// copySize.depth * bytesPerImage overflows uint64_t, then requiredBytesInCopy is definitely
// too large to fit in the available data size.
if (std::numeric_limits<uint64_t>::max() / copySize.depth < bytesPerImage) {
return DAWN_VALIDATION_ERROR("requiredBytesInCopy is too large");
}
return bytesPerImage * (copySize.depth - 1) + bytesInLastSlice;
}
MaybeError ValidateCopySizeFitsInBuffer(const Ref<BufferBase>& buffer,
uint64_t offset,
uint64_t size) {
uint64_t bufferSize = buffer->GetSize();
bool fitsInBuffer = offset <= bufferSize && (size <= (bufferSize - offset));
if (!fitsInBuffer) {
return DAWN_VALIDATION_ERROR("Copy would overflow the buffer");
}
return {};
}
MaybeError ValidateLinearTextureData(const TextureDataLayout& layout,
uint64_t byteSize,
const TexelBlockInfo& blockInfo,
const Extent3D& copyExtent) {
// Validation for the texel block alignments:
if (layout.rowsPerImage % blockInfo.blockHeight != 0) {
return DAWN_VALIDATION_ERROR(
"rowsPerImage must be a multiple of compressed texture format block height");
}
if (layout.offset % blockInfo.blockByteSize != 0) {
return DAWN_VALIDATION_ERROR("Offset must be a multiple of the texel or block size");
}
// Validation for other members in layout:
if ((copyExtent.height > 1 || copyExtent.depth > 1) &&
layout.bytesPerRow <
copyExtent.width / blockInfo.blockWidth * blockInfo.blockByteSize) {
return DAWN_VALIDATION_ERROR(
"bytesPerRow must not be less than the number of bytes per row");
}
// TODO(tommek@google.com): to match the spec there should be another condition here
// on rowsPerImage >= copyExtent.height if copyExtent.depth > 1.
// Validation for the copy being in-bounds:
if (layout.rowsPerImage != 0 && layout.rowsPerImage < copyExtent.height) {
return DAWN_VALIDATION_ERROR("rowsPerImage must not be less than the copy height.");
}
// We compute required bytes in copy after validating texel block alignments
// because the divisibility conditions are necessary for the algorithm to be valid,
// also the bytesPerRow bound is necessary to avoid overflows.
uint64_t requiredBytesInCopy;
DAWN_TRY_ASSIGN(requiredBytesInCopy,
ComputeRequiredBytesInCopy(blockInfo, copyExtent, layout.bytesPerRow,
layout.rowsPerImage));
bool fitsInData =
layout.offset <= byteSize && (requiredBytesInCopy <= (byteSize - layout.offset));
if (!fitsInData) {
return DAWN_VALIDATION_ERROR(
"Required size for texture data layout exceeds the given size");
}
return {};
}
MaybeError ValidateBufferCopyView(DeviceBase const* device,
const BufferCopyView& bufferCopyView) {
DAWN_TRY(device->ValidateObject(bufferCopyView.buffer));
if (bufferCopyView.layout.bytesPerRow % kTextureBytesPerRowAlignment != 0) {
return DAWN_VALIDATION_ERROR("bytesPerRow must be a multiple of 256");
}
return {};
}
MaybeError ValidateTextureCopyView(DeviceBase const* device,
const TextureCopyView& textureCopy,
const Extent3D& copySize) {
const TextureBase* texture = textureCopy.texture;
DAWN_TRY(device->ValidateObject(texture));
if (textureCopy.mipLevel >= texture->GetNumMipLevels()) {
return DAWN_VALIDATION_ERROR("mipLevel out of range");
}
switch (textureCopy.aspect) {
case wgpu::TextureAspect::All:
break;
case wgpu::TextureAspect::DepthOnly:
if ((texture->GetFormat().aspects & Aspect::Depth) == 0) {
return DAWN_VALIDATION_ERROR(
"Texture does not have depth aspect for texture copy");
}
break;
case wgpu::TextureAspect::StencilOnly:
if ((texture->GetFormat().aspects & Aspect::Stencil) == 0) {
return DAWN_VALIDATION_ERROR(
"Texture does not have stencil aspect for texture copy");
}
break;
default:
UNREACHABLE();
break;
}
if (texture->GetSampleCount() > 1 || texture->GetFormat().HasDepthOrStencil()) {
Extent3D subresourceSize = texture->GetMipLevelPhysicalSize(textureCopy.mipLevel);
ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D);
if (textureCopy.origin.x != 0 || textureCopy.origin.y != 0 ||
subresourceSize.width != copySize.width ||
subresourceSize.height != copySize.height) {
return DAWN_VALIDATION_ERROR(
"The entire subresource must be copied when using a depth/stencil texture, or "
"when sample count is greater than 1.");
}
}
return {};
}
MaybeError ValidateTextureCopyRange(const TextureCopyView& textureCopy,
const Extent3D& copySize) {
// TODO(jiawei.shao@intel.com): add validations on the texture-to-texture copies within the
// same texture.
const TextureBase* texture = textureCopy.texture;
// Validation for the copy being in-bounds:
Extent3D mipSize = texture->GetMipLevelPhysicalSize(textureCopy.mipLevel);
// For 2D textures, include the array layer as depth so it can be checked with other
// dimensions.
ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D);
mipSize.depth = texture->GetArrayLayers();
// All texture dimensions are in uint32_t so by doing checks in uint64_t we avoid
// overflows.
if (static_cast<uint64_t>(textureCopy.origin.x) + static_cast<uint64_t>(copySize.width) >
static_cast<uint64_t>(mipSize.width) ||
static_cast<uint64_t>(textureCopy.origin.y) + static_cast<uint64_t>(copySize.height) >
static_cast<uint64_t>(mipSize.height) ||
static_cast<uint64_t>(textureCopy.origin.z) + static_cast<uint64_t>(copySize.depth) >
static_cast<uint64_t>(mipSize.depth)) {
return DAWN_VALIDATION_ERROR("Touching outside of the texture");
}
// Validation for the texel block alignments:
const TexelBlockInfo& blockInfo =
textureCopy.texture->GetFormat().GetTexelBlockInfo(textureCopy.aspect);
if (textureCopy.origin.x % blockInfo.blockWidth != 0) {
return DAWN_VALIDATION_ERROR(
"Offset.x must be a multiple of compressed texture format block width");
}
if (textureCopy.origin.y % blockInfo.blockHeight != 0) {
return DAWN_VALIDATION_ERROR(
"Offset.y must be a multiple of compressed texture format block height");
}
if (copySize.width % blockInfo.blockWidth != 0) {
return DAWN_VALIDATION_ERROR(
"copySize.width must be a multiple of compressed texture format block width");
}
if (copySize.height % blockInfo.blockHeight != 0) {
return DAWN_VALIDATION_ERROR(
"copySize.height must be a multiple of compressed texture format block height");
}
return {};
}
MaybeError ValidateBufferToTextureCopyRestrictions(const TextureCopyView& dst) {
const Format& format = dst.texture->GetFormat();
bool depthSelected = false;
switch (dst.aspect) {
case wgpu::TextureAspect::All:
switch (format.aspects) {
case Aspect::Color:
case Aspect::Stencil:
break;
case Aspect::Depth:
depthSelected = true;
break;
default:
return DAWN_VALIDATION_ERROR(
"A single aspect must be selected for multi planar formats in buffer "
"to texture copies");
}
break;
case wgpu::TextureAspect::DepthOnly:
ASSERT(format.aspects & Aspect::Depth);
depthSelected = true;
break;
case wgpu::TextureAspect::StencilOnly:
ASSERT(format.aspects & Aspect::Stencil);
break;
}
if (depthSelected) {
return DAWN_VALIDATION_ERROR("Cannot copy into the depth aspect of a texture");
}
return {};
}
} // namespace dawn_native