UNPKG

@ludicon/spark.js

Version:

Real-Time GPU Texture Codecs for the Web

1,133 lines (1,132 loc) 32.8 kB
const V = /* @__PURE__ */ Object.assign({ "./spark_astc_rgb.wgsl": () => import("./spark_astc_rgb-DaSIMKXW.js"), "./spark_astc_rgba.wgsl": () => import("./spark_astc_rgba-BToA2Rcq.js"), "./spark_bc1_rgb.wgsl": () => import("./spark_bc1_rgb-DZwuM1tO.js"), "./spark_bc4_r.wgsl": () => import("./spark_bc4_r-DjThizCH.js"), "./spark_bc5_rg.wgsl": () => import("./spark_bc5_rg-6bO0Gvo9.js"), "./spark_bc7_rgb.wgsl": () => import("./spark_bc7_rgb-FXpBw9fE.js"), "./spark_bc7_rgba.wgsl": () => import("./spark_bc7_rgba-C8Hi2pUY.js"), "./spark_eac_r.wgsl": () => import("./spark_eac_r-D8HGiglc.js"), "./spark_eac_rg.wgsl": () => import("./spark_eac_rg-DvsrLP9h.js"), "./spark_etc2_rgb.wgsl": () => import("./spark_etc2_rgb-C-j5FZpn.js"), "./utils.wgsl": () => import("./utils-BigAnOxg.js") }), I = Object.fromEntries( Object.entries(V).map(([c, e]) => [c.replace("./", ""), async () => (await e()).default]) ), i = { ASTC_4x4_RGB: 0, ASTC_4x4_RGBA: 1, // ASTC_4x4_RGBM: 2, // ASTC_6x6_RGB: 3, EAC_R: 4, EAC_RG: 5, ETC2_RGB: 6, // ETC2_RGBA: 7, // ETC2_RGBM: 8, BC1_RGB: 9, // BC3_RGBA: 10, // BC3_YCoCg: 11, // BC3_RGBM: 12, BC4_R: 13, BC5_RG: 14, // BC6H_RGB: 15, BC7_RGB: 16, BC7_RGBA: 17 }, G = [ /* 0 */ "astc-4x4-rgb", // ASTC_4x4_RGB /* 1 */ "astc-4x4-rgba", // ASTC_4x4_RGBA /* 2 */ null, /* 3 */ null, /* 4 */ "eac-r", // EAC_R /* 5 */ "eac-rg", // EAC_RG /* 6 */ "etc2-rgb", // ETC2_RGB /* 7 */ null, /* 8 */ null, /* 9 */ "bc1-rgb", // BC1_RGB /* 10 */ null, /* 11 */ null, /* 12 */ null, /* 13 */ "bc4-r", // BC4_R /* 14 */ "bc5-rg", // BC5_RG /* 15 */ null, /* 16 */ "bc7-rgb", // BC7_RGB /* 17 */ "bc7-rgba" // BC7_RGBA ], q = [ /* 0 */ "spark_astc_rgb.wgsl", // ASTC_4x4_RGB /* 1 */ "spark_astc_rgba.wgsl", // ASTC_4x4_RGBA /* 2 */ null, /* 3 */ null, /* 4 */ "spark_eac_r.wgsl", // EAC_R /* 5 */ "spark_eac_rg.wgsl", // EAC_RG /* 6 */ "spark_etc2_rgb.wgsl", // ETC2_RGB /* 7 */ null, /* 8 */ null, /* 9 */ "spark_bc1_rgb.wgsl", // BC1_RGB /* 10 */ null, /* 11 */ null, /* 12 */ null, /* 13 */ "spark_bc4_r.wgsl", // BC4_R /* 14 */ "spark_bc5_rg.wgsl", // BC5_RG /* 15 */ null, /* 16 */ "spark_bc7_rgb.wgsl", // BC7_RGB /* 17 */ "spark_bc7_rgba.wgsl" // BC7_RGBA ], W = [ /* 0 */ 16, // ASTC_4x4_RGB /* 1 */ 16, // ASTC_4x4_RGBA /* 2 */ 0, /* 3 */ 0, /* 4 */ 8, // EAC_R /* 5 */ 16, // EAC_RG /* 6 */ 8, // ETC2_RGB /* 7 */ 0, /* 8 */ 0, /* 9 */ 8, // BC1_RGB /* 10 */ 0, /* 11 */ 0, /* 12 */ 0, /* 13 */ 8, // BC4_R /* 14 */ 16, // BC5_RG /* 15 */ 0, /* 16 */ 16, // BC7_RGB /* 17 */ 16 // BC7_RGB ], Y = [ /* 0 */ 4, // ASTC_4x4_RGB /* 1 */ 4, // ASTC_4x4_RGBA /* 2 */ 0, /* 3 */ 0, /* 4 */ 2, // EAC_R /* 5 */ 2, // EAC_RG /* 6 */ 8, // ETC2_RGB /* 7 */ 0, /* 8 */ 0, /* 9 */ 8, // BC1_RGB /* 10 */ 0, /* 11 */ 0, /* 12 */ 0, /* 13 */ 2, // BC4_R /* 14 */ 2, // BC5_RG /* 15 */ 0, /* 16 */ 4, // BC7_RGB /* 17 */ 4 // BC7_RGB ], P = Object.freeze({ "astc-4x4-rgb": i.ASTC_4x4_RGB, "astc-4x4-rgba": i.ASTC_4x4_RGBA, "eac-r": i.EAC_R, "eac-rg": i.EAC_RG, "etc2-rgb": i.ETC2_RGB, "bc1-rgb": i.BC1_RGB, "bc4-r": i.BC4_R, "bc5-rg": i.BC5_RG, "bc7-rgb": i.BC7_RGB, "bc7-rgba": i.BC7_RGBA, // aliases: "astc-rgb": i.ASTC_4x4_RGB, "astc-rgba": i.ASTC_4x4_RGBA, // webgpu aliases: "bc1-rgba-unorm": i.BC1_RGB, "bc1-rgba-unorm-srgb": i.BC1_RGB, "bc4-r-unorm": i.BC4_R, "bc5-rg-unorm": i.BC5_RG, "bc7-rgba-unorm": i.BC7_RGBA, "bc7-rgba-unorm-srgb": i.BC7_RGBA, "etc2-rgb8unorm": i.ETC2_RGB, "etc2-rgb8unorm-srgb": i.ETC2_RGB, "eac-r11unorm": i.EAC_R, "eac-rg11unorm": i.EAC_RG, "astc-4x4-unorm": i.ASTC_4x4_RGBA, "astc-4x4-unorm-srgb": i.ASTC_4x4_RGBA }), $ = [ /* 0 */ "astc-4x4-unorm", // ASTC_4x4_RGB /* 1 */ "astc-4x4-unorm", // ASTC_4x4_RGBA /* 2 */ null, /* 3 */ null, /* 4 */ "eac-r11unorm", // EAC_R /* 5 */ "eac-rg11unorm", // EAC_RG /* 6 */ "etc2-rgb8unorm", // ETC2_RGB /* 7 */ null, /* 8 */ null, /* 9 */ "bc1-rgba-unorm", // BC1_RGB /* 10 */ null, /* 11 */ null, /* 12 */ null, /* 13 */ "bc4-r-unorm", // BC4_R /* 14 */ "bc5-rg-unorm", // BC5_RG /* 15 */ null, /* 16 */ "bc7-rgba-unorm", // BC7_RGB /* 17 */ "bc7-rgba-unorm" // BC7_RGB ], X = [ /* 0 */ !0, // ASTC_4x4_RGB /* 1 */ !0, // ASTC_4x4_RGBA /* 2 */ null, /* 3 */ null, /* 4 */ !1, // EAC_R /* 5 */ !1, // EAC_RG /* 6 */ !0, // ETC2_RGB /* 7 */ null, /* 8 */ null, /* 9 */ !0, // BC1_RGB /* 10 */ null, /* 11 */ null, /* 12 */ null, /* 13 */ !1, // BC4_R /* 14 */ !1, // BC5_RG /* 15 */ null, /* 16 */ !0, // BC7_RGB /* 17 */ !0 // BC7_RGB ]; function w(c, e) { if (!c) throw new Error(e); } function H(c) { return typeof GPUDevice < "u" && c instanceof GPUDevice; } function j() { return ["iPad Simulator", "iPhone Simulator", "iPod Simulator", "iPad", "iPhone", "iPod"].includes(navigator.platform) || // iPad on iOS 13 detection navigator.userAgent.includes("Mac") && "ontouchend" in document; } function O() { const c = navigator.userAgent.match(/Safari\/(\d+(\.\d+)?)/); return c && parseFloat(c[1]); } function k() { const c = navigator.userAgent.match(/Firefox\/(\d+(\.\d+)?)/); return c && parseFloat(c[1]); } function Q(c) { const e = /* @__PURE__ */ new Set(), r = { "texture-compression-bc": [ i.BC1_RGB, i.BC4_R, i.BC5_RG, i.BC7_RGB, i.BC7_RGBA ], "texture-compression-etc2": [i.ETC2_RGB, i.EAC_R, i.EAC_RG], "texture-compression-astc": [i.ASTC_4x4_RGB, i.ASTC_4x4_RGBA] }; for (const [t, s] of Object.entries(r)) if (c.features.has(t)) for (const n of s) e.add(n); return e; } function Z(c) { const e = document.createElement("canvas"); e.width = c.width, e.height = c.height; const r = e.getContext("2d"); r.drawImage(c, 0, 0); const t = r.getImageData(0, 0, c.width, c.height); return new Uint8Array(t.data.buffer); } function J(c) { return /\.svg(?:$|\?)/i.test(c) || /^data:image\/svg\+xml[,;]/i.test(c); } function K(c) { return new Promise((e, r) => { const t = new Image(); t.crossOrigin = "anonymous", t.decoding = "async", t.onload = () => e(t), t.onerror = r, t.src = c; }); } async function ee(c, e = {}) { const r = await fetch(c, { mode: "cors" }); if (!r.ok) throw new Error(`HTTP ${r.status} for ${c}`); const t = await r.blob(); return createImageBitmap(t, { imageOrientation: e.flipY ? "flipY" : "none", colorSpaceConversion: e.colorSpaceConversion ?? "none", premultiplyAlpha: "none" }); } const te = O(); function N(c) { return J(c) || te ? K(c) : ee(c); } const L = 256, F = 4; function re(c, e, r, t) { let s = 0, n = 0; const a = []; do { const o = Math.ceil(c / 4), u = Math.ceil(e / 4), l = Math.ceil(o * r / L) * L, f = u * l; s++, a.push({ offset: n, alignedSize: f, w: c, h: e, bw: o, bh: u, bytesPerRow: l }), n += f, c = Math.max(1, Math.floor(c / 2)), e = Math.max(1, Math.floor(e / 2)); } while (t && (c >= F || e >= F)); return { mipmapCount: s, outputSize: n, bufferRanges: a }; } class D { #e; #f; #l = []; #g; #a = !1; #s; #o; #u; #m; #i; #n = new Array(3); #r; #p; #c; #G = 0; /** * Initialize the encoder by detecting available compression formats. * @param {GPUDevice} device - WebGPU device. * @param {Object} options - Encoder options. * @param {boolean} options.preload - Whether to preload all encoder pipelines (false by default). * @returns {Promise<void>} Resolves when initialization is complete. */ static async create(e, r = {}) { const t = new D(); return await t.#B(e, r.preload ?? !1, r.useTimestampQueries ?? !1), t; } /** * Returns a list of supported texture compression format names. * * This function checks a predefined list of common GPU compression formats * (ASTC, ETC2, EAC, BCn) and filters it based on the formats actually supported * by the current device as determined by `Spark.supportedFormats`. * * @returns {string[]} An array of format names (e.g., "bc1-rgb", "astc-4x4-rgba") that are supported on the current platform. * * @example * const spark = await Spark.create(device); * const formats = spark.enumerateSupportedFormats(); * console.log("Supported formats:", formats); */ enumerateSupportedFormats() { const e = [ "astc-4x4-rgb", "astc-4x4-rgba", "eac-r", "eac-rg", "etc2-rgb", "bc1-rgb", "bc4-r", "bc5-rg", "bc7-rgb", "bc7-rgba" ], r = []; for (const t of e) { const s = P[t]; if (this.#t(s)) { const n = Y[s]; r.push({ format: t, ratio: n }); } } return r; } /** * Determines the set of WebGPU features to request when initializing the device. * * This function inspects the given `adapter` to see which texture compression and shader * features are available, and returns a list of those that are both supported and safe to enable. * * @param {GPUAdapter} adapter - The WebGPU adapter returned from `navigator.gpu.requestAdapter()`. * @returns {string[]} An array of WebGPU feature names to request during `adapter.requestDevice()`. * * @example * // Create device using the features required by spark.js * const adapter = await navigator.gpu.requestAdapter() * const requiredFeatures = Spark.getRequiredFeatures(adapter) * const device = await adapter.requestDevice({ requiredFeatures }) * * // Create spark object for the given device. * const spark = Spark.create(device) */ static getRequiredFeatures(e) { const r = []; return !j() && e.features.has("texture-compression-bc") && r.push("texture-compression-bc"), e.features.has("texture-compression-etc2") && r.push("texture-compression-etc2"), e.features.has("texture-compression-astc") && r.push("texture-compression-astc"), e.features.has("shader-f16") && r.push("shader-f16"), e.features.has("timestamp-query") && r.push("timestamp-query"), r; } /** * Try to determine the best compression options automatically. Do not use this in production, this is * for the convenience of the spark.js image viewer only. * * @param {string | HTMLImageElement | ImageBitmap | GPUTexture} source - Image input. * @param {Object} options - Encoding options. * @returns {Object} - Recommended encoding options with an explicit encoding format. */ async selectPreferredOptions(e, r = {}) { if (r.format == null || r.format == "auto") { const t = e instanceof Image || e instanceof ImageBitmap || e instanceof GPUTexture ? e : await N(e); r.format = "auto"; const s = await this.#d(r, t); r.format = G[s], t instanceof GPUTexture && t.format.endsWith("-srgb") && (r.srgb = !0), (s == i.EAC_RG || s == i.BC5_RG) && (r.normal = !0); } return r; } /** * Load an image and encode it to a compressed GPU texture. * * @param {string | HTMLImageElement | ImageBitmap | GPUTexture} source * The image to encode. Can be a GPUTexture, URL, DOM image or ImageBitmap. * * @param {Object} [options] - Optional configuration for encoding. * * @param {string} [options.format="rgb"] * Desired block compression format. Can be specified in several ways: * - A channel mask indicating the number of channels in your input: * "rgba", "rgb", "rg", or "r". The actual GPU format is selected * based on device capabilities. * - An explicit WebGPU BC, ETC, or ASTC format name, or an abbreviated * form such as "bc7" or "astc". Note: only 4x4 LDR formats are supported. * - "auto" to analyze the input texture and detect the required channels. * This has some overhead, so specifying a format explicitly is preferred. * * @param {boolean} [options.alpha] * Hint for the automatic format selector. When no explicit format is provided, * the format is assumed to be "rgb". Supplying `alpha: true` will favor RGBA formats. * * @param {boolean} [options.mips=false] | [options.generateMipmaps=false] * Whether to generate mipmaps. Mipmaps are generated with a basic box filter * in linear space. * * @param {boolean} [options.srgb=false] * Whether to encode the image in an sRGB format. Also affects mipmap generation. * The `srgb` mode can also be inferred from the `format`. * * @param {boolean} [options.normal=false] * Interpret the image as a normal map. Affects automatic format selection, * favoring "bc5" and "eac-rg" formats. * * @param {boolean} [options.flipY=false] * Whether to vertically flip the image before encoding. * * @returns {Promise<GPUTexture>} A promise resolving to the encoded GPU texture. */ async encodeTexture(e, r = {}) { w(this.#e, "Spark is not initialized"); const t = e instanceof Image || e instanceof ImageBitmap || e instanceof GPUTexture ? e : await N(e); console.log("Loaded image", t); const s = await this.#d(r, t), n = this.#h(s), a = Math.ceil(t.width / 4) * 4, o = Math.ceil(t.height / 4) * 4, u = W[s], l = r.generateMipmaps || r.mips, { mipmapCount: f, outputSize: h, bufferRanges: m } = re(a, o, u, l), b = (r.srgb || r.format?.endsWith("srgb")) && X[s], _ = b ? 1 : r.normal ? 2 : 0, B = $[s] + (b ? "-srgb" : ""), A = b ? ["rgba8unorm", "rgba8unorm-srgb"] : ["rgba8unorm"], x = this.#G++; console.time("create input texture #" + x); let C = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST; this.#a ? C |= GPUTextureUsage.RENDER_ATTACHMENT : C |= GPUTextureUsage.STORAGE_BINDING; const y = r.flipY || a != t.width || o != t.height; !y && !(t instanceof GPUTexture) && (C |= GPUTextureUsage.RENDER_ATTACHMENT); const p = this.#e.createCommandEncoder(); p.pushDebugGroup?.("spark process texture"), this.#r && typeof p.writeTimestamp == "function" && p.writeTimestamp(this.#r, 0); let d; (y || !(t instanceof GPUTexture && !l)) && (d = this.#e.createTexture({ size: [a, o, 1], mipLevelCount: f, format: "rgba8unorm", usage: C, viewFormats: A })); let R; y ? t instanceof GPUTexture ? this.#_(p, t, d, a, o, b, r.flipY) : (R = this.#e.createTexture({ size: [t.width, t.height, 1], mipLevelCount: 1, format: "rgba8unorm", // RENDER_ATTACHMENT usage is necessary for copyExternalImageToTexture usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, viewFormats: A }), this.#e.queue.copyExternalImageToTexture( { source: t }, { texture: R }, { width: t.width, height: t.height } ), this.#_(p, R, d, a, o, _, r.flipY)) : t instanceof GPUTexture ? l ? p.copyTextureToTexture({ texture: t }, { texture: d }, { width: a, height: o }) : d = t : this.#e.queue.copyExternalImageToTexture({ source: t }, { texture: d }, { width: a, height: o }), l && this.#w(p, d, f, a, o, _), p.popDebugGroup?.(), console.timeEnd("create input texture #" + x); const E = this.#e.createTexture({ size: [a, o, 1], mipLevelCount: f, format: B, usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST }), U = this.#e.createBuffer({ size: h, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC }), S = `dispatch compute shader '${G[s]}' #${x}`; console.time(S), p.pushDebugGroup?.("spark encode texture"); let M = {}; this.#r && typeof p.writeTimestamp != "function" && (M = { writeTimestamps: { querySet: this.#r, beginningOfPassWriteIndex: 0, endOfPassWriteIndex: 1 } }); const v = await n, T = p.beginComputePass(M); T.setPipeline(v); for (let g = 0; g < f; g++) { const z = this.#e.createBindGroup({ layout: v.getBindGroupLayout(0), entries: [ { binding: 0, resource: d.createView({ baseMipLevel: g, mipLevelCount: 1 }) }, { binding: 1, resource: this.#i }, { binding: 2, resource: { buffer: U, offset: m[g].offset, size: m[g].size } } ] }); T.setBindGroup(0, z), T.dispatchWorkgroups(Math.ceil(m[g].bw / 16), Math.ceil(m[g].bh / 16)); } T.end(); for (let g = 0; g < f; g++) p.copyBufferToTexture( { buffer: U, offset: m[g].offset, bytesPerRow: m[g].bytesPerRow, rowsPerImage: m[g].bh }, { texture: E, mipLevel: g }, { width: m[g].bw * 4, height: m[g].bh * 4, depthOrArrayLayers: 1 } ); return this.#r && typeof p.writeTimestamp == "function" && p.writeTimestamp(this.#r, 1), p.popDebugGroup?.(), this.#e.queue.submit([p.finish()]), console.timeEnd(S), R?.destroy(), d != t && d?.destroy(), U?.destroy(), E; } /** * Returns the time (in milliseconds) it took to perform the most recent `encodeTexture()` call. * * This function resolves GPU timestamp queries that were recorded before and after the * compression dispatch in `encodeTexture()`. It waits for the GPU to finish processing, * reads back the timestamps, and computes the elapsed GPU time. * * Must be called *after* `encodeTexture()` has been invoked and submitted. * * @async * @returns {Promise<number>} Elapsed GPU time in milliseconds. * * @example * await spark.encodeTexture(...); * const elapsed = await spark.getTimeElapsed(); * console.log(`Encode took ${elapsed.toFixed(2)} ms`); * * @throws {Error} If the GPU work has not been submitted, or if timestamp queries fail. */ async getTimeElapsed() { if (!this.#r) return 0; const e = this.#e.createCommandEncoder(); e.resolveQuerySet(this.#r, 0, 2, this.#p, 0), e.copyBufferToBuffer(this.#p, 0, this.#c, 0, 16), this.#e.queue.submit([e.finish()]), await this.#e.queue.onSubmittedWorkDone(), await this.#c.mapAsync(GPUMapMode.READ); const r = this.#c.getMappedRange(), t = new BigUint64Array(r), s = t[0], n = t[1]; return this.#c.unmap(), Number(n - s) / 1e6; } async #B(e, r, t) { w(e, "device is required"), w(H(e), "device is not a WebGPU device"), this.#e = e, this.#f = Q(this.#e), this.#i = this.#e.createSampler({ magFilter: "linear", minFilter: "linear" }); for (let s = 0; s < 3; s++) this.#n[s] = this.#e.createBuffer({ size: 4, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST }), this.#e.queue.writeBuffer(this.#n[s], 0, new Uint32Array([s])); if (t && this.#e.features.has("timestamp-query")) { const s = O(), n = k(); (!s || s >= 26) && !n && (this.#r = this.#e.createQuerySet({ type: "timestamp", count: 2 }), this.#p = this.#e.createBuffer({ size: 16, // 2 timestamps × 8 bytes each usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.QUERY_RESOLVE }), this.#c = this.#e.createBuffer({ size: 16, // 2 timestamps × 8 bytes each usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ })); } if (this.#g = this.#e.features.has("shader-f16"), await this.#C(), r) { let s; Array.isArray(r) ? s = r.map((n) => this.#b(n, !1)) : s = this.#f; for (const n of s) n !== void 0 && !this.#l[n] && this.#h(n).catch((a) => { console.error(`Failed to preload pipeline for format ${n}:`, a); }); } } async #C() { const e = this.#e.createShaderModule({ code: await I["utils.wgsl"](), label: "utils" }); if (typeof e.compilationInfo == "function") { const t = await e.compilationInfo(); if (t.messages.some((s) => s.type == "error")) { console.error("WGSL compilation errors:"); for (const s of t.messages) console.error(s); throw new Error("Shader compilation failed"); } } if (k() && (this.#a = !0), this.#a) { this.#s = {}, this.#o = {}, this.#u = {}; const t = ["rgba8unorm-srgb", "rgba8unorm"]; for (const s of t) this.#s[s] = this.#e.createRenderPipeline({ label: `mipmap-pipeline-${s}`, layout: "auto", vertex: { module: e, entryPoint: "fullscreen_vs" }, fragment: { module: e, entryPoint: "mipmap_fs", targets: [{ format: s }] }, primitive: { topology: "triangle-strip", stripIndexFormat: "uint32" } }), this.#o[s] = this.#e.createRenderPipeline({ label: `resize-pipeline-${s}`, layout: "auto", vertex: { module: e, entryPoint: "fullscreen_vs" }, fragment: { module: e, entryPoint: "resize_fs", targets: [{ format: s }] }, primitive: { topology: "triangle-strip", stripIndexFormat: "uint32" } }), this.#u[s] = this.#e.createRenderPipeline({ label: `flip-y-pipeline-${s}`, layout: "auto", vertex: { module: e, entryPoint: "fullscreen_vs" }, fragment: { module: e, entryPoint: "flipy_fs", targets: [{ format: s }] }, primitive: { topology: "triangle-strip", stripIndexFormat: "uint32" } }); } else this.#s = this.#e.createComputePipeline({ layout: "auto", compute: { module: e, entryPoint: "mipmap" } }), this.#o = this.#e.createComputePipeline({ layout: "auto", compute: { module: e, entryPoint: "resize" } }), this.#u = this.#e.createComputePipeline({ layout: "auto", compute: { module: e, entryPoint: "flipy" } }); this.#m = await this.#e.createComputePipelineAsync({ layout: "auto", compute: { module: e, entryPoint: "detect_channel_count" } }); } async #h(e) { if (this.#l[e]) return this.#l[e]; const r = (async () => { console.time("loadPipeline " + G[e]); const t = q[e]; w(t, `No shader available for format ${G[e]}`); let s = await I[t](); this.#g || (s = s.replace(/^enable f16;\s*/m, "").replace(/\bf16\b/g, "f32").replace(/\bvec([234])h\b/g, "vec$1f").replace(/\bmat([234]x[234])h/g, "mat$1f").replace(/\b(\d*\.\d+|\d+\.)h\b/g, "$1")); const n = this.#e.createShaderModule({ code: s, label: G[e] }); if (typeof n.getCompilationInfo == "function") { const o = await n.getCompilationInfo(); if (o.messages.some((u) => u.type == "error")) { console.error("WGSL compilation errors:"); for (const u of o.messages) console.error(u); throw new Error("Shader compilation failed"); } } const a = await this.#e.createComputePipelineAsync({ layout: "auto", compute: { module: n, entryPoint: "main" } }); return console.timeEnd("loadPipeline " + G[e]), a; })(); return this.#l[e] = r, r; } #t(e) { return this.#f.has(e); } #b(e, r) { const t = P[e]; if (t != null && this.#t(t)) return t; const s = r ? [ "bc4-r", "eac-r", "bc5-rg", "eac-rg", "bc1-rgb", "etc2-rgb", "bc7-rgb", "astc-rgb", "astc-4x4-rgb", "bc7-rgba", "astc-rgba", "astc-4x4-rgba" ] : [ "bc4-r", "eac-r", "bc5-rg", "eac-rg", "bc7-rgb", "bc1-rgb", "astc-rgb", "astc-4x4-rgb", "etc2-rgb", "bc7-rgba", "astc-rgba", "astc-4x4-rgba" ]; for (const n of s) if (n.includes(e) && this.#t(P[n])) return P[n]; } async #d(e, r) { if (e.format == null) e.format = "rgb"; else if (e.format == "auto") { if (e.alpha) { if (this.#t(i.BC7_RGBA)) return i.BC7_RGBA; if (this.#t(i.ASTC_4x4_RGBA)) return i.ASTC_4x4_RGBA; } else if (e.srgb) { if (this.#t(i.BC7_RGB)) return i.BC7_RGB; if (this.#t(i.ASTC_4x4_RGB)) return i.ASTC_4x4_RGB; if (this.#t(i.BC1_RGB)) return i.BC1_RGB; if (this.#t(i.ETC2_RGB)) return i.ETC2_RGB; } else if (e.normal) { if (this.#t(i.BC5_RG)) return i.BC5_RG; if (this.#t(i.EAC_RG)) return i.EAC_RG; } else { let s; if (r instanceof GPUTexture) r.format == "r8unorm" || r.format == "r16unorm" ? s = 1 : r.format == "rg8unorm" || r.format == "rg16unorm" ? s = 2 : s = await this.#T(r); else { const n = Z(r); s = this.#R(n); } if (s == 4) { if (this.#t(i.BC7_RGBA)) return i.BC7_RGBA; if (this.#t(i.ASTC_4x4_RGBA)) return i.ASTC_4x4_RGBA; } else if (s == 3) { if (this.#t(i.BC7_RGB)) return i.BC7_RGB; if (this.#t(i.ASTC_4x4_RGB)) return i.ASTC_4x4_RGB; if (this.#t(i.BC1_RGB)) return i.BC1_RGB; if (this.#t(i.ETC2_RGB)) return i.ETC2_RGB; } else if (s == 2) { if (this.#t(i.BC5_RG)) return i.BC5_RG; if (this.#t(i.EAC_RG)) return i.EAC_RG; } else if (s == 1) { if (this.#t(i.BC4_R)) return i.BC4_R; if (this.#t(i.EAC_R)) return i.EAC_R; } } throw new Error("No supported format found."); } const t = this.#b(e.format, e.preferLowQuality); if (t === void 0) throw new Error(`Unsupported format: ${e.format}`); return t; } #R(e) { let r = !0, t = !0, s = 0; const n = Math.min(1024 * 128, e.length); for (let a = 0; a < n; a += 4) { const o = e[a] / 255, u = e[a + 1] / 255, l = e[a + 2] / 255; e[a + 3] < 255 && (r = !1), (o != u || u != l) && (t = !1); const h = 2 * o - 1, m = 2 * u - 1, b = 2 * l - 1, _ = h * h + m * m + b * b, B = Math.sqrt(_); (Math.abs(B - 1) > 0.2 || b < -0.1) && (s += 1); } return r ? t ? 1 : 16 * s < n ? 2 : 3 : 4; } async #T(e) { const t = this.#e.createBuffer({ size: 12, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC }), s = this.#e.createBuffer({ size: 12, usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ }), n = this.#e.createBindGroup({ layout: this.#m.getBindGroupLayout(0), entries: [ { binding: 0, resource: e.createView() }, { binding: 1, resource: { buffer: t } } ] }), a = this.#e.createCommandEncoder(), o = a.beginComputePass(); o.setPipeline(this.#m), o.setBindGroup(0, n); const { width: u, height: l } = e, f = Math.ceil(u / 8), h = Math.ceil(l / 8); o.dispatchWorkgroups(f, h), o.end(), a.copyBufferToBuffer(t, 0, s, 0, 12), this.#e.queue.submit([a.finish()]), await this.#e.queue.onSubmittedWorkDone(), await s.mapAsync(GPUMapMode.READ); const m = new Uint32Array(s.getMappedRange()), b = m[0] == 0, _ = m[1] == 0, B = m[2]; return s.unmap(), s.destroy(), t.destroy(), b ? _ ? 1 : 4 * B < u * l ? 2 : 3 : 4; } // Apply scaling and flipY transform. #_(e, r, t, s, n, a, o) { if (this.#a) { this.#P(e, r, t, s, n, a, o); return; } const u = e.beginComputePass(), l = o ? this.#u : this.#o; u.setPipeline(l); const f = this.#e.createBindGroup({ layout: l.getBindGroupLayout(0), entries: [ { binding: 0, resource: r.createView({ baseMipLevel: 0, mipLevelCount: 1, format: a == 1 ? "rgba8unorm-srgb" : "rgba8unorm", usage: GPUTextureUsage.TEXTURE_BINDING }) }, { binding: 1, resource: t.createView({ baseMipLevel: 0, mipLevelCount: 1, dimension: "2d", format: "rgba8unorm", usage: GPUTextureUsage.STORAGE_BINDING }) }, { binding: 2, resource: this.#i }, { binding: 3, resource: { buffer: this.#n[a] } } ] }); u.setBindGroup(0, f), u.dispatchWorkgroups(Math.ceil(s / 8), Math.ceil(n / 8)), u.end(); } // Apply scaling and flipY transform. #P(e, r, t, s, n, a, o) { const u = a == 1 ? "rgba8unorm-srgb" : "rgba8unorm", l = t.createView({ baseMipLevel: 0, mipLevelCount: 1, dimension: "2d", format: u, usage: GPUTextureUsage.RENDER_ATTACHMENT }), f = e.beginRenderPass({ colorAttachments: [ { view: l, loadOp: "clear", storeOp: "store", clearValue: [0, 0, 0, 0] } ] }), h = o ? this.#u[u] : this.#o[u]; f.setPipeline(h); const m = this.#e.createBindGroup({ layout: h.getBindGroupLayout(0), entries: [ { binding: 0, resource: r.createView({ baseMipLevel: 0, mipLevelCount: 1, format: a == 1 ? "rgba8unorm-srgb" : "rgba8unorm", usage: GPUTextureUsage.TEXTURE_BINDING }) }, { binding: 2, resource: this.#i }, { binding: 3, resource: { buffer: this.#n[a] } } ] }); f.setBindGroup(0, m), f.draw(4, 1, 0, 0), f.end(); } async #w(e, r, t, s, n, a) { let o = s, u = n; if (this.#a) for (let l = 0; l < t - 1; l++) o = Math.max(1, Math.floor(o / 2)), u = Math.max(1, Math.floor(u / 2)), this.#y(e, r, l, l + 1, o, u, a); else { const l = e.beginComputePass(); l.setPipeline(this.#s); for (let f = 0; f < t - 1; f++) o = Math.max(1, Math.floor(o / 2)), u = Math.max(1, Math.floor(u / 2)), this.#x(l, r, f, f + 1, o, u, a); l.end(); } } #x(e, r, t, s, n, a, o) { const u = this.#e.createBindGroup({ layout: this.#s.getBindGroupLayout(0), entries: [ { binding: 0, resource: r.createView({ baseMipLevel: t, mipLevelCount: 1, format: o == 1 ? "rgba8unorm-srgb" : "rgba8unorm", usage: GPUTextureUsage.TEXTURE_BINDING }) }, { binding: 1, resource: r.createView({ baseMipLevel: s, mipLevelCount: 1, dimension: "2d", format: "rgba8unorm", usage: GPUTextureUsage.STORAGE_BINDING }) }, { binding: 2, resource: this.#i }, { binding: 3, resource: { buffer: this.#n[o] } } ] }); e.setBindGroup(0, u), e.dispatchWorkgroups(Math.ceil(n / 8), Math.ceil(a / 8)); } #y(e, r, t, s, n, a, o) { const u = o == 1 ? "rgba8unorm-srgb" : "rgba8unorm", l = r.createView({ baseMipLevel: s, mipLevelCount: 1, dimension: "2d", format: u, usage: GPUTextureUsage.RENDER_ATTACHMENT }), f = e.beginRenderPass({ colorAttachments: [ { view: l, loadOp: "clear", storeOp: "store", clearValue: [0, 0, 0, 0] } ] }), h = this.#e.createBindGroup({ layout: this.#s[u].getBindGroupLayout(0), entries: [ { binding: 0, resource: r.createView({ baseMipLevel: t, mipLevelCount: 1, format: u, usage: GPUTextureUsage.TEXTURE_BINDING }) }, { binding: 2, resource: this.#i }, { binding: 3, resource: { buffer: this.#n[o] } } ] }); f.setPipeline(this.#s[u]), f.setBindGroup(0, h), f.draw(4, 1, 0, 0), f.end(); } } export { D as Spark };