@ludicon/spark.js
Version:
Real-Time GPU Texture Codecs for the Web
235 lines (181 loc) • 6.22 kB
JavaScript
const n = `struct Params {
colorMode: u32,
};
var src : texture_2d<f32>;
var dst : texture_storage_2d<rgba8unorm, write>;
var smp: sampler;
var<uniform> params: Params;
fn linear_to_srgb_vec3(c: vec3<f32>) -> vec3<f32> {
return select(
1.055 * pow(c, vec3<f32>(1.0 / 2.4)) - 0.055,
c * 12.92,
c <= vec3<f32>(0.0031308)
);
}
fn linear_to_srgb_vec4(c: vec4<f32>) -> vec4<f32> {
return vec4<f32>(linear_to_srgb_vec3(c.xyz), c.w);
}
fn normalize_vec4(c: vec4<f32>) -> vec4<f32> {
if (c.z == 0.0) {
// If the normal is stored with only the XY components, there's no need to normalize.
return c;
}
else {
return vec4<f32>(saturate(0.5 * normalize(2 * c.xyz - 1) + 0.5), c.w);
}
}
fn mipmap( id : vec3<u32>) {
let dstSize = textureDimensions(dst).xy;
if (id.x >= dstSize.x || id.y >= dstSize.y) {
return;
}
let size_rcp = vec2f(1.0) / vec2f(dstSize);
// We are not doing this yet, but in some cases we want to take 4 samples in order to apply alpha weighting,
// or to support non multiple of two textures.
let uv0 = (vec2f(id.xy) + vec2f(0.25)) * size_rcp;
let uv1 = uv0 + 0.5 * size_rcp;
var color = vec4f(0.0);
color += textureSampleLevel(src, smp, vec2f(uv0.x, uv0.y), 0);
color += textureSampleLevel(src, smp, vec2f(uv1.x, uv0.y), 0);
color += textureSampleLevel(src, smp, vec2f(uv0.x, uv1.y), 0);
color += textureSampleLevel(src, smp, vec2f(uv1.x, uv1.y), 0);
color *= 0.25;
// This would be the single sample implementation:
// let uv = (vec2f(id.xy) + vec2f(0.5)) * size_rcp;
// var color = textureSampleLevel(src, smp, vec2f(uv.x, uv.y), 0);
if (params.colorMode == 1) {
color = linear_to_srgb_vec4(color);
} else if (params.colorMode == 2) {
color = normalize_vec4(color);
}
textureStore(dst, id.xy, color);
}
fn resize( id : vec3<u32>) {
let dstSize = textureDimensions(dst).xy;
if (id.x >= dstSize.x || id.y >= dstSize.y) {
return;
}
let uv = (vec2f(id.xy) + vec2f(0.5)) / vec2f(dstSize);
var color = textureSampleLevel(src, smp, uv, 0);
if (params.colorMode == 1) {
color = linear_to_srgb_vec4(color);
} else if (params.colorMode == 2) {
color = normalize_vec4(color);
}
textureStore(dst, id.xy, color);
}
fn flipy( id : vec3<u32>) {
let dstSize = textureDimensions(dst).xy;
if (id.x >= dstSize.x || id.y >= dstSize.y) {
return;
}
let uv = (vec2f(f32(id.x), f32(dstSize.y - 1u - id.y)) + vec2f(0.5)) / vec2f(dstSize);
var color = textureSampleLevel(src, smp, uv, 0);
if (params.colorMode == 1) {
color = linear_to_srgb_vec4(color);
} else if (params.colorMode == 2) {
color = normalize_vec4(color);
}
textureStore(dst, id.xy, color);
}
// Fullscreen vertex shader
struct VSOutput {
pos: vec4<f32>,
tex : vec2<f32>
};
fn fullscreen_vs( vertexIndex : u32) -> VSOutput {
var pos = array<vec2<f32>, 4>(
vec2<f32>(-1.0, 1.0),
vec2<f32>( 1.0, 1.0),
vec2<f32>(-1.0, -1.0),
vec2<f32>( 1.0, -1.0)
);
var tex = array<vec2<f32>, 4>(
vec2<f32>(0.0, 0.0),
vec2<f32>(1.0, 0.0),
vec2<f32>(0.0, 1.0),
vec2<f32>(1.0, 1.0)
);
var vs_output : VSOutput;
vs_output.tex = tex[vertexIndex];
vs_output.pos = vec4<f32>(pos[vertexIndex], 0.0, 1.0);
return vs_output;
}
fn mipmap_fs( uv : vec2<f32>) -> vec4<f32> {
var color = textureSample(src, smp, uv);
if (params.colorMode == 2) {
color = normalize_vec4(color);
}
return color;
}
fn resize_fs( uv : vec2<f32>) -> vec4<f32> {
var color = textureSample(src, smp, uv);
if (params.colorMode == 2) {
color = normalize_vec4(color);
}
return color;
}
fn flipy_fs( uv : vec2<f32>) -> vec4<f32> {
var color = textureSample(src, smp, vec2(uv.x, 1 - uv.y));
if (params.colorMode == 2) {
color = normalize_vec4(color);
}
return color;
}
var<storage, read_write> global_counters: array<atomic<u32>, 3>;
var<workgroup> local_opaque: atomic<u32>;
var<workgroup> local_grayscale: atomic<u32>;
var<workgroup> local_invalid_normals: atomic<u32>;
fn detect_channel_count( global_id: vec3<u32>,
local_id: u32) {
if (local_id == 0u) {
atomicStore(&local_opaque, 1u);
atomicStore(&local_grayscale, 1u);
atomicStore(&local_invalid_normals, 0u);
}
workgroupBarrier();
let tex_size = textureDimensions(src);
if (global_id.x < tex_size.x && global_id.y < tex_size.y) {
let color = textureLoad(src, vec2<i32>(global_id.xy), 0);
// Alpha check
if (color.a < 1.0) {
atomicStore(&local_opaque, 0u);
}
// Grayscale check
if (color.r != color.g || color.g != color.b) {
atomicStore(&local_grayscale, 0u);
}
// Normal check
let n = color.rgb * 2.0 - vec3(1.0);
let len = length(n);
if (abs(len - 1.0) > 0.2 || n.z < -0.1) {
atomicAdd(&local_invalid_normals, 1u);
}
}
workgroupBarrier();
if (local_id == 0u) {
// If not opaque, write not-opaque flag.
if (atomicLoad(&local_opaque) == 0u) {
atomicStore(&global_counters[0], 1u);
}
// If not greyscale, write not greyscale flag.
if (atomicLoad(&local_grayscale) == 0u) {
atomicStore(&global_counters[1], 1u);
}
// Add number of texels that are not normal.
atomicAdd(&global_counters[2], atomicLoad(&local_invalid_normals));
}
}
// @@ Compute RMSE?
`;
export {
n as default
};