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 = {
subpixelOptions: new RowHeader("global options"),
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),
translateY: new Slider("translate y", -1, 1),
multisampleCount: new Slider("multisampleCount", 1, 4, 1),

View File

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

130
index.js
View File

@ -56,6 +56,7 @@ module.memory = memory;
var depthTexture = undefined;
var multisampleTexture = undefined;
var copyTexture = undefined;
const eyeValueX = document.getElementById("eye-value-x");
const eyeValueY = document.getElementById("eye-value-y");
@ -64,6 +65,9 @@ const eyeValueZ = document.getElementById("eye-value-z");
const yawValue = document.getElementById("yaw");
const pitchValue = document.getElementById("pitch");
var downloadFramebufferPending = false;
var tempBuffer = undefined;
function recreateDepth(canvasTexture, sampleCount)
{
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) {
const multisampleResized = multisampleTexture === undefined || canvasTexture.width !== multisampleTexture.width || canvasTexture.height !== multisampleTexture.height;
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('keyup', handleKeyup, false);
const downloadFramebufferButton = document.getElementById("download-framebuffer");
downloadFramebufferButton.addEventListener("click", handleDownloadButton, false);
function updateView()
{
const lu = keyState[KEY.W] === true;
@ -260,7 +300,30 @@ canvas.addEventListener("mousemove", onMouseMove);
var canRender = false;
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()
{
@ -281,16 +344,19 @@ function render2()
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) ? {
view: canvasTexture.createView(),
view: viewTexture.createView(),
loadOp: "clear",
clearValue: clearValue,
storeOp: "store",
} : {
view: multisampleTexture.createView(),
resolveTarget: canvasTexture.createView(),
resolveTarget: viewTexture.createView(),
loadOp: "clear",
clearValue: clearValue,
storeOp: "store",
@ -318,44 +384,40 @@ function render2()
renderPass.end();
/*
encoder.copyTextureToBuffer({
texture: multisampleTexture,
}, {
buffer: tempBuffer,
bytesPerRow: canvasTexture.width * 4 * 4,
}, {
width: canvasTexture.width,
height: canvasTexture.height,
depthOrArrayLayers: 1,
if (downloadFramebufferPending) {
encoder.copyTextureToBuffer({
texture: copyTexture,
}, {
buffer: tempBuffer,
bytesPerRow: canvasTexture.width * 4,
}, {
width: copyTexture.width,
height: copyTexture.height,
depthOrArrayLayers: 1,
});
*/
}
const commandBuffer = encoder.finish();
device.queue.submit([commandBuffer]);
frameNumber = (frameNumber + 1) % 2;
/*
tempBuffer.mapAsync(GPUMapMode.READ).then(() => {
const readBuffer = tempBuffer.getMappedRange(); // arraybuffer
//console.log(mousePositionClick);
const x = mousePositionClick[0];
const y = mousePositionClick[1];
const f32ReadBuffer = new Float32Array(readBuffer);
for (let i = 0; i < 2; i++) {
const a = f32ReadBuffer[((y + i) * canvasTexture.width + x) * 4 + 0];
const b = f32ReadBuffer[((y + i) * canvasTexture.width + x) * 4 + 1];
const c = f32ReadBuffer[((y + i) * canvasTexture.width + x) * 4 + 2];
const d = f32ReadBuffer[((y + i) * canvasTexture.width + x) * 4 + 3];
//console.log(a, b, c, d);
}
tempBuffer.unmap();
requestAnimationFrame(render2);
if (downloadFramebufferPending) {
tempBuffer.mapAsync(GPUMapMode.READ).then(() => {
const mappedRange = tempBuffer.getMappedRange(); // arraybuffer
const blob = tgaBlob(copyTexture, mappedRange);
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.setAttribute("href", url);
a.setAttribute("download", "framebuffer.tga");
a.click();
tempBuffer.unmap();
requestAnimationFrame(render2);
});
*/
requestAnimationFrame(render2);
downloadFramebufferPending = false;
} else {
requestAnimationFrame(render2);
}
} else {
requestAnimationFrame(render2);
}