dioxus-chimeras 0.2.0

Dioxus integration for the chimeras camera library. Provides a preview server, frame registry, camera pump, a use_camera_stream hook, and a StreamPreview component for rendering live chimeras frames inside a Dioxus desktop app via WebGL2.
(function () {
    const VS = `#version 300 es

in vec2 position;

out vec2 v_uv;

void main() {

    v_uv = vec2((position.x + 1.0) * 0.5, 1.0 - (position.y + 1.0) * 0.5);

    gl_Position = vec4(position, 0.0, 1.0);

}`;

    const FS_NV12 = `#version 300 es

precision mediump float;

in vec2 v_uv;

out vec4 frag_color;

uniform sampler2D y_tex;

uniform sampler2D uv_tex;

uniform vec2 u_crop;

void main() {

    vec2 uv = v_uv * u_crop;

    float y = texture(y_tex, uv).r;

    vec2 cbcr = texture(uv_tex, uv).rg;

    float cb = cbcr.r - 0.5;

    float cr = cbcr.g - 0.5;

    float c = y - 0.0625;

    float r = clamp(1.164 * c + 1.596 * cr, 0.0, 1.0);

    float g = clamp(1.164 * c - 0.391 * cb - 0.813 * cr, 0.0, 1.0);

    float b = clamp(1.164 * c + 2.018 * cb, 0.0, 1.0);

    frag_color = vec4(r, g, b, 1.0);

}`;

    const FS_PACKED = `#version 300 es

precision mediump float;

in vec2 v_uv;

out vec4 frag_color;

uniform sampler2D rgba_tex;

uniform vec2 u_crop;

uniform float u_swap_rb;

void main() {

    vec4 c = texture(rgba_tex, v_uv * u_crop);

    vec3 rgb = mix(c.rgb, c.bgr, u_swap_rb);

    frag_color = vec4(rgb, 1.0);

}`;

    function setupCanvas(canvas) {
        if (canvas._chimerasInit) return;
        canvas._chimerasInit = true;
        const url = canvas.dataset.previewUrl;
        if (!url) return;
        const gl = canvas.getContext("webgl2", { alpha: false, antialias: false });
        if (!gl) return;

        function compile(src, type) {
            const s = gl.createShader(type);
            gl.shaderSource(s, src);
            gl.compileShader(s);
            if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
                gl.deleteShader(s);
                return null;
            }
            return s;
        }
        function link(vs, fs) {
            const p = gl.createProgram();
            gl.attachShader(p, vs);
            gl.attachShader(p, fs);
            gl.linkProgram(p);
            if (!gl.getProgramParameter(p, gl.LINK_STATUS)) {
                gl.deleteProgram(p);
                return null;
            }
            return p;
        }
        const vs = compile(VS, gl.VERTEX_SHADER);
        const progNv12 = link(vs, compile(FS_NV12, gl.FRAGMENT_SHADER));
        const progPacked = link(vs, compile(FS_PACKED, gl.FRAGMENT_SHADER));

        const quad = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, quad);
        gl.bufferData(
            gl.ARRAY_BUFFER,
            new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),
            gl.STATIC_DRAW,
        );
        function bindQuad(program) {
            const loc = gl.getAttribLocation(program, "position");
            gl.bindBuffer(gl.ARRAY_BUFFER, quad);
            gl.enableVertexAttribArray(loc);
            gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
        }
        function makeTexture() {
            const t = gl.createTexture();
            gl.bindTexture(gl.TEXTURE_2D, t);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
            return t;
        }
        const yTex = makeTexture();
        const uvTex = makeTexture();
        const packedTex = makeTexture();

        let fetching = false;
        let lastCounter = 0;
        let currentFormat = 0;
        let currentWidth = 0;
        let currentHeight = 0;
        let currentStride = 0;
        let yTexDims = { w: 0, h: 0 };
        let uvTexDims = { w: 0, h: 0 };
        let packedTexDims = { w: 0, h: 0 };

        async function fetchAndUpload() {
            if (fetching) return;
            fetching = true;
            try {
                const response = await fetch(url, { cache: "no-store" });
                if (!response.ok) return;
                const buffer = await response.arrayBuffer();
                if (buffer.byteLength < 24) return;
                const view = new DataView(buffer);
                if (
                    view.getUint8(0) !== 0x43 ||
                    view.getUint8(1) !== 0x48 ||
                    view.getUint8(2) !== 0x49 ||
                    view.getUint8(3) !== 0x4d
                ) {
                    return;
                }
                const format = view.getUint8(5);
                const width = view.getUint32(8, true);
                const height = view.getUint32(12, true);
                const stride = view.getUint32(16, true);
                const counter = view.getUint32(20, true);
                if (counter === lastCounter && format === currentFormat) return;
                lastCounter = counter;
                currentFormat = format;
                currentWidth = width;
                currentHeight = height;
                currentStride = stride;
                if (format === 0 || width === 0 || height === 0) return;
                const payload = new Uint8Array(buffer, 24);
                gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
                if (format === 1) {
                    const ySize = stride * height;
                    const uvSize = stride * (height >> 1);
                    if (payload.byteLength < ySize + uvSize) return;
                    const yBytes = payload.subarray(0, ySize);
                    const uvBytes = payload.subarray(ySize, ySize + uvSize);
                    gl.activeTexture(gl.TEXTURE0);
                    gl.bindTexture(gl.TEXTURE_2D, yTex);
                    if (yTexDims.w !== stride || yTexDims.h !== height) {
                        gl.texImage2D(
                            gl.TEXTURE_2D,
                            0,
                            gl.R8,
                            stride,
                            height,
                            0,
                            gl.RED,
                            gl.UNSIGNED_BYTE,
                            yBytes,
                        );
                        yTexDims = { w: stride, h: height };
                    } else {
                        gl.texSubImage2D(
                            gl.TEXTURE_2D,
                            0,
                            0,
                            0,
                            stride,
                            height,
                            gl.RED,
                            gl.UNSIGNED_BYTE,
                            yBytes,
                        );
                    }
                    const uvW = stride >> 1;
                    const uvH = height >> 1;
                    gl.activeTexture(gl.TEXTURE1);
                    gl.bindTexture(gl.TEXTURE_2D, uvTex);
                    if (uvTexDims.w !== uvW || uvTexDims.h !== uvH) {
                        gl.texImage2D(
                            gl.TEXTURE_2D,
                            0,
                            gl.RG8,
                            uvW,
                            uvH,
                            0,
                            gl.RG,
                            gl.UNSIGNED_BYTE,
                            uvBytes,
                        );
                        uvTexDims = { w: uvW, h: uvH };
                    } else {
                        gl.texSubImage2D(
                            gl.TEXTURE_2D,
                            0,
                            0,
                            0,
                            uvW,
                            uvH,
                            gl.RG,
                            gl.UNSIGNED_BYTE,
                            uvBytes,
                        );
                    }
                } else if (format === 2 || format === 3) {
                    const rowPixels = stride >> 2;
                    if (payload.byteLength < stride * height) return;
                    const slice = payload.subarray(0, stride * height);
                    gl.activeTexture(gl.TEXTURE0);
                    gl.bindTexture(gl.TEXTURE_2D, packedTex);
                    if (packedTexDims.w !== rowPixels || packedTexDims.h !== height) {
                        gl.texImage2D(
                            gl.TEXTURE_2D,
                            0,
                            gl.RGBA8,
                            rowPixels,
                            height,
                            0,
                            gl.RGBA,
                            gl.UNSIGNED_BYTE,
                            slice,
                        );
                        packedTexDims = { w: rowPixels, h: height };
                    } else {
                        gl.texSubImage2D(
                            gl.TEXTURE_2D,
                            0,
                            0,
                            0,
                            rowPixels,
                            height,
                            gl.RGBA,
                            gl.UNSIGNED_BYTE,
                            slice,
                        );
                    }
                }
            } catch (_) {
            } finally {
                fetching = false;
            }
        }

        function resize() {
            const parent = canvas.parentElement;
            if (!parent) return;
            const rect = parent.getBoundingClientRect();
            const ratio = window.devicePixelRatio || 1;
            const w = Math.max(1, Math.floor(rect.width * ratio));
            const h = Math.max(1, Math.floor(rect.height * ratio));
            if (canvas.width !== w || canvas.height !== h) {
                canvas.width = w;
                canvas.height = h;
            }
        }

        function render() {
            resize();
            gl.viewport(0, 0, canvas.width, canvas.height);
            gl.clearColor(0, 0, 0, 1);
            gl.clear(gl.COLOR_BUFFER_BIT);
            if (currentFormat === 1 && currentWidth > 0 && currentHeight > 0) {
                const cropX = currentStride > 0 ? currentWidth / currentStride : 1;
                gl.useProgram(progNv12);
                bindQuad(progNv12);
                gl.uniform1i(gl.getUniformLocation(progNv12, "y_tex"), 0);
                gl.uniform1i(gl.getUniformLocation(progNv12, "uv_tex"), 1);
                gl.uniform2f(gl.getUniformLocation(progNv12, "u_crop"), cropX, 1.0);
                gl.activeTexture(gl.TEXTURE0);
                gl.bindTexture(gl.TEXTURE_2D, yTex);
                gl.activeTexture(gl.TEXTURE1);
                gl.bindTexture(gl.TEXTURE_2D, uvTex);
                gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
            } else if (
                (currentFormat === 2 || currentFormat === 3)
                && currentWidth > 0
                && currentHeight > 0
            ) {
                const rowPixels = currentStride >> 2;
                const cropX = rowPixels > 0 ? currentWidth / rowPixels : 1;
                gl.useProgram(progPacked);
                bindQuad(progPacked);
                gl.uniform1i(gl.getUniformLocation(progPacked, "rgba_tex"), 0);
                gl.uniform2f(gl.getUniformLocation(progPacked, "u_crop"), cropX, 1.0);
                gl.uniform1f(
                    gl.getUniformLocation(progPacked, "u_swap_rb"),
                    currentFormat === 2 ? 1.0 : 0.0,
                );
                gl.activeTexture(gl.TEXTURE0);
                gl.bindTexture(gl.TEXTURE_2D, packedTex);
                gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
            }
        }

        let lastFetchStart = 0;
        function loop() {
            if (!canvas.isConnected) return;
            const now = performance.now();
            if (!fetching && now - lastFetchStart > 28) {
                lastFetchStart = now;
                fetchAndUpload();
            }
            render();
            requestAnimationFrame(loop);
        }
        requestAnimationFrame(loop);
    }

    function scan() {
        document
            .querySelectorAll("canvas[data-stream-id]")
            .forEach(setupCanvas);
    }

    const observer = new MutationObserver(() => scan());
    observer.observe(document.body, { childList: true, subtree: true });
    scan();
})();