framebuffer tga download

This commit is contained in:
Zack Buhman 2026-08-07 23:22:01 -05:00
parent c9a65e3bcf
commit 717e73d32d
3 changed files with 106 additions and 35 deletions

View File

@ -9,7 +9,7 @@ function makeSliders()
const sliders = { const sliders = {
subpixelOptions: new RowHeader("global options"), subpixelOptions: new RowHeader("global options"),
mode: new Select("mode", ["supersampling", "subpixel"]), mode: new Select("mode", ["supersampling", "subpixel"]),
scale: new Slider("scale", 0.0, 0.01), scale: new Slider("scale", 0.0, 0.05),
translateX: new Slider("translate x", -1, 1), translateX: new Slider("translate x", -1, 1),
translateY: new Slider("translate y", -1, 1), translateY: new Slider("translate y", -1, 1),
multisampleCount: new Slider("multisampleCount", 1, 4, 1), multisampleCount: new Slider("multisampleCount", 1, 4, 1),

View File

@ -65,6 +65,11 @@
font: 0.8rem sans-serif; font: 0.8rem sans-serif;
} }
input[type="button"] {
width: calc(100% - 1.1em);
grid-column: 1 / span 3;
}
input[type="range"], input[type="text"] { input[type="range"], input[type="text"] {
height: 1rem; height: 1rem;
} }
@ -100,6 +105,10 @@
<control-root> <control-root>
<control-main> <control-main>
<control-grid id="control-parent"> <control-grid id="control-parent">
<row>
<input id="download-framebuffer" type="button" value="download framebuffer"></input>
</row>
<row-separator></row-separator>
</control-grid> </control-grid>
</control-main> </control-main>
</control-root> </control-root>

130
index.js
View File

@ -56,6 +56,7 @@ module.memory = memory;
var depthTexture = undefined; var depthTexture = undefined;
var multisampleTexture = undefined; var multisampleTexture = undefined;
var copyTexture = undefined;
const eyeValueX = document.getElementById("eye-value-x"); const eyeValueX = document.getElementById("eye-value-x");
const eyeValueY = document.getElementById("eye-value-y"); const eyeValueY = document.getElementById("eye-value-y");
@ -64,6 +65,9 @@ const eyeValueZ = document.getElementById("eye-value-z");
const yawValue = document.getElementById("yaw"); const yawValue = document.getElementById("yaw");
const pitchValue = document.getElementById("pitch"); const pitchValue = document.getElementById("pitch");
var downloadFramebufferPending = false;
var tempBuffer = undefined;
function recreateDepth(canvasTexture, sampleCount) function recreateDepth(canvasTexture, sampleCount)
{ {
const depthResized = depthTexture === undefined || canvasTexture.width != depthTexture.width || canvasTexture.height != depthTexture.height; const depthResized = depthTexture === undefined || canvasTexture.width != depthTexture.width || canvasTexture.height != depthTexture.height;
@ -81,6 +85,33 @@ function recreateDepth(canvasTexture, sampleCount)
}); });
} }
const tempBufferSize = canvasTexture.width * canvasTexture.height * 4;
const tempResized = tempBuffer === undefined || tempBufferSize != tempBuffer.size;
if (tempResized) {
if (tempBuffer !== undefined) {
tempBuffer.destroy();
}
tempBuffer = device.createBuffer({
label: "temp buffer",
size: tempBufferSize,
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
});
}
const copyResized = copyTexture === undefined || canvasTexture.width !== copyTexture.width || canvasTexture.height !== copyTexture.height;
if (copyResized) {
if (copyTexture !== undefined) {
copyTexture.destroy();
}
copyTexture = device.createTexture({
format: canvasTexture.format,
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
size: [canvasTexture.width, canvasTexture.height],
sampleCount: 1,
});
}
if (sampleCount > 1) { if (sampleCount > 1) {
const multisampleResized = multisampleTexture === undefined || canvasTexture.width !== multisampleTexture.width || canvasTexture.height !== multisampleTexture.height; const multisampleResized = multisampleTexture === undefined || canvasTexture.width !== multisampleTexture.width || canvasTexture.height !== multisampleTexture.height;
if (multisampleResized || multisampleTexture.sampleCount !== sampleCount) { if (multisampleResized || multisampleTexture.sampleCount !== sampleCount) {
@ -177,9 +208,18 @@ function handleKeyup(e)
} }
} }
function handleDownloadButton(e)
{
downloadFramebufferPending = true;
console.log("download framebuffer");
}
window.addEventListener('keydown', handleKeydown, false); window.addEventListener('keydown', handleKeydown, false);
window.addEventListener('keyup', handleKeyup, false); window.addEventListener('keyup', handleKeyup, false);
const downloadFramebufferButton = document.getElementById("download-framebuffer");
downloadFramebufferButton.addEventListener("click", handleDownloadButton, false);
function updateView() function updateView()
{ {
const lu = keyState[KEY.W] === true; const lu = keyState[KEY.W] === true;
@ -260,7 +300,30 @@ canvas.addEventListener("mousemove", onMouseMove);
var canRender = false; var canRender = false;
var frameNumber = 0; var frameNumber = 0;
var tempBuffer = undefined; function tgaBlob(texture, mappedRange)
{
const tgaHeaderSize = 18;
const tgaBuffer = new ArrayBuffer(mappedRange.byteLength + tgaHeaderSize);
const tgaView = new DataView(tgaBuffer);
tgaView.setUint8(0, 0); // idLength
tgaView.setUint8(1, 0); // colorMapType
tgaView.setUint8(2, 2); // imageTypeCode: 2 uncompressed RGB
tgaView.setUint16(3, 0, true); // colorMap origin
tgaView.setUint16(5, 0, true); // colorMap length
tgaView.setUint8(7, 0); // colorMap depth
tgaView.setUint16(8, 0, true); // xOrigin
tgaView.setUint16(10, 0, true); // yOrigin
tgaView.setUint16(12, texture.width, true); // width
tgaView.setUint16(14, texture.height, true); // height
tgaView.setUint8(16, 32); // bits per pixel
const descriptor = ((8 << 0) | // number of attribute bits
(1 << 5) | // origin in upper left
(0 << 6)); // non-interleaved
tgaView.setUint8(17, descriptor); // descriptor
const tgaU8 = new Uint8Array(tgaBuffer);
tgaU8.set(new Uint8Array(mappedRange), 18);
return new Blob([tgaBuffer]);
}
function render2() function render2()
{ {
@ -281,16 +344,19 @@ function render2()
updateView(); updateView();
const clearValue = { r: 0.2, g: 0.2, b: 0.4, a: 1.0 }; //const clearValue = ;
const clearValue = (downloadFramebufferPending) ? { r: 0.0, g: 0.0, b: 0.0, a: 0.0 } : { r: 0.2, g: 0.2, b: 0.4, a: 1.0 };
const viewTexture = (downloadFramebufferPending) ? copyTexture : canvasTexture;
const attachment0 = (sampleCount == 1) ? { const attachment0 = (sampleCount == 1) ? {
view: canvasTexture.createView(), view: viewTexture.createView(),
loadOp: "clear", loadOp: "clear",
clearValue: clearValue, clearValue: clearValue,
storeOp: "store", storeOp: "store",
} : { } : {
view: multisampleTexture.createView(), view: multisampleTexture.createView(),
resolveTarget: canvasTexture.createView(), resolveTarget: viewTexture.createView(),
loadOp: "clear", loadOp: "clear",
clearValue: clearValue, clearValue: clearValue,
storeOp: "store", storeOp: "store",
@ -318,44 +384,40 @@ function render2()
renderPass.end(); renderPass.end();
/* if (downloadFramebufferPending) {
encoder.copyTextureToBuffer({ encoder.copyTextureToBuffer({
texture: multisampleTexture, texture: copyTexture,
}, { }, {
buffer: tempBuffer, buffer: tempBuffer,
bytesPerRow: canvasTexture.width * 4 * 4, bytesPerRow: canvasTexture.width * 4,
}, { }, {
width: canvasTexture.width, width: copyTexture.width,
height: canvasTexture.height, height: copyTexture.height,
depthOrArrayLayers: 1, depthOrArrayLayers: 1,
}); });
*/ }
const commandBuffer = encoder.finish(); const commandBuffer = encoder.finish();
device.queue.submit([commandBuffer]); device.queue.submit([commandBuffer]);
frameNumber = (frameNumber + 1) % 2; frameNumber = (frameNumber + 1) % 2;
/* if (downloadFramebufferPending) {
tempBuffer.mapAsync(GPUMapMode.READ).then(() => { tempBuffer.mapAsync(GPUMapMode.READ).then(() => {
const readBuffer = tempBuffer.getMappedRange(); // arraybuffer const mappedRange = tempBuffer.getMappedRange(); // arraybuffer
//console.log(mousePositionClick); const blob = tgaBlob(copyTexture, mappedRange);
const x = mousePositionClick[0]; const url = URL.createObjectURL(blob);
const y = mousePositionClick[1]; const a = document.createElement("a");
const f32ReadBuffer = new Float32Array(readBuffer); a.setAttribute("href", url);
for (let i = 0; i < 2; i++) { a.setAttribute("download", "framebuffer.tga");
const a = f32ReadBuffer[((y + i) * canvasTexture.width + x) * 4 + 0]; a.click();
const b = f32ReadBuffer[((y + i) * canvasTexture.width + x) * 4 + 1]; tempBuffer.unmap();
const c = f32ReadBuffer[((y + i) * canvasTexture.width + x) * 4 + 2]; requestAnimationFrame(render2);
const d = f32ReadBuffer[((y + i) * canvasTexture.width + x) * 4 + 3];
//console.log(a, b, c, d);
}
tempBuffer.unmap();
requestAnimationFrame(render2);
}); });
*/ downloadFramebufferPending = false;
requestAnimationFrame(render2); } else {
requestAnimationFrame(render2);
}
} else { } else {
requestAnimationFrame(render2); requestAnimationFrame(render2);
} }