render target blending, more sliders

This commit is contained in:
Zack Buhman 2026-08-05 20:54:56 -05:00
parent 997bfac193
commit 53cbbebdc7
7 changed files with 100 additions and 23 deletions

49
font.js
View File

@ -86,7 +86,7 @@ class FontRenderer {
const configurationBuffer = device.createBuffer({ const configurationBuffer = device.createBuffer({
label: `configuration buffer ${i}`, label: `configuration buffer ${i}`,
size: 4 * 4, size: this.sliderArray.byteLength,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
}); });
@ -195,7 +195,7 @@ class FontRenderer {
buffer: { type: "read-only-storage" } buffer: { type: "read-only-storage" }
}, { // configuration }, { // configuration
binding: 1, binding: 1,
visibility: GPUShaderStage.VERTEX, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
buffer: { type: "uniform" } buffer: { type: "uniform" }
}] }]
}), }),
@ -210,6 +210,7 @@ class FontRenderer {
], ],
}); });
console.log(canvasFormat);
this.renderPipeline = device.createRenderPipeline({ this.renderPipeline = device.createRenderPipeline({
label: `${label} pipeline`, label: `${label} pipeline`,
layout: pipelineLayout, layout: pipelineLayout,
@ -221,6 +222,17 @@ class FontRenderer {
module: shaderModule, module: shaderModule,
entryPoint: "fragmentMain", entryPoint: "fragmentMain",
targets: [{ targets: [{
blend: {
color: {
dstFactor: "one-minus-src-alpha",
srcFactor: "src-alpha",
},
alpha: {
dstFactor: "one-minus-src-alpha",
srcFactor: "src-alpha",
},
operation: "add",
},
format: canvasFormat, format: canvasFormat,
}] }]
}, },
@ -229,11 +241,18 @@ class FontRenderer {
}, },
depthStencil: { depthStencil: {
depthWriteEnabled: true, depthWriteEnabled: true,
depthCompare: "less", depthCompare: "always",
format: "depth24plus", format: "depth24plus",
}, },
}); });
//////////////////////////////////////////////////////////////////////
// sliders
//////////////////////////////////////////////////////////////////////
this.sliders = makeSliders();
this.sliderArray = new Float32Array(4 * 4);
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// buffers // buffers
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@ -252,13 +271,6 @@ class FontRenderer {
resource: { buffer: viewUniformBuffer }, resource: { buffer: viewUniformBuffer },
}], }],
}); });
//////////////////////////////////////////////////////////////////////
// sliders
//////////////////////////////////////////////////////////////////////
this.sliders = makeSliders();
this.sliderArray = new Float32Array(4);
} }
update(device, frameNumber) update(device, frameNumber)
@ -277,6 +289,19 @@ class FontRenderer {
this.sliderArray[0] = configuration.scale; this.sliderArray[0] = configuration.scale;
this.sliderArray[1] = configuration.translateX; this.sliderArray[1] = configuration.translateX;
this.sliderArray[2] = configuration.translateY; this.sliderArray[2] = configuration.translateY;
this.sliderArray[4] = configuration.distanceThreshold;
this.sliderArray[5] = configuration.distanceScale;
this.sliderArray[6] = configuration.distanceOffset;
this.sliderArray[8] = configuration.outlineThreshold;
this.sliderArray[9] = configuration.outlineScale;
this.sliderArray[10] = configuration.outlineOffset;
this.sliderArray[12] = configuration.colorMix;
this.sliderArray[13] = configuration.colorScale;
this.sliderArray[14] = configuration.colorOffset;
device.queue.writeBuffer(this.frames[frameNumber].configurationBuffer, 0, device.queue.writeBuffer(this.frames[frameNumber].configurationBuffer, 0,
this.sliderArray); this.sliderArray);
} }
@ -289,7 +314,9 @@ class FontRenderer {
renderPass.setBindGroup(1, this.perFontBindGroup); renderPass.setBindGroup(1, this.perFontBindGroup);
renderPass.setBindGroup(2, this.frames[frameNumber].bindGroup); renderPass.setBindGroup(2, this.frames[frameNumber].bindGroup);
renderPass.drawIndexed(6, this.instanceCount); for (let i = 0; i < this.instanceCount; i++) {
renderPass.drawIndexed(6, 1, 0, 0, i);
}
} }
} }

View File

@ -10,6 +10,19 @@ struct Config {
scale: f32, scale: f32,
translateX: f32, translateX: f32,
translateY: f32, translateY: f32,
padding0: f32,
distanceThreshold: f32,
distanceScale: f32,
distanceOffset: f32,
padding1: f32,
outlineThreshold: f32,
outlineScale: f32,
outlineOffset: f32,
padding2: f32,
colorMix: f32,
colorScale: f32,
colorOffset: f32,
padding3: f32,
}; };
struct GlyphBuffer { struct GlyphBuffer {
@ -70,21 +83,45 @@ fn vertexMain(input: VertexInput) -> VertexOutput
return output; return output;
} }
fn blend(dst: vec4f, src: vec4f, distance: f32, threshold: f32, scale: f32, offset: f32) -> vec4f
{
let scaledDistance = (distance - threshold) * scale + offset;
let alpha = clamp(scaledDistance, 0.0, 1.0);
let opacity = clamp(src.a * alpha - dst.a, 0.0, 1.0);
return opacity * src + (1.0 - opacity) * dst;
}
fn palette(t: f32) -> vec4f
{
let a = vec3f(0.5, 0.5, 0.5);
let b = vec3f(0.5, 0.5, 0.5);
let c = vec3f(1.0, 1.0, 1.0);
let d = vec3f(0.00, 0.33, 0.67);
//0.263,0.416,0.557
let color = a + b * cos(6.28318 * (c * t + d));
return vec4f(color, 1);
}
@fragment @fragment
fn fragmentMain(input: VertexOutput) -> @location(0) vec4f fn fragmentMain(input: VertexOutput) -> @location(0) vec4f
{ {
var color = vec3f(input.texture.xy, 0);
let p = glyphBuffer[input.glyphIndex].texPosition; let p = glyphBuffer[input.glyphIndex].texPosition;
let s = glyphBuffer[input.glyphIndex].texSize; let s = glyphBuffer[input.glyphIndex].texSize;
let base = textureSample(fontTexture, linearSampler, input.texture * s + p); let base = textureSample(fontTexture, linearSampler, input.texture * s + p);
let distance = base.x;
let t = f32(base.x > 0.5); let insideColorW = vec4f(1, 1, 1, 1);
let insideColorP = palette(distance * config.colorScale + config.colorOffset);
let insideColor = mix(insideColorW, insideColorP, config.colorMix);
let outlineColor = vec4f(0, 0, 0, 1);
if (base.x < 0.5) { var color = vec4f(0, 0, 0, 0);
discard; color = blend(color, insideColor, distance,
} config.distanceThreshold, config.distanceScale, config.distanceOffset);
return vec4f(t, t, t, 1); color = blend(color, outlineColor, distance,
config.distanceThreshold - config.outlineThreshold,
config.outlineScale, config.outlineOffset);
return color;
} }

View File

@ -6,6 +6,18 @@ function makeSliders()
scale: new Slider("scale", 0.0, 0.05), scale: new Slider("scale", 0.0, 0.05),
translateX: new Slider("translate x", -10, 10), translateX: new Slider("translate x", -10, 10),
translateY: new Slider("translate y", -10, 10), translateY: new Slider("translate y", -10, 10),
distanceThreshold: new Slider("dist. threshold", 0.0, 1.0),
distanceScale: new Slider("distance scale", 0.0, 200.0),
distanceOffset: new Slider("distance offset", -20.0, 20.0),
outlineThreshold: new Slider("outline threshold", 0.0, 1.0),
outlineScale: new Slider("outline scale", 0.0, 200.0),
outlineOffset: new Slider("outline offset", -20.0, 20.0),
colorMix: new Slider("color mix", 0.0, 1.0),
colorScale: new Slider("color scale", 0.0, 10.0),
colorOffset: new Slider("color offset", 0.0, 3.1415),
}; };
} }

View File

@ -41,13 +41,13 @@
background: #eee; background: #eee;
} }
.control-main { .control-main {
width: 310px; width: 340px;
float: right; float: right;
margin-right: 15px; margin-right: 15px;
background: #444; background: #444;
} }
.label { .label {
width: calc(30% - 0.3rem); width: calc(35% - 0.3rem);
float: left; float: left;
clear: left; clear: left;
overflow: hidden; overflow: hidden;
@ -55,7 +55,7 @@
margin-top: 0.25rem; margin-top: 0.25rem;
} }
.value { .value {
width: calc(70% - 0.2rem); width: calc(65% - 0.2rem);
float: left; float: left;
margin-top: 0.1rem; margin-top: 0.1rem;
margin-right: 0.2rem; margin-right: 0.2rem;

View File

@ -25,6 +25,7 @@ const canvasFormat = navigator.gpu.getPreferredCanvasFormat();
context.configure({ context.configure({
device: device, device: device,
format: canvasFormat, format: canvasFormat,
alphaMode: 'premultiplied',
}); });
const encoder = device.createCommandEncoder(); const encoder = device.createCommandEncoder();

View File

@ -57,7 +57,7 @@ class Slider {
localStorage.setItem(this.label, rawValue); localStorage.setItem(this.label, rawValue);
} }
if (this.high > 10) { if (this.high > 10) {
this.span.innerHTML = value.toFixed(0); this.span.innerHTML = value.toFixed(2);
} else { } else {
this.span.innerHTML = value.toFixed(4); this.span.innerHTML = value.toFixed(4);
} }

View File

@ -51,6 +51,6 @@ extern "C" {
void font_layout__draw(FontLayout * font_layout) void font_layout__draw(FontLayout * font_layout)
{ {
font_layout->draw_string("asdf qwer"); font_layout->draw_string("hexagon grid");
} }
}; };