more supersampling experiments
This commit is contained in:
parent
a3d5b6650e
commit
b1122bdbf5
40
font.js
40
font.js
@ -211,8 +211,10 @@ class FontRenderer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
console.log(canvasFormat);
|
console.log(canvasFormat);
|
||||||
this.renderPipeline = device.createRenderPipeline({
|
this.renderPipelines = {}
|
||||||
label: `${label} pipeline`,
|
for (let i = 1; i <= 4; i += 3) {
|
||||||
|
this.renderPipelines[i] = device.createRenderPipeline({
|
||||||
|
label: `${label} pipeline sample count ${i}`,
|
||||||
layout: pipelineLayout,
|
layout: pipelineLayout,
|
||||||
vertex: {
|
vertex: {
|
||||||
module: shaderModule,
|
module: shaderModule,
|
||||||
@ -245,9 +247,10 @@ class FontRenderer {
|
|||||||
format: "depth24plus",
|
format: "depth24plus",
|
||||||
},
|
},
|
||||||
multisample: {
|
multisample: {
|
||||||
count: 4,
|
count: i,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
// sliders
|
// sliders
|
||||||
@ -289,29 +292,36 @@ class FontRenderer {
|
|||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
const configuration = updateSliders(this.sliders);
|
const configuration = updateSliders(this.sliders);
|
||||||
this.sliderArray[0] = configuration.scale;
|
|
||||||
this.sliderArray[1] = configuration.translateX;
|
this.sliderArray[0] = configuration.mode;
|
||||||
this.sliderArray[2] = configuration.translateY;
|
this.sliderArray[1] = configuration.scale;
|
||||||
|
this.sliderArray[2] = configuration.translateX;
|
||||||
|
this.sliderArray[3] = configuration.translateY;
|
||||||
|
|
||||||
this.sliderArray[4] = configuration.distanceThreshold;
|
this.sliderArray[4] = configuration.distanceThreshold;
|
||||||
this.sliderArray[5] = configuration.distanceScale;
|
this.sliderArray[5] = configuration.distanceScale;
|
||||||
//this.sliderArray[6] = configuration.distanceOffset;
|
this.sliderArray[6] = configuration.aliasingEnable;
|
||||||
|
this.sliderArray[7] = configuration.rampMin;
|
||||||
|
|
||||||
this.sliderArray[8] = configuration.outlineThreshold;
|
this.sliderArray[8] = configuration.subpixelEnable;
|
||||||
this.sliderArray[9] = configuration.outlineScale;
|
this.sliderArray[9] = configuration.subpixelStrength;
|
||||||
//this.sliderArray[10] = configuration.outlineOffset;
|
this.sliderArray[10] = configuration.subpixelOffset;
|
||||||
|
this.sliderArray[11] = configuration.rampMax;
|
||||||
|
|
||||||
this.sliderArray[12] = configuration.colorMix;
|
this.sliderArray[12] = configuration.supersampleCount;
|
||||||
this.sliderArray[13] = configuration.colorScale;
|
this.sliderArray[13] = configuration.supersampleOffset;
|
||||||
this.sliderArray[14] = configuration.colorOffset;
|
this.sliderArray[14] = configuration.outlineThreshold;
|
||||||
|
this.sliderArray[15] = configuration.outlineScale;
|
||||||
|
|
||||||
device.queue.writeBuffer(this.frames[frameNumber].configurationBuffer, 0,
|
device.queue.writeBuffer(this.frames[frameNumber].configurationBuffer, 0,
|
||||||
this.sliderArray);
|
this.sliderArray);
|
||||||
|
|
||||||
|
return configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
render(renderPass, frameNumber)
|
render(renderPass, frameNumber, sampleCount)
|
||||||
{
|
{
|
||||||
renderPass.setPipeline(this.renderPipeline);
|
renderPass.setPipeline(this.renderPipelines[sampleCount]);
|
||||||
renderPass.setIndexBuffer(this.buffer, "uint16");
|
renderPass.setIndexBuffer(this.buffer, "uint16");
|
||||||
renderPass.setBindGroup(0, this.viewBindGroup);
|
renderPass.setBindGroup(0, this.viewBindGroup);
|
||||||
renderPass.setBindGroup(1, this.perFontBindGroup);
|
renderPass.setBindGroup(1, this.perFontBindGroup);
|
||||||
|
|||||||
171
font.wgsl
171
font.wgsl
@ -7,22 +7,25 @@ struct View {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct Config {
|
struct Config {
|
||||||
|
mode: f32,
|
||||||
scale: f32,
|
scale: f32,
|
||||||
translateX: f32,
|
translateX: f32,
|
||||||
translateY: f32,
|
translateY: f32,
|
||||||
padding0: f32,
|
|
||||||
distanceThreshold: f32,
|
distanceThreshold: f32,
|
||||||
distanceScale: f32,
|
distanceScale: f32,
|
||||||
distanceOffset: f32,
|
aliasingEnable: f32,
|
||||||
padding1: f32,
|
rampMin: f32,
|
||||||
|
|
||||||
|
subpixelEnable: f32,
|
||||||
|
subpixelStrength: f32,
|
||||||
|
subpixelOffset: f32,
|
||||||
|
rampMax: f32,
|
||||||
|
|
||||||
|
supersampleCount: f32,
|
||||||
|
supersampleOffset: f32,
|
||||||
outlineThreshold: f32,
|
outlineThreshold: f32,
|
||||||
outlineScale: f32,
|
outlineScale: f32,
|
||||||
outlineOffset: f32,
|
|
||||||
padding2: f32,
|
|
||||||
colorMix: f32,
|
|
||||||
colorScale: f32,
|
|
||||||
colorOffset: f32,
|
|
||||||
padding3: f32,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GlyphBuffer {
|
struct GlyphBuffer {
|
||||||
@ -55,7 +58,6 @@ struct VertexOutput {
|
|||||||
@location(0) @interpolate(linear, sample) texture: vec2f,
|
@location(0) @interpolate(linear, sample) texture: vec2f,
|
||||||
//@location(0) texture: vec2f,
|
//@location(0) texture: vec2f,
|
||||||
@location(1) @interpolate(flat) glyphIndex: u32,
|
@location(1) @interpolate(flat) glyphIndex: u32,
|
||||||
@location(2) foo: vec4f,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const vertices = array(
|
const vertices = array(
|
||||||
@ -80,18 +82,9 @@ fn vertexMain(input: VertexInput) -> VertexOutput
|
|||||||
output.position = vec4f(screenPosition, 0.0, 1.0);
|
output.position = vec4f(screenPosition, 0.0, 1.0);
|
||||||
output.texture = texture;
|
output.texture = texture;
|
||||||
output.glyphIndex = glyphIndex;
|
output.glyphIndex = glyphIndex;
|
||||||
output.foo = vec4f(layoutBuffer[glyphIndex].position.xy, 0, 0);
|
|
||||||
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
|
fn palette(t: f32) -> vec4f
|
||||||
{
|
{
|
||||||
let a = vec3f(0.5, 0.5, 0.5);
|
let a = vec3f(0.5, 0.5, 0.5);
|
||||||
@ -111,46 +104,130 @@ fn aliasedLinearSaturation(distance: f32, threshold: f32, scale: f32) -> f32
|
|||||||
let gradientLength = length(vec2f(dx, dy));
|
let gradientLength = length(vec2f(dx, dy));
|
||||||
let thresholdWidth = scale * gradientLength;
|
let thresholdWidth = scale * gradientLength;
|
||||||
let aliased = saturate((distanceFromEdge / thresholdWidth) + 0.5);
|
let aliased = saturate((distanceFromEdge / thresholdWidth) + 0.5);
|
||||||
return aliased;
|
return 1.0 - aliased;
|
||||||
}
|
}
|
||||||
|
|
||||||
@fragment
|
struct FragmentOutput {
|
||||||
fn fragmentMain(input: VertexOutput) -> @location(0) vec4f
|
@location(0) color: vec4f,
|
||||||
{
|
};
|
||||||
let p = glyphBuffer[input.glyphIndex].texPosition;
|
|
||||||
let s = glyphBuffer[input.glyphIndex].texSize;
|
|
||||||
|
|
||||||
let base = textureSample(fontTexture, linearSampler, input.texture * s + p);
|
fn sampleAt(texture: vec2f) -> f32
|
||||||
|
{
|
||||||
|
let base = textureSample(fontTexture, linearSampler, texture);
|
||||||
let distance = base.x;
|
let distance = base.x;
|
||||||
|
|
||||||
let insideT = aliasedLinearSaturation(distance,
|
var t: f32;
|
||||||
config.distanceThreshold,
|
if (config.aliasingEnable == 1.0) {
|
||||||
config.distanceScale);
|
t = aliasedLinearSaturation(distance, config.distanceThreshold, config.distanceScale);
|
||||||
|
} else {
|
||||||
|
t = f32(distance > config.distanceThreshold);
|
||||||
|
}
|
||||||
|
|
||||||
let outlineT = aliasedLinearSaturation(distance,
|
return t;
|
||||||
config.outlineThreshold,
|
}
|
||||||
config.outlineScale);
|
|
||||||
|
|
||||||
|
fn sampleAt2(texture: vec2f,
|
||||||
|
threshold1: f32, scale1: f32,
|
||||||
|
threshold2: f32, scale2: f32) -> vec2f
|
||||||
|
{
|
||||||
|
let base = textureSample(fontTexture, linearSampler, texture);
|
||||||
|
let distance = base.x;
|
||||||
|
|
||||||
|
var t: vec2f;
|
||||||
|
if (config.aliasingEnable == 1.0) {
|
||||||
|
t.x = aliasedLinearSaturation(distance, threshold1, scale1);
|
||||||
|
t.y = aliasedLinearSaturation(distance, threshold2, scale2);
|
||||||
|
} else {
|
||||||
|
t.x = f32(distance > threshold1);
|
||||||
|
t.y = f32(distance > threshold2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
const offsets = array(
|
||||||
|
vec2f(0.125, 0.375),
|
||||||
|
vec2f(-0.125, -0.375),
|
||||||
|
vec2f(0.375, -0.125),
|
||||||
|
vec2f(-0.375, 0.125),
|
||||||
|
);
|
||||||
|
|
||||||
|
fn supersampleColor(texture: vec2f, s: vec2f, p: vec2f) -> vec4f
|
||||||
|
{
|
||||||
let insideColor = vec4f(1, 1, 1, 1);
|
let insideColor = vec4f(1, 1, 1, 1);
|
||||||
let outlineColor = vec4f(0, 0, 0, 1);
|
let outlineColor = vec4f(0, 0, 0, 1);
|
||||||
let outsideColor = vec4f(0, 0, 0, 0);
|
let outsideColor = vec4f(0, 0, 0, 0);
|
||||||
|
|
||||||
let outline = mix(outlineColor, outsideColor, outlineT);
|
let dx = dpdx(texture.x * s.x);
|
||||||
return mix(insideColor, outline, insideT);
|
let dy = dpdy(texture.y * s.y);
|
||||||
|
|
||||||
/*
|
let fdim = config.supersampleCount;
|
||||||
let insideColorW = vec4f(1, 1, 1, 1);
|
let idim = i32(floor(fdim / 2.0));
|
||||||
let insideColorP = palette(distance * config.colorScale + config.colorOffset);
|
|
||||||
let insideColor = mix(insideColorW, insideColorP, config.colorMix);
|
|
||||||
let outlineColor = vec4f(0, 0, 0, 1);
|
|
||||||
|
|
||||||
var color = vec4f(0, 0, 0, 0);
|
var t = vec2f(0, 0);
|
||||||
color = blend(color, insideColor, distance,
|
if (fdim == 1) {
|
||||||
config.distanceThreshold, config.distanceScale, config.distanceOffset);
|
let coordinate = texture * s + p;
|
||||||
|
t = sampleAt2(coordinate,
|
||||||
|
config.distanceThreshold, config.distanceScale,
|
||||||
|
config.outlineThreshold, config.outlineScale);
|
||||||
|
} else {
|
||||||
|
for (var y = -idim; y <= idim; y++) {
|
||||||
|
for (var x = -idim; x <= idim; x++) {
|
||||||
|
//for (var i = 0; i < 4; i++) {
|
||||||
|
//let offset = vec2f(dx, dy) * offsets[i] * config.supersampleOffset;
|
||||||
|
let offset = vec2f(dx, dy) * vec2f(f32(x), f32(y)) * 0.1 * config.supersampleOffset;
|
||||||
|
let coordinate = texture * s + offset + p;
|
||||||
|
t += sampleAt2(coordinate,
|
||||||
|
config.distanceThreshold, config.distanceScale,
|
||||||
|
config.outlineThreshold, config.outlineScale);
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t *= (1.0 / (fdim * fdim));
|
||||||
|
}
|
||||||
|
|
||||||
color = blend(color, outlineColor, distance,
|
let color = mix(outsideColor, outlineColor, t.y);
|
||||||
config.distanceThreshold - config.outlineThreshold,
|
return mix(color, insideColor, t.x);
|
||||||
config.outlineScale, config.outlineOffset);
|
}
|
||||||
return color;
|
|
||||||
*/
|
fn subpixelColor(texture: vec2f, s: vec2f, p: vec2f) -> vec4f
|
||||||
|
{
|
||||||
|
let dx = dpdx(texture.x * s.x * 1/3);
|
||||||
|
let dy = dpdy(texture.y * s.y);
|
||||||
|
|
||||||
|
var subpixelColor = vec3f(0, 0, 0);
|
||||||
|
let insideColor = vec4f(1, 1, 1, 1);
|
||||||
|
let outsideColor = vec4f(0, 0, 0, 0);
|
||||||
|
|
||||||
|
if (config.subpixelEnable == 0) {
|
||||||
|
let coordinate = texture * s + p;
|
||||||
|
let t = sampleAt(coordinate);
|
||||||
|
return mix(outsideColor, insideColor, t);
|
||||||
|
} else {
|
||||||
|
for (var x = -1; x <= 1; x += 1) {
|
||||||
|
let offset = vec2f(dx * f32(x) * config.subpixelOffset, 0);
|
||||||
|
let coordinate = texture * s + offset + p;
|
||||||
|
subpixelColor[(x + 1)] = sampleAt(coordinate);
|
||||||
|
}
|
||||||
|
let mono = (subpixelColor.x + subpixelColor.y + subpixelColor.z) / 3;
|
||||||
|
let subpixelMix = mix(vec3f(mono, mono, mono), subpixelColor.xyz, config.subpixelStrength);
|
||||||
|
return vec4f(subpixelMix, length(subpixelMix));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fragmentMain(input: VertexOutput) -> FragmentOutput
|
||||||
|
{
|
||||||
|
let s = glyphBuffer[input.glyphIndex].texSize;
|
||||||
|
let p = glyphBuffer[input.glyphIndex].texPosition;
|
||||||
|
|
||||||
|
var output: FragmentOutput;
|
||||||
|
|
||||||
|
if (config.mode == 0) {
|
||||||
|
output.color = subpixelColor(input.texture, s, p);
|
||||||
|
} else {
|
||||||
|
output.color = supersampleColor(input.texture, s, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,23 +1,37 @@
|
|||||||
import { Slider } from "./slider.js"
|
import { Slider } from "./slider.js"
|
||||||
|
import { Select } from "./select.js"
|
||||||
|
import { RowHeader } from "./row-header.js"
|
||||||
|
|
||||||
function makeSliders()
|
function makeSliders()
|
||||||
{
|
{
|
||||||
const sliders = {
|
const sliders = {
|
||||||
|
subpixelOptions: new RowHeader("global options"),
|
||||||
|
mode: new Select("mode", ["subpixel", "supersampling"]),
|
||||||
scale: new Slider("scale", 0.0, 0.01),
|
scale: new Slider("scale", 0.0, 0.01),
|
||||||
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),
|
||||||
|
|
||||||
distanceThreshold: new Slider("dist. threshold", 0.25, 0.75),
|
distanceThreshold: new Slider("distance threshold", 0.25, 0.75),
|
||||||
distanceScale: new Slider("distance scale", 0.0, 2.0),
|
distanceScale: new Slider("distance scale", 0.0, 20.0),
|
||||||
//distanceOffset: new Slider("distance offset", -20.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),
|
||||||
|
|
||||||
|
subpixelOptions: new RowHeader("subpixel mode options"),
|
||||||
|
subpixelEnable: new Slider("subpixel enable", 0, 1, 1),
|
||||||
|
subpixelStrength: new Slider("subpixel strength", 0.0, 1.0),
|
||||||
|
subpixelOffset: new Slider("subpixel offset", 0.0, 10.0),
|
||||||
|
|
||||||
|
supersamplingOptions: new RowHeader("supersampling mode options"),
|
||||||
|
supersampleCount: new Slider("supersample count", 1.0, 15.0, 7),
|
||||||
|
supersampleOffset: new Slider("supersample offset", 0.0, 10.0),
|
||||||
outlineThreshold: new Slider("outline threshold", 0.0, 1.0),
|
outlineThreshold: new Slider("outline threshold", 0.0, 1.0),
|
||||||
outlineScale: new Slider("outline scale", 0.0, 50.0),
|
outlineScale: new Slider("outline scale", 0.0, 20.0),
|
||||||
//outlineOffset: new Slider("outline offset", -20.0, 20.0),
|
|
||||||
|
|
||||||
colorMix: new Slider("color mix", 0.0, 1.0),
|
//colorMix: new Slider("color mix", 0.0, 1.0),
|
||||||
colorScale: new Slider("color scale", 0.0, 10.0),
|
//colorScale: new Slider("color scale", 0.0, 10.0),
|
||||||
colorOffset: new Slider("color offset", 0.0, 3.1415),
|
//colorOffset: new Slider("color offset", 0.0, 3.1415),
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
172
index.html
172
index.html
@ -8,15 +8,71 @@
|
|||||||
:root {
|
:root {
|
||||||
color-scheme: light dark;
|
color-scheme: light dark;
|
||||||
}
|
}
|
||||||
div, span {
|
label {
|
||||||
font: 0.8rem sans-serif;
|
font: 0.8rem sans-serif;
|
||||||
}
|
}
|
||||||
span {
|
code {
|
||||||
padding-bottom: 0.1rem;
|
font: 0.85rem monospace;
|
||||||
}
|
}
|
||||||
|
control-root {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 0;
|
||||||
|
background: #eee;
|
||||||
|
}
|
||||||
|
control-main {
|
||||||
|
width: 350px;
|
||||||
|
float: right;
|
||||||
|
margin-right: 1rem;
|
||||||
|
background: #444;
|
||||||
|
}
|
||||||
|
control-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto auto minmax(4.25em, auto);
|
||||||
|
column-gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
row {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
row > *:first-child {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
justify-self: left;
|
||||||
|
}
|
||||||
|
row > *:last-child {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
justify-self: right;
|
||||||
|
}
|
||||||
|
row-header {
|
||||||
|
font: 0.9rem sans-serif;
|
||||||
|
font-weight: bold;
|
||||||
|
justify-self: center !important;
|
||||||
|
margin-top: 0.2em;
|
||||||
|
}
|
||||||
|
row-separator, row-header {
|
||||||
|
grid-column: 1 / span 3;
|
||||||
|
}
|
||||||
|
row-separator:not(:last-child) {
|
||||||
|
border-bottom: 1px solid #777;
|
||||||
|
}
|
||||||
|
row-header + row-separator {
|
||||||
|
border-bottom: 2px solid #888 !important;
|
||||||
|
}
|
||||||
|
|
||||||
select, option {
|
select, option {
|
||||||
font: 0.8rem sans-serif;
|
font: 0.8rem sans-serif;
|
||||||
}
|
}
|
||||||
|
input[type="range"] {
|
||||||
|
height: 1rem;
|
||||||
|
width: calc(100% - 0.5em);
|
||||||
|
}
|
||||||
|
.span-2 {
|
||||||
|
grid-column: span 2;
|
||||||
|
width: calc(100% - 0.5em);
|
||||||
|
}
|
||||||
|
|
||||||
canvas {
|
canvas {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -26,115 +82,17 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="range"] {
|
|
||||||
height: 0.9rem;
|
|
||||||
width: 70%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-root {
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 0;
|
|
||||||
background: #eee;
|
|
||||||
}
|
|
||||||
.control-main {
|
|
||||||
width: 340px;
|
|
||||||
float: right;
|
|
||||||
margin-right: 15px;
|
|
||||||
background: #444;
|
|
||||||
}
|
|
||||||
.label {
|
|
||||||
width: calc(35% - 0.3rem);
|
|
||||||
float: left;
|
|
||||||
clear: left;
|
|
||||||
overflow: hidden;
|
|
||||||
margin-left: 0.3rem;
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
}
|
|
||||||
.value {
|
|
||||||
width: calc(65% - 0.2rem);
|
|
||||||
float: left;
|
|
||||||
margin-top: 0.1rem;
|
|
||||||
margin-right: 0.2rem;
|
|
||||||
}
|
|
||||||
.row:not(:last-child) {
|
|
||||||
border-bottom: 1px solid #999;
|
|
||||||
}
|
|
||||||
.row {
|
|
||||||
height: 1.5rem;
|
|
||||||
}
|
|
||||||
.code, code {
|
|
||||||
font: 0.85rem monospace;
|
|
||||||
padding-top: 0.15rem;
|
|
||||||
}
|
|
||||||
.float3 {
|
|
||||||
width: 30%;
|
|
||||||
display: inline-block;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
.float2 {
|
|
||||||
width: 40%;
|
|
||||||
display: inline-block;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
.range-span {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
<script src="index.js" type="module"></script>
|
<script src="index.js" type="module"></script>
|
||||||
<!--<script src="index2.js" type="module"></script>-->
|
<!--<script src="index2.js" type="module"></script>-->
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<canvas></canvas>
|
<canvas></canvas>
|
||||||
<div class="control-root">
|
<control-root>
|
||||||
<div class="control-main" id="control-main">
|
<control-main>
|
||||||
<!--
|
<control-grid id="control-parent">
|
||||||
<div class="row">
|
</control-grid>
|
||||||
<span class="label">model</span>
|
</control-main>
|
||||||
<select class="value" id="model-select"></select>
|
</control-root>
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<span class="label">lighting</span>
|
|
||||||
<select class="value" id="lighting-select">
|
|
||||||
<option>unlit</option>
|
|
||||||
<option>diffuse</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
-->
|
|
||||||
<!--
|
|
||||||
<div class="row">
|
|
||||||
<span class="label">eye</span>
|
|
||||||
<div class="value">
|
|
||||||
<code class="float3" id="eye-value-x">0.0</code>
|
|
||||||
<code class="float3" id="eye-value-y">0.0</code>
|
|
||||||
<code class="float3" id="eye-value-z">0.0</code>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<span class="label">yaw/pitch</span>
|
|
||||||
<div class="value">
|
|
||||||
<code class="float2" id="yaw">0.0</code>
|
|
||||||
<code class="float2" id="pitch">0.0</code>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
-->
|
|
||||||
<!--
|
|
||||||
<div class="row">
|
|
||||||
<span class="label">test2</span>
|
|
||||||
<select class="value">
|
|
||||||
<option value="test2">test2</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
-->
|
|
||||||
<!--
|
|
||||||
<div class="row">
|
|
||||||
<span class="label">eye</span>
|
|
||||||
<input type="color" class="value">
|
|
||||||
</div>
|
|
||||||
-->
|
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
134
index.js
134
index.js
@ -54,16 +54,8 @@ const module = await WebAssembly.instantiateStreaming(fetch("src/module.wasm"),
|
|||||||
});
|
});
|
||||||
module.memory = memory;
|
module.memory = memory;
|
||||||
|
|
||||||
function createDepthTexture() {
|
|
||||||
return device.createTexture({
|
|
||||||
size: [canvas.width, canvas.height],
|
|
||||||
format: 'depth24plus',
|
|
||||||
usage: GPUTextureUsage.RENDER_ATTACHMENT,
|
|
||||||
sampleCount: 4,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var depthTexture = undefined;
|
var depthTexture = undefined;
|
||||||
|
var multisampleTexture = 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");
|
||||||
@ -72,16 +64,37 @@ 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");
|
||||||
|
|
||||||
function recreateDepth(force)
|
function recreateDepth(canvasTexture, sampleCount)
|
||||||
{
|
{
|
||||||
if (force === true || canvas.clientWidth !== canvas.width || canvas.clientWidth !== canvas.height) {
|
const depthResized = depthTexture === undefined || canvasTexture.width != depthTexture.width || canvasTexture.height != depthTexture.height;
|
||||||
canvas.width = canvas.clientWidth;
|
|
||||||
canvas.height = canvas.clientHeight;
|
|
||||||
|
|
||||||
|
if (depthResized || depthTexture.sampleCount !== sampleCount) {
|
||||||
if (depthTexture !== undefined) {
|
if (depthTexture !== undefined) {
|
||||||
depthTexture.destroy();
|
depthTexture.destroy();
|
||||||
}
|
}
|
||||||
depthTexture = createDepthTexture();
|
|
||||||
|
depthTexture = device.createTexture({
|
||||||
|
format: 'depth24plus',
|
||||||
|
usage: GPUTextureUsage.RENDER_ATTACHMENT,
|
||||||
|
size: [canvasTexture.width, canvasTexture.height],
|
||||||
|
sampleCount: sampleCount,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sampleCount > 1) {
|
||||||
|
const multisampleResized = multisampleTexture === undefined || canvasTexture.width !== multisampleTexture.width || canvasTexture.height !== multisampleTexture.height;
|
||||||
|
if (multisampleResized || multisampleTexture.sampleCount !== sampleCount) {
|
||||||
|
if ((multisampleTexture !== undefined)) {
|
||||||
|
multisampleTexture.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
multisampleTexture = device.createTexture({
|
||||||
|
format: canvasTexture.format,
|
||||||
|
usage: GPUTextureUsage.RENDER_ATTACHMENT,
|
||||||
|
size: [canvasTexture.width, canvasTexture.height],
|
||||||
|
sampleCount: sampleCount,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,6 +230,7 @@ const flatSliders = {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const mousePosition = new Float32Array([0, 0, 0, 0]);
|
const mousePosition = new Float32Array([0, 0, 0, 0]);
|
||||||
|
const mousePositionClick = new Float32Array([0, 0, 0, 0]);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
const snakeSliders = {
|
const snakeSliders = {
|
||||||
@ -229,10 +243,10 @@ const snakeSliders = {
|
|||||||
|
|
||||||
function onClick(e)
|
function onClick(e)
|
||||||
{
|
{
|
||||||
mousePosition[0] = e.clientX / canvas.width;
|
mousePositionClick[0] = e.clientX;
|
||||||
mousePosition[1] = 1.0 - (e.clientY / canvas.height);
|
mousePositionClick[1] = e.clientY;
|
||||||
}
|
}
|
||||||
//canvas.addEventListener("click", onClick);
|
canvas.addEventListener("click", onClick);
|
||||||
|
|
||||||
function onMouseMove(e)
|
function onMouseMove(e)
|
||||||
{
|
{
|
||||||
@ -245,44 +259,46 @@ canvas.addEventListener("mousemove", onMouseMove);
|
|||||||
|
|
||||||
var canRender = false;
|
var canRender = false;
|
||||||
var frameNumber = 0;
|
var frameNumber = 0;
|
||||||
var multisampleTexture = undefined;
|
|
||||||
|
var tempBuffer = undefined;
|
||||||
|
|
||||||
function render2()
|
function render2()
|
||||||
{
|
{
|
||||||
if (canRender === true) {
|
if (canRender === true) {
|
||||||
|
canRender = false;
|
||||||
|
|
||||||
|
canvas.width = canvas.clientWidth * window.devicePixelRatio;
|
||||||
|
canvas.height = canvas.clientHeight * window.devicePixelRatio;
|
||||||
|
|
||||||
|
const configuration = fontRenderer.update(device, frameNumber);
|
||||||
|
const sampleCount = configuration.multisampleCount;
|
||||||
|
|
||||||
//const configuration = updateSliders(flatSliders);
|
//const configuration = updateSliders(flatSliders);
|
||||||
//const configuration = updateSliders(snakeSliders);
|
//const configuration = updateSliders(snakeSliders);
|
||||||
|
|
||||||
canRender = false;
|
|
||||||
recreateDepth(false);
|
|
||||||
const canvasTexture = context.getCurrentTexture();
|
const canvasTexture = context.getCurrentTexture();
|
||||||
if ((multisampleTexture === undefined) ||
|
recreateDepth(canvasTexture, sampleCount);
|
||||||
(multisampleTexture.width !== canvasTexture.width) ||
|
|
||||||
(multisampleTexture.height !== canvasTexture.height)) {
|
|
||||||
if ((multisampleTexture !== undefined)) {
|
|
||||||
multisampleTexture.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
multisampleTexture = device.createTexture({
|
|
||||||
format: canvasTexture.format,
|
|
||||||
usage: GPUTextureUsage.RENDER_ATTACHMENT,
|
|
||||||
size: [canvasTexture.width, canvasTexture.height],
|
|
||||||
sampleCount: 4,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
updateView();
|
updateView();
|
||||||
|
|
||||||
const encoder = device.createCommandEncoder();
|
const clearValue = { r: 0.2, g: 0.2, b: 0.4, a: 1.0 };
|
||||||
const renderPass = encoder.beginRenderPass({
|
|
||||||
colorAttachments: [{
|
const attachment0 = (sampleCount == 1) ? {
|
||||||
|
view: canvasTexture.createView(),
|
||||||
|
loadOp: "clear",
|
||||||
|
clearValue: clearValue,
|
||||||
|
storeOp: "store",
|
||||||
|
} : {
|
||||||
view: multisampleTexture.createView(),
|
view: multisampleTexture.createView(),
|
||||||
//view: canvasTexture.createView(),
|
|
||||||
resolveTarget: canvasTexture.createView(),
|
resolveTarget: canvasTexture.createView(),
|
||||||
loadOp: "clear",
|
loadOp: "clear",
|
||||||
clearValue: { r: 0.2, g: 0.2, b: 0.4, a: 1 },
|
clearValue: clearValue,
|
||||||
storeOp: "store",
|
storeOp: "store",
|
||||||
}],
|
};
|
||||||
|
|
||||||
|
const encoder = device.createCommandEncoder();
|
||||||
|
const renderPass = encoder.beginRenderPass({
|
||||||
|
colorAttachments: [attachment0],
|
||||||
depthStencilAttachment: {
|
depthStencilAttachment: {
|
||||||
view: depthTexture.createView(),
|
view: depthTexture.createView(),
|
||||||
depthClearValue: 1.0,
|
depthClearValue: 1.0,
|
||||||
@ -298,18 +314,51 @@ function render2()
|
|||||||
//snakeRenderer.update(device, frameNumber, mousePosition, configuration);
|
//snakeRenderer.update(device, frameNumber, mousePosition, configuration);
|
||||||
//snakeRenderer.render(renderPass, frameNumber);
|
//snakeRenderer.render(renderPass, frameNumber);
|
||||||
|
|
||||||
fontRenderer.update(device, frameNumber);
|
fontRenderer.render(renderPass, frameNumber, sampleCount);
|
||||||
fontRenderer.render(renderPass, frameNumber);
|
|
||||||
|
|
||||||
renderPass.end();
|
renderPass.end();
|
||||||
|
|
||||||
|
/*
|
||||||
|
encoder.copyTextureToBuffer({
|
||||||
|
texture: multisampleTexture,
|
||||||
|
}, {
|
||||||
|
buffer: tempBuffer,
|
||||||
|
bytesPerRow: canvasTexture.width * 4 * 4,
|
||||||
|
}, {
|
||||||
|
width: canvasTexture.width,
|
||||||
|
height: canvasTexture.height,
|
||||||
|
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;
|
||||||
|
|
||||||
|
/*
|
||||||
|
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);
|
requestAnimationFrame(render2);
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
requestAnimationFrame(render2);
|
||||||
|
} else {
|
||||||
|
requestAnimationFrame(render2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function setCanRender()
|
function setCanRender()
|
||||||
{
|
{
|
||||||
@ -317,5 +366,4 @@ function setCanRender()
|
|||||||
}
|
}
|
||||||
setInterval(setCanRender, 33.33); // 15 fps
|
setInterval(setCanRender, 33.33); // 15 fps
|
||||||
|
|
||||||
recreateDepth(true);
|
|
||||||
requestAnimationFrame(render2);
|
requestAnimationFrame(render2);
|
||||||
|
|||||||
26
row-header.js
Normal file
26
row-header.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
const controlGrid = document.getElementById("control-parent");
|
||||||
|
|
||||||
|
class RowHeader {
|
||||||
|
constructor(label)
|
||||||
|
{
|
||||||
|
this.label = label;
|
||||||
|
|
||||||
|
const parent = controlGrid;
|
||||||
|
|
||||||
|
const rowHeaderE = document.createElement("row-header");
|
||||||
|
rowHeaderE.innerHTML = label;
|
||||||
|
|
||||||
|
const rowE = document.createElement("row");
|
||||||
|
const sepE = document.createElement("row-separator");
|
||||||
|
|
||||||
|
parent.appendChild(rowHeaderE);
|
||||||
|
parent.appendChild(sepE);
|
||||||
|
}
|
||||||
|
|
||||||
|
update()
|
||||||
|
{
|
||||||
|
return this.label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { RowHeader };
|
||||||
55
select.js
Normal file
55
select.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
const controlGrid = document.getElementById("control-parent");
|
||||||
|
|
||||||
|
class Select {
|
||||||
|
constructor(label, options)
|
||||||
|
{
|
||||||
|
const parent = controlGrid;
|
||||||
|
|
||||||
|
const labelE = document.createElement("label");
|
||||||
|
labelE.innerHTML = label;
|
||||||
|
|
||||||
|
const selectE = document.createElement("select");
|
||||||
|
selectE.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rowE = document.createElement("row");
|
||||||
|
const sepE = document.createElement("row-separator");
|
||||||
|
|
||||||
|
rowE.appendChild(labelE);
|
||||||
|
rowE.appendChild(selectE);
|
||||||
|
|
||||||
|
parent.appendChild(rowE);
|
||||||
|
parent.appendChild(sepE);
|
||||||
|
|
||||||
|
this.select = selectE;
|
||||||
|
this.options = options;
|
||||||
|
|
||||||
|
this.lastValue = undefined;
|
||||||
|
|
||||||
|
const storageValue = localStorage.getItem(this.label);
|
||||||
|
if (storageValue !== null) {
|
||||||
|
this.select.value = storageValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
update()
|
||||||
|
{
|
||||||
|
const rawValue = parseInt(this.select.value);
|
||||||
|
if (rawValue !== this.lastValue) {
|
||||||
|
this.lastValue = rawValue;
|
||||||
|
localStorage.setItem(this.label, rawValue);
|
||||||
|
}
|
||||||
|
return rawValue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Select };
|
||||||
37
slider.js
37
slider.js
@ -1,4 +1,4 @@
|
|||||||
const controlMain = document.getElementById("control-main");
|
const controlGrid = document.getElementById("control-parent");
|
||||||
|
|
||||||
function remap(low1, high1, low2, high2, value)
|
function remap(low1, high1, low2, high2, value)
|
||||||
{
|
{
|
||||||
@ -8,30 +8,33 @@ function remap(low1, high1, low2, high2, value)
|
|||||||
class Slider {
|
class Slider {
|
||||||
constructor(label, low, high, max = 1024)
|
constructor(label, low, high, max = 1024)
|
||||||
{
|
{
|
||||||
const labelE = document.createElement("span");
|
const parent = controlGrid;
|
||||||
labelE.className = "label";
|
|
||||||
|
const labelE = document.createElement("label");
|
||||||
labelE.innerHTML = label;
|
labelE.innerHTML = label;
|
||||||
|
|
||||||
const valueE = document.createElement("div");
|
|
||||||
valueE.className = "value";
|
|
||||||
|
|
||||||
const inputE = document.createElement("input");
|
const inputE = document.createElement("input");
|
||||||
inputE.className = "range";
|
|
||||||
inputE.min = 0;
|
inputE.min = 0;
|
||||||
inputE.max = max;
|
inputE.max = max;
|
||||||
inputE.type = "range";
|
inputE.type = "range";
|
||||||
const spanE = document.createElement("span");
|
const rangeValueE = document.createElement("range-value");
|
||||||
spanE.className = "range-span code";
|
const codeE = document.createElement("code");
|
||||||
|
rangeValueE.appendChild(codeE);
|
||||||
|
|
||||||
valueE.appendChild(inputE);
|
const rowE = document.createElement("row");
|
||||||
valueE.appendChild(spanE);
|
|
||||||
|
|
||||||
controlMain.appendChild(labelE);
|
const sepE = document.createElement("row-separator");
|
||||||
controlMain.appendChild(valueE);
|
|
||||||
|
rowE.appendChild(labelE);
|
||||||
|
rowE.appendChild(inputE);
|
||||||
|
rowE.appendChild(rangeValueE);
|
||||||
|
|
||||||
|
parent.appendChild(rowE);
|
||||||
|
parent.appendChild(sepE);
|
||||||
|
|
||||||
this.label = label;
|
this.label = label;
|
||||||
this.input = inputE;
|
this.input = inputE;
|
||||||
this.span = spanE;
|
this.code = codeE;
|
||||||
this.low = low;
|
this.low = low;
|
||||||
this.high = high;
|
this.high = high;
|
||||||
this.lastValue = undefined;
|
this.lastValue = undefined;
|
||||||
@ -47,15 +50,15 @@ class Slider {
|
|||||||
update()
|
update()
|
||||||
{
|
{
|
||||||
const rawValue = parseInt(this.input.value);
|
const rawValue = parseInt(this.input.value);
|
||||||
const value = remap(this.input.min, this.input.max, this.low, this.high, this.input.value);
|
const value = remap(this.input.min, this.input.max, this.low, this.high, rawValue);
|
||||||
if (rawValue !== this.lastValue) {
|
if (rawValue !== this.lastValue) {
|
||||||
this.lastValue = rawValue;
|
this.lastValue = rawValue;
|
||||||
localStorage.setItem(this.label, rawValue);
|
localStorage.setItem(this.label, rawValue);
|
||||||
}
|
}
|
||||||
if (this.high > 10) {
|
if (this.high > 10) {
|
||||||
this.span.innerHTML = value.toFixed(2);
|
this.code.innerHTML = value.toFixed(2);
|
||||||
} else {
|
} else {
|
||||||
this.span.innerHTML = value.toFixed(4);
|
this.code.innerHTML = value.toFixed(4);
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user