screen-space derivatives, 4x multi-sampling

This commit is contained in:
Zack Buhman 2026-08-06 14:29:30 -05:00
parent 53cbbebdc7
commit a3d5b6650e
9 changed files with 107 additions and 33 deletions

View File

@ -63,7 +63,7 @@ class FontRenderer {
resource: sampler,
}, {
binding: 1,
resource: texture,
resource: texture.createView(),
}, {
binding: 2,
resource: { buffer: glyphBuffer },
@ -244,6 +244,9 @@ class FontRenderer {
depthCompare: "always",
format: "depth24plus",
},
multisample: {
count: 4,
}
});
//////////////////////////////////////////////////////////////////////
@ -292,11 +295,11 @@ class FontRenderer {
this.sliderArray[4] = configuration.distanceThreshold;
this.sliderArray[5] = configuration.distanceScale;
this.sliderArray[6] = configuration.distanceOffset;
//this.sliderArray[6] = configuration.distanceOffset;
this.sliderArray[8] = configuration.outlineThreshold;
this.sliderArray[9] = configuration.outlineScale;
this.sliderArray[10] = configuration.outlineOffset;
//this.sliderArray[10] = configuration.outlineOffset;
this.sliderArray[12] = configuration.colorMix;
this.sliderArray[13] = configuration.colorScale;

View File

@ -52,7 +52,8 @@ struct VertexInput {
struct VertexOutput {
@builtin(position) position: vec4f,
@location(0) texture: vec2f,
@location(0) @interpolate(linear, sample) texture: vec2f,
//@location(0) texture: vec2f,
@location(1) @interpolate(flat) glyphIndex: u32,
@location(2) foo: vec4f,
};
@ -102,6 +103,17 @@ fn palette(t: f32) -> vec4f
return vec4f(color, 1);
}
fn aliasedLinearSaturation(distance: f32, threshold: f32, scale: f32) -> f32
{
let distanceFromEdge = threshold - distance;
let dx = dpdx(distanceFromEdge);
let dy = dpdy(distanceFromEdge);
let gradientLength = length(vec2f(dx, dy));
let thresholdWidth = scale * gradientLength;
let aliased = saturate((distanceFromEdge / thresholdWidth) + 0.5);
return aliased;
}
@fragment
fn fragmentMain(input: VertexOutput) -> @location(0) vec4f
{
@ -111,6 +123,22 @@ fn fragmentMain(input: VertexOutput) -> @location(0) vec4f
let base = textureSample(fontTexture, linearSampler, input.texture * s + p);
let distance = base.x;
let insideT = aliasedLinearSaturation(distance,
config.distanceThreshold,
config.distanceScale);
let outlineT = aliasedLinearSaturation(distance,
config.outlineThreshold,
config.outlineScale);
let insideColor = vec4f(1, 1, 1, 1);
let outlineColor = vec4f(0, 0, 0, 1);
let outsideColor = vec4f(0, 0, 0, 0);
let outline = mix(outlineColor, outsideColor, outlineT);
return mix(insideColor, outline, insideT);
/*
let insideColorW = vec4f(1, 1, 1, 1);
let insideColorP = palette(distance * config.colorScale + config.colorOffset);
let insideColor = mix(insideColorW, insideColorP, config.colorMix);
@ -124,4 +152,5 @@ fn fragmentMain(input: VertexOutput) -> @location(0) vec4f
config.distanceThreshold - config.outlineThreshold,
config.outlineScale, config.outlineOffset);
return color;
*/
}

View File

@ -2,23 +2,34 @@ import { Slider } from "./slider.js"
function makeSliders()
{
return {
scale: new Slider("scale", 0.0, 0.05),
translateX: new Slider("translate x", -10, 10),
translateY: new Slider("translate y", -10, 10),
const sliders = {
scale: new Slider("scale", 0.0, 0.01),
translateX: new Slider("translate x", -1, 1),
translateY: new Slider("translate y", -1, 1),
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),
distanceThreshold: new Slider("dist. threshold", 0.25, 0.75),
distanceScale: new Slider("distance scale", 0.0, 2.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),
outlineScale: new Slider("outline scale", 0.0, 50.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),
};
/*
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;
}
export { makeSliders };

View File

@ -59,6 +59,7 @@ function createDepthTexture() {
size: [canvas.width, canvas.height],
format: 'depth24plus',
usage: GPUTextureUsage.RENDER_ATTACHMENT,
sampleCount: 4,
});
}
@ -73,7 +74,7 @@ const pitchValue = document.getElementById("pitch");
function recreateDepth(force)
{
if (canvas.clientWidth !== canvas.width || canvas.clientWidth !== canvas.height || force === true) {
if (force === true || canvas.clientWidth !== canvas.width || canvas.clientWidth !== canvas.height) {
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
@ -244,6 +245,8 @@ canvas.addEventListener("mousemove", onMouseMove);
var canRender = false;
var frameNumber = 0;
var multisampleTexture = undefined;
function render2()
{
if (canRender === true) {
@ -252,14 +255,30 @@ function render2()
canRender = false;
recreateDepth(false);
const colorView = context.getCurrentTexture().createView();
const canvasTexture = context.getCurrentTexture();
if ((multisampleTexture === undefined) ||
(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();
const encoder = device.createCommandEncoder();
const renderPass = encoder.beginRenderPass({
colorAttachments: [{
view: colorView,
view: multisampleTexture.createView(),
//view: canvasTexture.createView(),
resolveTarget: canvasTexture.createView(),
loadOp: "clear",
clearValue: { r: 0.2, g: 0.2, b: 0.4, a: 1 },
storeOp: "store",

View File

@ -5,8 +5,6 @@ function remap(low1, high1, low2, high2, value)
return low2 + (value - low1) * (high2 - low2) / (high1 - low1);
}
const sliderDefaults = {"bone length":"32","velocity acc.":"385","velocity damp.":"796","bone count":"40"};
class Slider {
constructor(label, low, high, max = 1024)
{
@ -41,8 +39,6 @@ class Slider {
const storageValue = localStorage.getItem(this.label);
if (storageValue !== null) {
this.input.value = storageValue;
} else if (this.label in sliderDefaults) {
this.input.value = sliderDefaults[this.label];
}
this.update();

View File

@ -6,6 +6,8 @@ static inline size_t texture_offset(font * font)
return (sizeof (struct font)) + (sizeof (struct glyph)) * font->glyph_count;
}
extern "C" void log(int);
void FontLayout::init(size_t buffer, int layout_buffer_length)
{
this->font = reinterpret_cast<struct font *>(buffer + 0);
@ -18,12 +20,14 @@ void FontLayout::init(size_t buffer, int layout_buffer_length)
this->layout_buffer = New<LayoutBuffer>(layout_buffer_length);
this->glyph_buffer = New<GlyphBuffer>(this->font->glyph_count);
int o = 0;
log(o);
for (uint16_t i = 0; i < this->font->glyph_count; i++) {
glyph_texture const & texture = this->glyphs[i].texture;
this->glyph_buffer[i].texPosition.x = float(texture.x) / float(this->font->texture_width);
this->glyph_buffer[i].texPosition.y = float(texture.y) / float(this->font->texture_height);
this->glyph_buffer[i].texSize.x = float(texture.width) / float(this->font->texture_width);
this->glyph_buffer[i].texSize.y = float(texture.height) / float(this->font->texture_height);
this->glyph_buffer[i].texPosition.x = float(texture.x - o) / float(this->font->texture_width);
this->glyph_buffer[i].texPosition.y = float(texture.y - o) / float(this->font->texture_height);
this->glyph_buffer[i].texSize.x = float(texture.width - o) / float(this->font->texture_width);
this->glyph_buffer[i].texSize.y = float(texture.height - o) / float(this->font->texture_height);
this->glyph_buffer[i].size.x = float(texture.width);
this->glyph_buffer[i].size.y = float(texture.height);
}

View File

@ -1,8 +1,13 @@
CFLAGS = -Og -g -gdwarf-4 -Wall -Wextra -Wno-error -Wfatal-errors
OPT = -flto -O3 -fno-stack-protector
CFLAGS = -g -gdwarf-4 -Wall -Wextra -Wno-error -Wfatal-errors
CFLAGS += -Wno-error=unused-parameter
CFLAGS += -Wno-error=unused-variable
CFLAGS += -Wno-error=unused-but-set-variable
CFLAGS += -I../../include
CFLAGS += $(OPT)
LDFLAGS += $(OPT)
CXXFLAGS = -std=c++23

View File

@ -1,5 +1,7 @@
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <array>
#include "ttf_2d_pack.h"
@ -9,9 +11,9 @@ struct size {
uint16_t height;
};
constexpr struct size max_texture = {1024, 1024};
constexpr struct size max_texture = {4096, 4096};
inline bool area_valid(const uint8_t texture[max_texture.height][max_texture.width],
inline bool area_valid(const uint8_t * texture,
const uint32_t x_offset,
const uint32_t y_offset,
const struct rect& rect,
@ -22,7 +24,7 @@ inline bool area_valid(const uint8_t texture[max_texture.height][max_texture.wid
uint32_t x = x_offset + xi;
uint32_t y = y_offset + yi;
if (texture[y][x] != 0)
if (texture[y * max_texture.width + x] != 0)
return false;
if (x >= window.width || y >= window.height)
@ -77,10 +79,12 @@ constexpr inline int log2(uint32_t n)
case 256: return 8;
case 512: return 9;
case 1024: return 10;
case 2048: return 11;
case 4096: return 12;
}
}
static void pack_into(uint8_t texture[max_texture.height][max_texture.width],
static void pack_into(uint8_t * texture,
size & window,
rect & rect)
{
@ -112,7 +116,7 @@ static void pack_into(uint8_t texture[max_texture.height][max_texture.width],
uint32_t x = x_offset + xi;
uint32_t y = y_offset + yi;
texture[y][x] = 1;
texture[y * max_texture.width + x] = 1;
}
}
@ -145,7 +149,8 @@ void insertion_sort(T * arr, int len)
window_curve_ix pack_all(struct rect * rects, const uint32_t num_rects)
{
uint8_t texture[max_texture.height][max_texture.width] = { 0 };
uint8_t * texture = (uint8_t *)malloc(max_texture.height * max_texture.width);
memset(texture, 0x00, max_texture.height * max_texture.width);
size window = {1, 1};
// sort all rectangles by size
@ -155,5 +160,7 @@ window_curve_ix pack_all(struct rect * rects, const uint32_t num_rects)
pack_into(texture, window, rects[i]);
}
free(texture);
return {window.width, window.height};
}

View File

@ -13,7 +13,7 @@
std::endian _target_endian;
constexpr uint32_t max_texture_dim = 1024;
constexpr uint32_t max_texture_dim = 4096;
constexpr uint32_t max_texture_size = max_texture_dim * max_texture_dim;
template< class T >
@ -287,7 +287,7 @@ int main(int argc, char *argv[])
uint32_t num_glyphs = (end - start) + 1;
glyph glyphs[num_glyphs];
uint8_t texture[max_texture_size];
uint8_t * texture = (uint8_t *)malloc(max_texture_size);
memset(texture, 0x00, max_texture_size);
auto window_curve_ix = load_all_positions(face, start, end, glyphs, texture);