Compare commits

...

3 Commits

9 changed files with 296 additions and 59 deletions

60
color-input.js Normal file
View File

@ -0,0 +1,60 @@
const controlGrid = document.getElementById("control-parent");
function parseColor(c)
{
const groups = c.match(/^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/i);
if (groups == null) {
throw new Error("invalid color", c);
} else {
return [parseInt(groups[1], 16), parseInt(groups[2], 16), parseInt(groups[3], 16)];
}
}
class ColorInput {
constructor(label, defaultValue)
{
const parent = controlGrid;
const labelE = document.createElement("label");
labelE.innerHTML = label;
const inputE = document.createElement("input");
inputE.type = "color";
inputE.className = "span-2";
const rowE = document.createElement("row");
const sepE = document.createElement("row-separator");
rowE.appendChild(labelE);
rowE.appendChild(inputE);
parent.appendChild(rowE);
parent.appendChild(sepE);
this.label = label;
this.input = inputE;
this.lastValue = undefined;
const storageValue = localStorage.getItem(this.label);
if (storageValue !== null) {
this.input.value = storageValue;
} else {
this.input.value = defaultValue;
}
this.update();
}
update()
{
const rawValue = this.input.value;
if (rawValue !== this.lastValue) {
this.lastValue = rawValue;
localStorage.setItem(this.label, rawValue);
}
return parseColor(rawValue);
}
};
export { ColorInput };

47
font.js
View File

@ -110,6 +110,18 @@ class FontRenderer {
}
}
font_layout__draw(s)
{
const length = Math.min(s.length, this.maxStringLength - 1);
for (let i = 0; i < length; i++) {
const c = s.charCodeAt(i) % 256;
this.stringBuffer[i] = c;
}
this.stringBuffer[length] = 0;
this.exports.font_layout__draw(this.fontLayoutAddress, this.stringBufferAddress);
}
constructor(device, canvasFormat, viewUniformBuffer, wgsl, module, fontBufferSrc)
{
const label = "font";
@ -127,6 +139,12 @@ class FontRenderer {
this.maxGlyphs = 1024;
this.fontLayoutAddress = this.exports.font_layout__create(this.fontBufferAddress, this.maxGlyphs);
// text buffer
this.maxStringLength = 1024;
this.stringBufferAddress = this.exports.mem_alloc(this.maxStringLength);
this.stringBuffer = new Uint8Array(this.module.memory.buffer, this.stringBufferAddress);
this.lastString = undefined;
//////////////////////////////////////////////////////////////////////
// shader module
//////////////////////////////////////////////////////////////////////
@ -257,7 +275,7 @@ class FontRenderer {
//////////////////////////////////////////////////////////////////////
this.sliders = makeSliders();
this.sliderArray = new Float32Array(4 * 4);
this.sliderArray = new Float32Array(4 * 6);
//////////////////////////////////////////////////////////////////////
// buffers
@ -281,6 +299,21 @@ class FontRenderer {
update(device, frameNumber)
{
const configuration = updateSliders(this.sliders);
//////////////////////////////////////////////////////////////////////
// string draw
//////////////////////////////////////////////////////////////////////
if (this.lastString === undefined || this.lastString !== configuration.text) {
this.lastString = configuration.text;
this.font_layout__draw(configuration.text);
}
//////////////////////////////////////////////////////////////////////
// layout buffer
//////////////////////////////////////////////////////////////////////
this.instanceCount = this.exports.font_layout__layout_buffer_index(this.fontLayoutAddress);
const layoutBufferSize = this.instanceCount * 4 * 4;
@ -291,8 +324,6 @@ class FontRenderer {
// sliders
//////////////////////////////////////////////////////////////////////
const configuration = updateSliders(this.sliders);
this.sliderArray[0] = configuration.mode;
this.sliderArray[1] = configuration.scale;
this.sliderArray[2] = configuration.translateX;
@ -313,6 +344,14 @@ class FontRenderer {
this.sliderArray[14] = configuration.outlineThreshold;
this.sliderArray[15] = configuration.outlineScale;
this.sliderArray[16] = configuration.insideColor[0] / 255.0;
this.sliderArray[17] = configuration.insideColor[1] / 255.0;
this.sliderArray[18] = configuration.insideColor[2] / 255.0;
this.sliderArray[20] = configuration.outlineColor[0] / 255.0;
this.sliderArray[21] = configuration.outlineColor[1] / 255.0;
this.sliderArray[22] = configuration.outlineColor[2] / 255.0;
device.queue.writeBuffer(this.frames[frameNumber].configurationBuffer, 0,
this.sliderArray);
@ -342,8 +381,6 @@ async function loadFont(device, canvasFormat, viewUniformBuffer, module)
const renderer = new FontRenderer(device, canvasFormat, viewUniformBuffer, fontWgsl, module, liberationBuffer);
module.instance.exports.font_layout__draw(renderer.fontLayoutAddress);
return renderer;
}

View File

@ -26,6 +26,9 @@ struct Config {
supersampleOffset: f32,
outlineThreshold: f32,
outlineScale: f32,
insideColor: vec4f,
outlineColor: vec4f,
};
struct GlyphBuffer {
@ -154,8 +157,8 @@ const offsets = array(
fn supersampleColor(texture: vec2f, s: vec2f, p: vec2f) -> vec4f
{
let insideColor = vec4f(1, 1, 1, 1);
let outlineColor = vec4f(0, 0, 0, 1);
let insideColor = vec4f(config.insideColor.xyz, 1);
let outlineColor = vec4f(config.outlineColor.xyz, 1);
let outsideColor = vec4f(0, 0, 0, 0);
let dx = dpdx(texture.x * s.x);
@ -223,11 +226,13 @@ fn fragmentMain(input: VertexOutput) -> FragmentOutput
var output: FragmentOutput;
if (config.mode == 0) {
if (config.mode == 1) {
output.color = subpixelColor(input.texture, s, p);
} else {
output.color = supersampleColor(input.texture, s, p);
}
output.color = vec4f(output.color.xyz / output.color.w, output.color.w);
return output;
}

View File

@ -1,13 +1,15 @@
import { Slider } from "./slider.js"
import { Select } from "./select.js"
import { RowHeader } from "./row-header.js"
import { TextInput } from "./text-input.js"
import { ColorInput } from "./color-input.js"
function makeSliders()
{
const sliders = {
subpixelOptions: new RowHeader("global options"),
mode: new Select("mode", ["subpixel", "supersampling"]),
scale: new Slider("scale", 0.0, 0.01),
mode: new Select("mode", ["supersampling", "subpixel"]),
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),
@ -15,8 +17,10 @@ function makeSliders()
distanceThreshold: new Slider("distance threshold", 0.25, 0.75),
distanceScale: new Slider("distance scale", 0.0, 20.0),
aliasingEnable: new Slider("aliasing enable", 0, 1, 1),
//rampMin: new Slider("ramp min", 0, 1.0),
//rampMax: new Slider("ramp max", 0, 1.0),
text: new TextInput("text"),
insideColor: new ColorInput("inside color", "#ffffff"),
outlineColor: new ColorInput("outline color", "#000000"),
subpixelOptions: new RowHeader("subpixel mode options"),
subpixelEnable: new Slider("subpixel enable", 0, 1, 1),
@ -34,13 +38,13 @@ function makeSliders()
//colorOffset: new Slider("color offset", 0.0, 3.1415),
};
const defaults = {"undefined":"0","subpixel offset":"62","text":"supersampling","bone length":"32","outline scale":"208","ramp min":"0","dist. threshold":"513","velocity acc.":"385","gamma":"494","aliasing enable":"1","outline threshold":"335","outline color":"#000000","threshold offset":"265","outline. threshold":"512","outline offset":"0","inside color":"#ffffff","color mix":"0","scale":"89","supersample count":"1","distance threshold":"494","supersamples":"0","distance offset":"513","translate y":"381","ramp max":"1024","multisampleCount":"1","supersample offset":"248","subpixel enable":"1","translate x":"581","color offset":"118","velocity damp.":"796","distance scale":"49","threshold scale":"739","bone count":"40","color scale":"148","subpixel strength":"348"};
/*
const defaults = {"color scale":"148","bone length":"32","outline scale":"37","dist. threshold":"562","velocity acc.":"385","translate x":"464","outline threshold":"433","translate y":"495","bone count":"40","outline offset":"482","scale":"229","outline. threshold":"512","color offset":"118","distance offset":"676","threshold offset":"265","velocity damp.":"796","distance scale":"404","threshold scale":"739","color mix":"0"};
for (let slider of Object.values(sliders)) {
if (slider.label in defaults) {
slider.input.value = defaults[slider.label];
}
}
}
*/
return sliders;

View File

@ -61,13 +61,27 @@
border-bottom: 2px solid #888 !important;
}
select, option {
select, option, input[type="text"] {
font: 0.8rem sans-serif;
}
input[type="range"] {
input[type="button"] {
width: calc(100% - 1.1em);
grid-column: 1 / span 3;
}
input[type="range"], input[type="text"] {
height: 1rem;
}
input[type="color"] {
height: 1.25rem;
}
input[type="range"] {
width: calc(100% - 0.5em);
}
input[type="text"] {
width: calc(100% - 1em);
}
.span-2 {
grid-column: span 2;
width: calc(100% - 0.5em);
@ -91,6 +105,10 @@
<control-root>
<control-main>
<control-grid id="control-parent">
<row>
<input id="download-render-buffer" type="button" value="download render buffer"></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 downloadRenderBufferPending = 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)
{
downloadRenderBufferPending = true;
console.log("download framebuffer");
}
window.addEventListener('keydown', handleKeydown, false);
window.addEventListener('keyup', handleKeyup, false);
const downloadRenderBufferButton = document.getElementById("download-render-buffer");
downloadRenderBufferButton.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 = (downloadRenderBufferPending) ? { 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 = (downloadRenderBufferPending) ? 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 (downloadRenderBufferPending) {
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 (downloadRenderBufferPending) {
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);
downloadRenderBufferPending = false;
} else {
requestAnimationFrame(render2);
}
} else {
requestAnimationFrame(render2);
}

View File

@ -8,34 +8,34 @@ class Select {
const labelE = document.createElement("label");
labelE.innerHTML = label;
const selectE = document.createElement("select");
selectE.className = "span-2";
const inputE = document.createElement("select");
inputE.className = "span-2";
for (let i = 0; i < options.length; i++) {
const option = options[i];
const optionE = document.createElement("option");
optionE.setAttribute("value", i);
optionE.innerHTML = option;
selectE.appendChild(optionE);
inputE.appendChild(optionE);
}
const rowE = document.createElement("row");
const sepE = document.createElement("row-separator");
rowE.appendChild(labelE);
rowE.appendChild(selectE);
rowE.appendChild(inputE);
parent.appendChild(rowE);
parent.appendChild(sepE);
this.select = selectE;
this.input = inputE;
this.options = options;
this.lastValue = undefined;
const storageValue = localStorage.getItem(this.label);
if (storageValue !== null) {
this.select.value = storageValue;
this.input.value = storageValue;
}
this.update();
@ -43,7 +43,7 @@ class Select {
update()
{
const rawValue = parseInt(this.select.value);
const rawValue = parseInt(this.input.value);
if (rawValue !== this.lastValue) {
this.lastValue = rawValue;
localStorage.setItem(this.label, rawValue);

View File

@ -49,8 +49,9 @@ extern "C" {
return font_layout->layout_buffer_index;
}
void font_layout__draw(FontLayout * font_layout)
void font_layout__draw(FontLayout * font_layout, char const * s)
{
font_layout->draw_string("hexagon grid");
font_layout->layout_buffer_index = 0;
font_layout->draw_string(s);
}
};

50
text-input.js Normal file
View File

@ -0,0 +1,50 @@
const controlGrid = document.getElementById("control-parent");
class TextInput {
constructor(label)
{
const parent = controlGrid;
const labelE = document.createElement("label");
labelE.innerHTML = label;
const inputE = document.createElement("input");
inputE.type = "text";
inputE.className = "span-2";
const rowE = document.createElement("row");
const sepE = document.createElement("row-separator");
rowE.appendChild(labelE);
rowE.appendChild(inputE);
parent.appendChild(rowE);
parent.appendChild(sepE);
this.label = label;
this.input = inputE;
this.lastValue = undefined;
const storageValue = localStorage.getItem(this.label);
if (storageValue !== null) {
this.input.value = storageValue;
} else {
this.input.value = "hexagon grid";
}
this.update();
}
update()
{
const rawValue = this.input.value;
if (rawValue !== this.lastValue) {
this.lastValue = rawValue;
localStorage.setItem(this.label, rawValue);
}
return rawValue;
}
};
export { TextInput };