mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-12-15 08:06:19 +00:00
Add/update destruction handling for command encoding objects
- Renames ProgrammablePassEncoder to just ProgrammableEncoder since it is also used in RenderBundleEncoder which is not a "pass" - Adds testing infrastructure to further test device errors - Ensures AttachmentStates are de-reffed when encoder objects are destroyed for proper cleanup - Makes sure that both encoded and partial encoded commands are freed at destruction Bug: dawn:628 Change-Id: Id62ab02d54461c4da266963035e8666799f61e9a Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/68461 Reviewed-by: Corentin Wallez <cwallez@chromium.org> Reviewed-by: Austin Eng <enga@chromium.org> Commit-Queue: Loko Kung <lokokung@google.com>
This commit is contained in:
@@ -12,10 +12,16 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include "dawn_native/CommandEncoder.h"
|
||||
|
||||
#include "tests/unittests/validation/ValidationTest.h"
|
||||
|
||||
#include "utils/WGPUHelpers.h"
|
||||
|
||||
using ::testing::HasSubstr;
|
||||
|
||||
class CommandBufferValidationTest : public ValidationTest {};
|
||||
|
||||
// Test for an empty command buffer
|
||||
@@ -39,7 +45,9 @@ TEST_F(CommandBufferValidationTest, EndedMidRenderPass) {
|
||||
{
|
||||
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
|
||||
ASSERT_DEVICE_ERROR(encoder.Finish());
|
||||
ASSERT_DEVICE_ERROR(
|
||||
encoder.Finish(),
|
||||
HasSubstr("Command buffer recording ended before [RenderPassEncoder] was ended."));
|
||||
}
|
||||
|
||||
// Error case, command buffer ended mid-pass. Trying to use encoders after Finish
|
||||
@@ -47,8 +55,12 @@ TEST_F(CommandBufferValidationTest, EndedMidRenderPass) {
|
||||
{
|
||||
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
|
||||
ASSERT_DEVICE_ERROR(encoder.Finish());
|
||||
ASSERT_DEVICE_ERROR(pass.EndPass());
|
||||
ASSERT_DEVICE_ERROR(
|
||||
encoder.Finish(),
|
||||
HasSubstr("Command buffer recording ended before [RenderPassEncoder] was ended."));
|
||||
ASSERT_DEVICE_ERROR(
|
||||
pass.EndPass(),
|
||||
HasSubstr("Recording in an error or already ended [RenderPassEncoder]."));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +78,9 @@ TEST_F(CommandBufferValidationTest, EndedMidComputePass) {
|
||||
{
|
||||
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
|
||||
ASSERT_DEVICE_ERROR(encoder.Finish());
|
||||
ASSERT_DEVICE_ERROR(
|
||||
encoder.Finish(),
|
||||
HasSubstr("Command buffer recording ended before [ComputePassEncoder] was ended."));
|
||||
}
|
||||
|
||||
// Error case, command buffer ended mid-pass. Trying to use encoders after Finish
|
||||
@@ -74,8 +88,12 @@ TEST_F(CommandBufferValidationTest, EndedMidComputePass) {
|
||||
{
|
||||
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
|
||||
ASSERT_DEVICE_ERROR(encoder.Finish());
|
||||
ASSERT_DEVICE_ERROR(pass.EndPass());
|
||||
ASSERT_DEVICE_ERROR(
|
||||
encoder.Finish(),
|
||||
HasSubstr("Command buffer recording ended before [ComputePassEncoder] was ended."));
|
||||
ASSERT_DEVICE_ERROR(
|
||||
pass.EndPass(),
|
||||
HasSubstr("Recording in an error or already ended [ComputePassEncoder]."));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +115,9 @@ TEST_F(CommandBufferValidationTest, RenderPassEndedTwice) {
|
||||
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
|
||||
pass.EndPass();
|
||||
pass.EndPass();
|
||||
ASSERT_DEVICE_ERROR(encoder.Finish());
|
||||
ASSERT_DEVICE_ERROR(
|
||||
encoder.Finish(),
|
||||
HasSubstr("Recording in an error or already ended [RenderPassEncoder]."));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +137,9 @@ TEST_F(CommandBufferValidationTest, ComputePassEndedTwice) {
|
||||
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
|
||||
pass.EndPass();
|
||||
pass.EndPass();
|
||||
ASSERT_DEVICE_ERROR(encoder.Finish());
|
||||
ASSERT_DEVICE_ERROR(
|
||||
encoder.Finish(),
|
||||
HasSubstr("Recording in an error or already ended [ComputePassEncoder]."));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,14 +245,18 @@ TEST_F(CommandBufferValidationTest, PassDereferenced) {
|
||||
{
|
||||
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||
encoder.BeginRenderPass(&dummyRenderPass);
|
||||
ASSERT_DEVICE_ERROR(encoder.Finish());
|
||||
ASSERT_DEVICE_ERROR(
|
||||
encoder.Finish(),
|
||||
HasSubstr("Command buffer recording ended before [RenderPassEncoder] was ended."));
|
||||
}
|
||||
|
||||
// Error case, no reference is kept to a compute pass.
|
||||
{
|
||||
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||
encoder.BeginComputePass();
|
||||
ASSERT_DEVICE_ERROR(encoder.Finish());
|
||||
ASSERT_DEVICE_ERROR(
|
||||
encoder.Finish(),
|
||||
HasSubstr("Command buffer recording ended before [ComputePassEncoder] was ended."));
|
||||
}
|
||||
|
||||
// Error case, beginning a new pass after failing to end a de-referenced pass.
|
||||
@@ -239,21 +265,25 @@ TEST_F(CommandBufferValidationTest, PassDereferenced) {
|
||||
encoder.BeginRenderPass(&dummyRenderPass);
|
||||
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
|
||||
pass.EndPass();
|
||||
ASSERT_DEVICE_ERROR(encoder.Finish());
|
||||
ASSERT_DEVICE_ERROR(
|
||||
encoder.Finish(),
|
||||
HasSubstr("Command buffer recording ended before [RenderPassEncoder] was ended."));
|
||||
}
|
||||
|
||||
// Error case, deleting the pass after finishing the commend encoder shouldn't generate an
|
||||
// Error case, deleting the pass after finishing the command encoder shouldn't generate an
|
||||
// uncaptured error.
|
||||
{
|
||||
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
|
||||
ASSERT_DEVICE_ERROR(encoder.Finish());
|
||||
ASSERT_DEVICE_ERROR(
|
||||
encoder.Finish(),
|
||||
HasSubstr("Command buffer recording ended before [ComputePassEncoder] was ended."));
|
||||
|
||||
pass = nullptr;
|
||||
}
|
||||
|
||||
// Valid case, command encoder is never finished so the de-referenced pass shouldn't generate an
|
||||
// uncaptured error.
|
||||
// Valid case, command encoder is never finished so the de-referenced pass shouldn't
|
||||
// generate an uncaptured error.
|
||||
{
|
||||
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||
encoder.BeginComputePass();
|
||||
@@ -264,5 +294,80 @@ TEST_F(CommandBufferValidationTest, PassDereferenced) {
|
||||
TEST_F(CommandBufferValidationTest, InjectValidationError) {
|
||||
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||
encoder.InjectValidationError("my error");
|
||||
ASSERT_DEVICE_ERROR(encoder.Finish());
|
||||
ASSERT_DEVICE_ERROR(encoder.Finish(), HasSubstr("my error"));
|
||||
}
|
||||
|
||||
TEST_F(CommandBufferValidationTest, DestroyEncoder) {
|
||||
// Skip these tests if we are using wire because the destroy functionality is not exposed
|
||||
// and needs to use a cast to call manually. We cannot test this in the wire case since the
|
||||
// only way to trigger the destroy call is by losing all references which means we cannot
|
||||
// call finish.
|
||||
DAWN_SKIP_TEST_IF(UsesWire());
|
||||
DummyRenderPass dummyRenderPass(device);
|
||||
|
||||
// Control case, command buffer ended after the pass is ended.
|
||||
{
|
||||
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
|
||||
pass.EndPass();
|
||||
encoder.Finish();
|
||||
}
|
||||
|
||||
// Destroyed encoder with encoded commands should emit error on finish.
|
||||
{
|
||||
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
|
||||
pass.EndPass();
|
||||
reinterpret_cast<dawn_native::CommandEncoder*>(encoder.Get())->Destroy();
|
||||
ASSERT_DEVICE_ERROR(encoder.Finish(), HasSubstr("Destroyed encoder cannot be finished."));
|
||||
}
|
||||
|
||||
// Destroyed encoder with encoded commands shouldn't emit an error if never finished.
|
||||
{
|
||||
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
|
||||
pass.EndPass();
|
||||
reinterpret_cast<dawn_native::CommandEncoder*>(encoder.Get())->Destroy();
|
||||
}
|
||||
|
||||
// Destroyed encoder should allow encoding, and emit error on finish.
|
||||
{
|
||||
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||
reinterpret_cast<dawn_native::CommandEncoder*>(encoder.Get())->Destroy();
|
||||
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
|
||||
pass.EndPass();
|
||||
ASSERT_DEVICE_ERROR(encoder.Finish(), HasSubstr("Destroyed encoder cannot be finished."));
|
||||
}
|
||||
|
||||
// Destroyed encoder should allow encoding and shouldn't emit an error if never finished.
|
||||
{
|
||||
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||
reinterpret_cast<dawn_native::CommandEncoder*>(encoder.Get())->Destroy();
|
||||
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
|
||||
pass.EndPass();
|
||||
}
|
||||
|
||||
// Destroying a finished encoder should not emit any errors.
|
||||
{
|
||||
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
|
||||
pass.EndPass();
|
||||
encoder.Finish();
|
||||
reinterpret_cast<dawn_native::CommandEncoder*>(encoder.Get())->Destroy();
|
||||
}
|
||||
|
||||
// Destroying an encoder twice should not emit any errors.
|
||||
{
|
||||
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||
reinterpret_cast<dawn_native::CommandEncoder*>(encoder.Get())->Destroy();
|
||||
reinterpret_cast<dawn_native::CommandEncoder*>(encoder.Get())->Destroy();
|
||||
}
|
||||
|
||||
// Destroying an encoder twice and then calling finish should fail.
|
||||
{
|
||||
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
|
||||
reinterpret_cast<dawn_native::CommandEncoder*>(encoder.Get())->Destroy();
|
||||
reinterpret_cast<dawn_native::CommandEncoder*>(encoder.Get())->Destroy();
|
||||
ASSERT_DEVICE_ERROR(encoder.Finish(), HasSubstr("Destroyed encoder cannot be finished."));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,12 +129,19 @@ void ValidationTest::TearDown() {
|
||||
}
|
||||
}
|
||||
|
||||
void ValidationTest::StartExpectDeviceError() {
|
||||
void ValidationTest::StartExpectDeviceError(testing::Matcher<std::string> errorMatcher) {
|
||||
mExpectError = true;
|
||||
mError = false;
|
||||
mErrorMatcher = errorMatcher;
|
||||
}
|
||||
|
||||
void ValidationTest::StartExpectDeviceError() {
|
||||
StartExpectDeviceError(testing::_);
|
||||
}
|
||||
|
||||
bool ValidationTest::EndExpectDeviceError() {
|
||||
mExpectError = false;
|
||||
mErrorMatcher = testing::_;
|
||||
return mError;
|
||||
}
|
||||
std::string ValidationTest::GetLastDeviceErrorMessage() const {
|
||||
@@ -210,6 +217,9 @@ void ValidationTest::OnDeviceError(WGPUErrorType type, const char* message, void
|
||||
|
||||
ASSERT_TRUE(self->mExpectError) << "Got unexpected device error: " << message;
|
||||
ASSERT_FALSE(self->mError) << "Got two errors in expect block";
|
||||
if (self->mExpectError) {
|
||||
ASSERT_THAT(message, self->mErrorMatcher);
|
||||
}
|
||||
self->mError = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,30 @@
|
||||
#include "dawn/webgpu_cpp.h"
|
||||
#include "dawn_native/DawnNative.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#define ASSERT_DEVICE_ERROR(statement) \
|
||||
FlushWire(); \
|
||||
// Argument helpers to allow macro overriding.
|
||||
#define UNIMPLEMENTED_MACRO(...) UNREACHABLE()
|
||||
#define GET_3RD_ARG_HELPER_(_1, _2, NAME, ...) NAME
|
||||
#define GET_3RD_ARG_(args) GET_3RD_ARG_HELPER_ args
|
||||
|
||||
// Overloaded to allow further validation of the error messages given an error is expected.
|
||||
// Especially useful to verify that the expected errors are occuring, not just any error.
|
||||
//
|
||||
// Example usages:
|
||||
// 1 Argument Case:
|
||||
// ASSERT_DEVICE_ERROR(FunctionThatExpectsError());
|
||||
//
|
||||
// 2 Argument Case:
|
||||
// ASSERT_DEVICE_ERROR(FunctionThatHasLongError(), HasSubstr("partial match"))
|
||||
// ASSERT_DEVICE_ERROR(FunctionThatHasShortError(), Eq("exact match"));
|
||||
#define ASSERT_DEVICE_ERROR(...) \
|
||||
GET_3RD_ARG_((__VA_ARGS__, ASSERT_DEVICE_ERROR_IMPL_2_, ASSERT_DEVICE_ERROR_IMPL_1_, \
|
||||
UNIMPLEMENTED_MACRO)) \
|
||||
(__VA_ARGS__)
|
||||
|
||||
#define ASSERT_DEVICE_ERROR_IMPL_1_(statement) \
|
||||
StartExpectDeviceError(); \
|
||||
statement; \
|
||||
FlushWire(); \
|
||||
@@ -32,6 +52,16 @@
|
||||
do { \
|
||||
} while (0)
|
||||
|
||||
#define ASSERT_DEVICE_ERROR_IMPL_2_(statement, matcher) \
|
||||
StartExpectDeviceError(matcher); \
|
||||
statement; \
|
||||
FlushWire(); \
|
||||
if (!EndExpectDeviceError()) { \
|
||||
FAIL() << "Expected device error in:\n " << #statement; \
|
||||
} \
|
||||
do { \
|
||||
} while (0)
|
||||
|
||||
// Skip a test when the given condition is satisfied.
|
||||
#define DAWN_SKIP_TEST_IF(condition) \
|
||||
do { \
|
||||
@@ -69,6 +99,7 @@ class ValidationTest : public testing::Test {
|
||||
void SetUp() override;
|
||||
void TearDown() override;
|
||||
|
||||
void StartExpectDeviceError(testing::Matcher<std::string> errorMatcher);
|
||||
void StartExpectDeviceError();
|
||||
bool EndExpectDeviceError();
|
||||
std::string GetLastDeviceErrorMessage() const;
|
||||
@@ -118,6 +149,7 @@ class ValidationTest : public testing::Test {
|
||||
std::string mDeviceErrorMessage;
|
||||
bool mExpectError = false;
|
||||
bool mError = false;
|
||||
testing::Matcher<std::string> mErrorMatcher;
|
||||
};
|
||||
|
||||
#endif // TESTS_UNITTESTS_VALIDATIONTEST_H_
|
||||
|
||||
Reference in New Issue
Block a user