Skip to content
Merged
Show file tree
Hide file tree
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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,9 @@ of 16 bytes for the pixel shader) before each of two draws, a color for each hal
indirectly (`drawIndirect`), from arguments the command list writes into a buffer: from offset
16 into one half, and from offset 0, no instance, into the other, which stays as cleared. A sixth
dispatches the compute pipeline indirectly (`dispatchIndirect`) the same way: from offset 12 into
one output buffer, and from offset 0, no group, into another, which stays as cleared.
one output buffer, and from offset 0, no group, into another, which stays as cleared. A seventh
uploads a texture through a staging texture the CPU writes (`mapStagingTexture` for writing,
`copyTexture`'s third overload) and reads it back, pixel for pixel.

It checks what it reads. On the way it also checks:
- the graphics API, the native objects and queues;
Expand Down
11 changes: 11 additions & 0 deletions core/nvrhi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,17 @@ export namespace nvrhi {
commandList.copyTexture(destArg, Ref(destSliceValue), srcArg, Ref(srcSliceValue));
}

// ICommandList::copyTexture of a staging texture into a texture, to upload it
export function copyTextureFromStaging(commandList: ICommandList, dest: ITexture, destSlice: TextureSlice,
src: IStagingTexture, srcSlice: TextureSlice): void {
// (as in copyTexture: TSLANG_ISSUES.md #19)
const destArg: ITexture | null = dest;
const srcArg: IStagingTexture | null = src;
let destSliceValue = destSlice;
let srcSliceValue = srcSlice;
commandList.copyTexture(destArg, Ref(destSliceValue), srcArg, Ref(srcSliceValue));
}

// A ShaderDesc as nvrhi::ShaderDesc's defaults make it, with the shader's stage (SHADER_TYPE_*),
// a debug name and its entry point's name (see stdString).
export function shaderDesc(shaderType: u16, debugName: string, entryName: string): ShaderDesc {
Expand Down
75 changes: 74 additions & 1 deletion examples/nvrhi_direct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ namespace NvrhiDirect {
// transparent color drawn over a cleared texture, alpha blended and added under a write mask;
// and another, push constants (testPushConstants): a color set before each of two draws; and
// another, indirect draws (testDrawIndirect): two draws' arguments from a buffer; and another,
// indirect dispatches (testDispatchIndirect) of the compute pipeline above.
// indirect dispatches (testDispatchIndirect) of the compute pipeline above; and another, an
// upload through a staging texture the CPU writes (testStagingUpload).
// It checks what it reads, and on the way the device's graphics API, its native objects,
// reference counting, debug names, the desc NVRHI keeps and the layouts of the structs it passes.

Expand Down Expand Up @@ -579,6 +580,77 @@ namespace NvrhiDirect {
skipped.Release();
}

// The upload staging texture's pixel (x, y): a different color for each
function uploadedPixel(x: int, y: int): u8[] {
return [<u8>(40 + 50 * x), <u8>(40 + 50 * y), <u8>(220 - 20 * x - 30 * y), 255];
}

// An upload through a staging texture, with a command list of its own: a staging texture the
// CPU writes (IDevice::createStagingTexture with CPU_ACCESS_WRITE, mapStagingTexture for writing,
// its rows rowPitch bytes apart), ICommandList::copyTexture from it into a texture (the third
// overload), and that read back through a staging texture: the same pixels.
function testStagingUpload(device: nvrhi.IDevice): void {
const upload = nvrhi.createStagingTexture(device, nvrhi.textureDesc(TEXTURE_SIZE, TEXTURE_SIZE,
Format.RGBA8_UNORM, "Upload texture", nvrhi.RESOURCE_STATE_COPY_SOURCE), nvrhi.CPU_ACCESS_WRITE);
const uploaded = nvrhi.createTexture(device, nvrhi.textureDesc(TEXTURE_SIZE, TEXTURE_SIZE,
Format.RGBA8_UNORM, "Uploaded", nvrhi.RESOURCE_STATE_COPY_DEST));
const readback = nvrhi.createStagingTexture(device, renderTargetDesc(), nvrhi.CPU_ACCESS_READ);
const commandList = nvrhi.createCommandList(device, nvrhi.commandListParameters());
if (!upload || !uploaded || !readback || !commandList) {
check(false, "the staging upload test's textures and command list");
return;
}

let rowPitch: index = 0;
const mapped = nvrhi.mapStagingTexture(device, upload, nvrhi.entireTexture(), nvrhi.CPU_ACCESS_WRITE, Ref(rowPitch));
if (!mapped || rowPitch < TEXTURE_SIZE * 4) {
check(false, `IDevice::mapStagingTexture for writing (row pitch ${rowPitch})`);
if (mapped) {
device.unmapStagingTexture(upload);
}
return;
}
const rows = <Ref<u8>>mapped;
for (let y = 0; y < TEXTURE_SIZE; y++) {
for (let x = 0; x < TEXTURE_SIZE; x++) {
const pixel = uploadedPixel(x, y);
for (let c = 0; c < 4; c++) {
Deref(rows[y * rowPitch + 4 * x + c]) = pixel[c];
}
}
}
device.unmapStagingTexture(upload);

commandList.open();
nvrhi.copyTextureFromStaging(commandList, uploaded, nvrhi.entireTexture(), upload, nvrhi.entireTexture());
nvrhi.copyTextureToStaging(commandList, readback, nvrhi.entireTexture(), uploaded, nvrhi.entireTexture());
commandList.close();
nvrhi.executeCommandList(device, commandList);
check(device.waitForIdle(), "IDevice::waitForIdle after the staging upload test");

const pixels = readStaging(device, readback);
if (pixels.length != 0) {
let mismatches = 0;
for (let y = 0; y < TEXTURE_SIZE; y++) {
for (let x = 0; x < TEXTURE_SIZE; x++) {
const i = 4 * (y * TEXTURE_SIZE + x);
const pixel = uploadedPixel(x, y);
if (pixels[i] != pixel[0] || pixels[i + 1] != pixel[1] || pixels[i + 2] != pixel[2] || pixels[i + 3] != pixel[3]) {
mismatches++;
}
}
}
const last = 4 * (TEXTURE_SIZE * TEXTURE_SIZE - 1);
console.log(`Uploaded (${pixels[0]}, ${pixels[1]}, ${pixels[2]}), ..., (${pixels[last]}, ${pixels[last + 1]}, ${pixels[last + 2]})`);
check(mismatches == 0, `ICommandList::copyTexture from a staging texture the CPU wrote (${mismatches} of ${TEXTURE_SIZE * TEXTURE_SIZE} pixels differ)`);
}

commandList.Release();
readback.Release();
uploaded.Release();
upload.Release();
}

function runTest(app: App, api: GraphicsAPI, validation: boolean): void {
// the tuples NVRHI reads as its structs: the same sizes as the C++ build's
check(nvrhi.layoutMatches(), "the tuples have the C++ structs' sizes");
Expand Down Expand Up @@ -952,6 +1024,7 @@ namespace NvrhiDirect {
testPushConstants(app, device, isD3D12);
testDrawIndirect(app, device, isD3D12);
testDispatchIndirect(device, pipeline, bindingLayout, input, data);
testStagingUpload(device);

// IResource::Release on what the create functions returned with a reference
commandList.Release();
Expand Down