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
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -331,8 +331,10 @@ Donut's wrappers elsewhere or with `-donut`. CTest runs both: `headless.<api>` a
depth test and no culling, a viewport and its scissor rectangle);
5. draws an indexed quad into its top right quarter from a vertex buffer (an input layout of
position and texture coordinates), sampling a 2 x 2 texture uploaded with `writeTexture` through
a point sampler, scaled by a constant buffer: one texel a pixel. The texture is read back through
Donut, its bottom right quarter as cleared.
a point sampler, scaled by a constant buffer: one texel a pixel. Its bottom right quarter is as
cleared;
6. copies the texture into another, and that into a staging texture (two of `copyTexture`'s
overloads), which it maps to read the pixels back, row by row (`mapStagingTexture`'s row pitch).

It checks what it reads. On the way it also checks:
- the graphics API, the native objects and queues;
Expand Down
2 changes: 2 additions & 0 deletions core/donut_interop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3328,6 +3328,8 @@ extern "C"
return int(sizeof(nvrhi::SamplerDesc));
if (n == "VertexAttributeDesc")
return int(sizeof(nvrhi::VertexAttributeDesc));
if (n == "TextureSlice")
return int(sizeof(nvrhi::TextureSlice));
return -1;
}

Expand Down
47 changes: 46 additions & 1 deletion core/nvrhi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ export namespace nvrhi {
&& sizeof<GraphicsState>() == Donut_GetNvrhiStructSize("GraphicsState")
&& sizeof<DrawArguments>() == Donut_GetNvrhiStructSize("DrawArguments")
&& sizeof<SamplerDesc>() == Donut_GetNvrhiStructSize("SamplerDesc")
&& sizeof<VertexAttributeDesc>() == Donut_GetNvrhiStructSize("VertexAttributeDesc");
&& sizeof<VertexAttributeDesc>() == Donut_GetNvrhiStructSize("VertexAttributeDesc")
&& sizeof<TextureSlice>() == Donut_GetNvrhiStructSize("TextureSlice");
}

// A BufferDesc as nvrhi::BufferDesc's defaults make it, with byteSize, a debug name (see
Expand Down Expand Up @@ -113,6 +114,50 @@ export namespace nvrhi {
return handle.ptr_ ? <ITexture><Opaque>handle.ptr_ : null;
}

// nvrhi::TextureSlice's defaults: the whole of mip level 0 of array slice 0 (its -1 sizes are the
// texture's, as TextureSlice::resolve makes them)
export function entireTexture(): TextureSlice {
return [0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff, 0, 0];
}

// IDevice::createStagingTexture: a texture of desc's size and format that the CPU maps with
// cpuAccess (CPU_ACCESS_READ, CPU_ACCESS_WRITE); null on failure. Release() it.
export function createStagingTexture(device: IDevice, desc: TextureDesc, cpuAccess: u8): IStagingTexture | null {
let descValue = desc;
const handle = device.createStagingTexture(Ref(descValue), cpuAccess);
return handle.ptr_ ? <IStagingTexture><Opaque>handle.ptr_ : null;
}

// IDevice::mapStagingTexture: the slice's first byte, its rows rowPitch bytes apart; null on
// failure. unmapStagingTexture it.
export function mapStagingTexture(device: IDevice, texture: IStagingTexture, slice: TextureSlice, cpuAccess: u8,
rowPitch: Ref<index>): Opaque | null {
let sliceValue = slice;
return device.mapStagingTexture(texture, Ref(sliceValue), cpuAccess, rowPitch);
}

// ICommandList::copyTexture between two textures
export function copyTexture(commandList: ICommandList, dest: ITexture, destSlice: TextureSlice,
src: ITexture, srcSlice: TextureSlice): void {
// (passed as the overload's `| null`: a class fits no overload, TSLANG_ISSUES.md #19)
const destArg: ITexture | null = dest;
const srcArg: ITexture | null = src;
let destSliceValue = destSlice;
let srcSliceValue = srcSlice;
commandList.copyTexture(destArg, Ref(destSliceValue), srcArg, Ref(srcSliceValue));
}

// ICommandList::copyTexture of a texture into a staging texture, to read it back
export function copyTextureToStaging(commandList: ICommandList, dest: IStagingTexture, destSlice: TextureSlice,
src: ITexture, srcSlice: TextureSlice): void {
// (as in copyTexture: TSLANG_ISSUES.md #19)
const destArg: IStagingTexture | null = dest;
const srcArg: ITexture | 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
48 changes: 44 additions & 4 deletions examples/nvrhi_direct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ namespace NvrhiDirect {
// framebuffer over the texture, vertex and pixel shaders, the render state with no depth test
// and no culling, a viewport), and into its top right quarter an indexed quad from a vertex
// buffer (an input layout), sampling a 2 x 2 texture (writeTexture, a sampler) scaled by a
// constant buffer; read back through Donut, the bottom right quarter as cleared.
// constant buffer, the bottom right quarter as cleared;
// - copies the texture into another (copyTexture), and that into a staging texture, mapped to
// read it back.
// 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 @@ -182,6 +184,22 @@ namespace NvrhiDirect {
check(nvrhi.getNativeObject(texture, isD3D12 ? nvrhi.OBJECT_TYPE_D3D12_RESOURCE : nvrhi.OBJECT_TYPE_VK_IMAGE) != null,
"the texture has its native resource");

// the texture's copy, and a staging texture the copy is read back through:
// IDevice::createStagingTexture, IStagingTexture::getDesc
const copied = nvrhi.createTexture(device, nvrhi.textureDesc(TEXTURE_SIZE, TEXTURE_SIZE, Format.RGBA8_UNORM,
"Copied texture", nvrhi.RESOURCE_STATE_COPY_DEST));
let stagingDesc = nvrhi.textureDesc(TEXTURE_SIZE, TEXTURE_SIZE, Format.RGBA8_UNORM, "Staging texture",
nvrhi.RESOURCE_STATE_COPY_DEST);
const staging = nvrhi.createStagingTexture(device, stagingDesc, nvrhi.CPU_ACCESS_READ);
if (!copied || !staging) {
check(false, "IDevice::createTexture and createStagingTexture");
return;
}
const keptStagingDesc = Deref(staging.getDesc());
check(keptStagingDesc.width == TEXTURE_SIZE && keptStagingDesc.height == TEXTURE_SIZE
&& keptStagingDesc.format == <u8>Format.RGBA8_UNORM
&& nvrhi.stdStringIs(keptStagingDesc.debugName, "Staging texture"), "IStagingTexture::getDesc");

// The compute pipeline: IDevice::createShader of the bytecode ShaderMake compiled (DXIL,
// SPIR-V), IShader::getDesc, getBytecode; its binding layout and set,
// IDevice::createComputePipeline
Expand Down Expand Up @@ -382,6 +400,11 @@ namespace NvrhiDirect {
nvrhi.setGraphicsState(commandList, nvrhi.graphicsState(texturedPipeline, framebuffer,
[TEXTURE_SIZE / 2, TEXTURE_SIZE, 0, TEXTURE_SIZE / 2, 0, 1], texturedSet, vertexBuffer, indexBuffer, Format.R16_UINT));
nvrhi.drawIndexed(commandList, 6);

// ICommandList::copyTexture, two of its overloads: the texture into its copy, the copy into
// the staging texture
nvrhi.copyTexture(commandList, copied, nvrhi.entireTexture(), texture, nvrhi.entireTexture());
nvrhi.copyTextureToStaging(commandList, staging, nvrhi.entireTexture(), copied, nvrhi.entireTexture());
commandList.endMarker();
commandList.close();

Expand Down Expand Up @@ -412,11 +435,26 @@ namespace NvrhiDirect {
check(clearMismatches == 0, `ICommandList::clearBufferUInt (${clearMismatches} of ${COUNT} differ)`);
check(computeMismatches == 0, `the compute dispatch (${computeMismatches} of ${COUNT} differ)`);

// the texture, read back through Donut (a staging texture)
// the texture's copy, read back through the staging texture: IDevice::mapStagingTexture (its
// rows rowPitch bytes apart: 256 on D3D12), unmapStagingTexture
let pixels: u8[] = [];
pixels.length = TEXTURE_SIZE * TEXTURE_SIZE * 4;
if (app.readTextureLevel(<TextureHandle><Opaque>texture, 0, <Opaque>Ref(pixels[0]), pixels.length) == 0) {
check(false, "the texture's readback");
let rowPitch: index = 0;
const mappedPixels = nvrhi.mapStagingTexture(device, staging, nvrhi.entireTexture(), nvrhi.CPU_ACCESS_READ, Ref(rowPitch));
const readBack = mappedPixels != null && rowPitch >= TEXTURE_SIZE * 4;
if (mappedPixels) {
if (readBack) {
const rows = <Ref<u8>>mappedPixels;
for (let y = 0; y < TEXTURE_SIZE; y++) {
for (let i = 0; i < TEXTURE_SIZE * 4; i++) {
pixels[y * TEXTURE_SIZE * 4 + i] = Deref(rows[y * rowPitch + i]);
}
}
}
device.unmapStagingTexture(staging);
}
if (!readBack) {
check(false, `IDevice::mapStagingTexture (row pitch ${rowPitch})`);
} else {
// the left half drawn green; the top right quarter the texels, a pixel each, green
// halved; the bottom right quarter as cleared
Expand Down Expand Up @@ -472,6 +510,8 @@ namespace NvrhiDirect {
bindingLayout.Release();
shader.Release();
texture.Release();
copied.Release();
staging.Release();
input.Release();
output.Release();
cleared.Release();
Expand Down