Skip to content

Commit f601680

Browse files
committed
Add early-exit validation to MapAsync for already mapped buffers and test case
1 parent 21b9aa6 commit f601680

File tree

2 files changed

+70
-0
lines changed

2 files changed

+70
-0
lines changed

src/dawn/native/Buffer.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,34 @@ Future BufferBase::APIMapAsync(wgpu::MapMode mode,
574574
{
575575
auto deviceGuard = GetDevice()->GetGuard();
576576

577+
// Early-exit validation: prevent double-mapping of the same buffer.
578+
//
579+
// According to the WebGPU specification, a buffer must not be mapped more than
580+
// once at a time. If a buffer is already in a Mapped or MappedAtCreation state,
581+
// calling MapAsync() again is invalid.
582+
//
583+
// Instead of deferring to the full validation path (ValidateMapAsync),
584+
// we return early here with a warning and an error callback to improve
585+
// developer feedback and reduce unnecessary work.
586+
587+
BufferState currentState = mState.load(std::memory_order::acquire);
588+
if (currentState == BufferState::Mapped || currentState == BufferState::MappedAtCreation) {
589+
// Log a warning for developer visibility. This does not raise a device error.
590+
dawn::EmitWarning(GetDevice(),
591+
"Attempted to map a buffer that is already mapped. "
592+
"Call Unmap() before calling MapAsync again.");
593+
594+
// Trigger the callback immediately with an error status.
595+
// This provides synchronous feedback for invalid usage.
596+
if (callbackInfo.callback != nullptr) {
597+
callbackInfo.callback(WGPUMapAsyncStatus_Error, callbackInfo.userdata);
598+
}
599+
600+
// Return a null future. No event is tracked for this invalid operation.
601+
return Future::MakeEmpty();
602+
}
603+
604+
577605
// Handle the defaulting of size required by WebGPU, even if in webgpu_cpp.h it is not
578606
// possible to default the function argument (because there is the callback later in the
579607
// argument list)
@@ -614,6 +642,9 @@ Future BufferBase::APIMapAsync(wgpu::MapMode mode,
614642
return {futureID};
615643
}
616644

645+
646+
//****************************************************************************************************** */
647+
617648
void* BufferBase::APIGetMappedRange(size_t offset, size_t size) {
618649
return GetMappedRange(offset, size, true);
619650
}

src/dawn/tests/end2end/BufferTests.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,6 +1230,45 @@ TEST_P(BufferTests, CreateErrorBuffer) {
12301230
ASSERT_EQ(buffer, nullptr);
12311231
}
12321232

1233+
// Test that MapAsync fails if called twice on the same buffer without unmapping.
1234+
TEST_P(BufferValidationTests, MapAsyncFailsWhenAlreadyMapped) {
1235+
// Create a writable buffer with MapWrite and CopyDst usage.
1236+
wgpu::BufferDescriptor desc;
1237+
desc.size = 64;
1238+
desc.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopyDst;
1239+
1240+
wgpu::Buffer buffer = device.CreateBuffer(&desc);
1241+
1242+
// First call to MapAsync should succeed.
1243+
bool callback1Called = false;
1244+
buffer.MapAsync(wgpu::MapMode::Write, 0, 64,
1245+
[&](WGPUMapAsyncStatus status) {
1246+
// Expect the first mapping to succeed.
1247+
EXPECT_EQ(status, WGPUMapAsyncStatus_Success);
1248+
callback1Called = true;
1249+
});
1250+
1251+
// Immediately call MapAsync again without unmapping.
1252+
bool callback2Called = false;
1253+
buffer.MapAsync(wgpu::MapMode::Write, 0, 64,
1254+
[&](WGPUMapAsyncStatus status) {
1255+
// The second mapping should fail and return an error.
1256+
EXPECT_EQ(status, WGPUMapAsyncStatus_Error);
1257+
callback2Called = true;
1258+
});
1259+
1260+
// Process the commands and trigger callbacks.
1261+
device.Tick();
1262+
1263+
// Ensure both callbacks were actually called.
1264+
EXPECT_TRUE(callback1Called);
1265+
EXPECT_TRUE(callback2Called);
1266+
1267+
// Cleanup: Unmap the buffer after use.
1268+
buffer.Unmap();
1269+
}
1270+
1271+
12331272
// Test that mapping an OOM buffer fails gracefully
12341273
TEST_P(BufferTests, CreateBufferOOMMapAsync) {
12351274
// TODO(crbug.com/346377856): fails on ANGLE/D3D11, but is likely a Dawn/GL bug that only

0 commit comments

Comments
 (0)