Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 169 additions & 53 deletions src/webgpu/api/operation/command_buffer/queries/timestampQuery.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,40 @@ import { AllFeaturesMaxLimitsGPUTest, GPUTest } from '../../../../gpu_test.js';

export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest);

function encodeTimestampQueries(
encoder: GPUCommandEncoder,
view: GPUTextureView,
querySet: GPUQuerySet,
stage: 'compute' | 'render',
slot: number
) {
switch (stage) {
case 'compute': {
const pass = encoder.beginComputePass({
timestampWrites: {
querySet,
beginningOfPassWriteIndex: slot,
endOfPassWriteIndex: slot + 1,
},
});
pass.end();
break;
}
case 'render': {
const pass = encoder.beginRenderPass({
colorAttachments: [{ view, loadOp: 'load', storeOp: 'store' }],
timestampWrites: {
querySet,
beginningOfPassWriteIndex: slot,
endOfPassWriteIndex: slot + 1,
},
});
pass.end();
break;
}
}
}

g.test('many_query_sets')
.desc(
`
Expand Down Expand Up @@ -61,31 +95,7 @@ and prevent pages from running.
count: 2,
});

switch (stage) {
case 'compute': {
const pass = encoder.beginComputePass({
timestampWrites: {
querySet,
beginningOfPassWriteIndex: 0,
endOfPassWriteIndex: 1,
},
});
pass.end();
break;
}
case 'render': {
const pass = encoder.beginRenderPass({
colorAttachments: [{ view, loadOp: 'load', storeOp: 'store' }],
timestampWrites: {
querySet,
beginningOfPassWriteIndex: 0,
endOfPassWriteIndex: 1,
},
});
pass.end();
break;
}
}
encodeTimestampQueries(encoder, view, querySet, stage, 0);
}

const shouldError = false; // just expect no error
Expand Down Expand Up @@ -119,34 +129,8 @@ function encoderQueryUsage(
count: numSlots,
});

switch (stage) {
case 'compute': {
for (let slot = 0; slot < numSlots; slot += 2) {
const pass = encoder.beginComputePass({
timestampWrites: {
querySet,
beginningOfPassWriteIndex: slot,
endOfPassWriteIndex: slot + 1,
},
});
pass.end();
}
break;
}
case 'render': {
for (let slot = 0; slot < numSlots; slot += 2) {
const pass = encoder.beginRenderPass({
colorAttachments: [{ view, loadOp: 'load', storeOp: 'store' }],
timestampWrites: {
querySet,
beginningOfPassWriteIndex: slot,
endOfPassWriteIndex: slot + 1,
},
});
pass.end();
}
break;
}
for (let slot = 0; slot < numSlots; slot += 2) {
encodeTimestampQueries(encoder, view, querySet, stage, slot);
}

return querySet;
Expand Down Expand Up @@ -217,3 +201,135 @@ to make sure the implementation doesn't mistakenly mark them as used.
t.expectGPUBufferValuesEqual(buffer, expected);
}
});

g.test('multi_resolve')
.desc(`Test resolving more than once does not change the results`)
.params(u => u.combine('stage', ['compute', 'render'] as const))
.fn(async t => {
const { stage } = t.params;
const kNumQueries = 64;

t.skipIfDeviceDoesNotHaveFeature('timestamp-query');

const querySet = t.createQuerySetTracked({
type: 'timestamp',
count: kNumQueries,
});

const view = t
.createTextureTracked({
size: [1, 1, 1],
format: 'rgba8unorm',
usage: GPUTextureUsage.RENDER_ATTACHMENT,
})
.createView();
const encoder = t.device.createCommandEncoder();

for (let slot = 0; slot < kNumQueries; slot += 2) {
// skip every other pair so we can test resolving un-used slots
const pair = (slot / 2) | 0;
if (pair % 2) {
continue;
}
encodeTimestampQueries(encoder, view, querySet, stage, slot);
}

const size = kNumQueries * 8;
const resolveBuffer1 = t.createBufferTracked({
size,
usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC,
});
const resolveBuffer2 = t.createBufferTracked({
size,
usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC,
});
const resultBuffer1 = t.createBufferTracked({
size,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
});

encoder.resolveQuerySet(querySet, 0, kNumQueries, resolveBuffer1, 0);
encoder.resolveQuerySet(querySet, 0, kNumQueries, resolveBuffer2, 0);
encoder.copyBufferToBuffer(resolveBuffer1, 0, resultBuffer1, 0, size);

t.device.queue.submit([encoder.finish()]);

// Read back the first result.
await resultBuffer1.mapAsync(GPUMapMode.READ);
const expected = new Uint32Array(resultBuffer1.getMappedRange());
t.expectGPUBufferValuesEqual(resolveBuffer2, expected);
});

g.test('unused_slots_are_zero')
.desc(`Test that unused slots are resolved to zero`)
.params(u => u.combine('stage', ['compute', 'render'] as const))
.fn(t => {
const { stage } = t.params;
const kNumQueries = 64;

t.skipIfDeviceDoesNotHaveFeature('timestamp-query');

const querySet = t.createQuerySetTracked({
type: 'timestamp',
count: kNumQueries,
});

const view = t
.createTextureTracked({
size: [1, 1, 1],
format: 'rgba8unorm',
usage: GPUTextureUsage.RENDER_ATTACHMENT,
})
.createView();
const usedEncoder = t.device.createCommandEncoder();
const unusedEncoder = t.device.createCommandEncoder();

for (let slot = 0; slot < kNumQueries; slot += 2) {
const pair = (slot / 2) | 0;
const encoder = pair % 2 ? usedEncoder : unusedEncoder;
encodeTimestampQueries(encoder, view, querySet, stage, slot);
}

unusedEncoder.finish(); // don't submit this encoder

const size = kNumQueries * 8;
const resolveBuffer = t.createBufferTracked({
size,
usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC,
});

usedEncoder.resolveQuerySet(querySet, 0, kNumQueries, resolveBuffer, 0);

t.device.queue.submit([usedEncoder.finish()]);

// Read back the first result.
t.expectGPUBufferValuesPassCheck(
resolveBuffer,
(actualU32: Uint32Array) => {
// MAINTENANCE_TODO: expectGPUBufferValuesPassCheck doesn't work with BigUint64Array.
const actual = new BigUint64Array(
actualU32.buffer,
actualU32.byteOffset,
actualU32.byteLength / 8
);
const errors: string[] = [];
for (let slot = 0; slot < kNumQueries; ++slot) {
t.debug(() => `slot ${slot}: ${actual[slot]}`);
const pair = (slot / 2) | 0;
if (pair % 2) {
// used slot, implementation defined so don't check
} else {
// unused slot, expect zero
if (actual[slot] !== 0n) {
errors.push(`slot ${slot} expected 0 but got ${actual[slot]}`);
}
}
}
return errors.length === 0 ? undefined : new Error(errors.join('\n'));
},
{
type: Uint32Array,
typedLength: kNumQueries * 2, // because it's really BigUint64Array data
}
);
});
Loading