WEBGPU REAL-TIME VIDEO-PROCESSING GRAPHICS FRONTEND GPU-COMPUTING WEB-API CREATIVE-CODING PERFORMANCE-OPTIMIZATION SHADER WGSL MULTIMEDIA

Membangun Efek Video Real-time di Browser dengan WebGPU: Menggali Potensi GPU untuk Kreativitas Visual

⏱️ 18 menit baca
👨‍💻

Membangun Efek Video Real-time di Browser dengan WebGPU: Menggali Potensi GPU untuk Kreativitas Visual

Pernahkah Anda membayangkan bisa menerapkan filter Instagram atau efek video TikTok langsung di browser, secara real-time, dengan performa yang luar biasa? Dulu, ini mungkin terdengar seperti fiksi ilmiah atau membutuhkan keahlian tingkat dewa WebGL. Tapi kini, dengan hadirnya WebGPU, potensi untuk manipulasi video real-time di web telah mencapai level baru!

Artikel ini akan membawa Anda menyelami dunia WebGPU untuk menciptakan efek video yang dinamis dan interaktif. Kita akan belajar bagaimana memanfaatkan kekuatan GPU (Graphics Processing Unit) langsung dari browser Anda untuk mengubah setiap frame video secara instan.

1. Pendahuluan: Kenapa WebGPU adalah Game-Changer untuk Video Real-time?

Manipulasi video real-time di web selalu menjadi tantangan. WebGL, pendahulu WebGPU, memang powerful, tetapi memiliki kurva belajar yang curam dan arsitektur yang seringkali terasa kuno. Ketika kita berbicara tentang efek video, yang kita lakukan sebenarnya adalah memproses jutaan piksel per detik. Ini adalah tugas yang sangat cocok untuk GPU, yang dirancang untuk komputasi paralel masif.

WebGPU hadir sebagai API grafis modern yang didesain untuk memanfaatkan kemampuan GPU secara lebih efisien dan ergonomis. Ia menawarkan akses yang lebih “dekat ke hardware” dibandingkan WebGL, memungkinkan kontrol yang lebih baik atas pipeline rendering dan komputasi.

📌 Mengapa WebGPU unggul untuk efek video real-time?

Bayangkan Anda ingin membuat filter “vintage” yang mengubah warna, menambahkan noise, dan mengurangi saturasi video dari webcam pengguna. Dengan WebGPU, Anda bisa melakukan semua itu dalam satu pass di GPU, menjaga frame rate tetap tinggi dan pengalaman pengguna tetap mulus.

2. Memulai dengan Video Stream: Sumber Data Kita

Langkah pertama untuk memproses video adalah mendapatkan video itu sendiri. Dalam konteks browser, ini biasanya berarti menggunakan webcam pengguna atau memuat file video lokal.

async function getVideoFeed() {
    try {
        const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false });
        const videoElement = document.createElement('video');
        videoElement.srcObject = stream;
        videoElement.play();
        videoElement.width = 640; // Atur ukuran sesuai kebutuhan
        videoElement.height = 480;
        document.body.appendChild(videoElement); // Atau tambahkan ke elemen lain
        return videoElement;
    } catch (error) {
        console.error("Gagal mengakses webcam:", error);
        alert("Mohon izinkan akses webcam untuk melanjutkan.");
        return null;
    }
}

Fungsi getVideoFeed ini akan meminta akses ke webcam dan mengembalikan elemen <video> yang sedang memutar feed dari kamera. Elemen video ini akan menjadi sumber frame kita yang akan diproses oleh WebGPU.

3. Konsep WebGPU untuk Pemrosesan Frame Video

Untuk memproses setiap frame video, kita akan mengikuti alur dasar WebGPU:

  1. Mendapatkan Adapter & Device: Inisialisasi WebGPU.
  2. Membuat Canvas: Tempat hasil rendering akan ditampilkan.
  3. Membuat Tekstur (Texture): Setiap frame video akan diubah menjadi tekstur GPU. Tekstur adalah representasi data gambar di GPU.
  4. Menulis Shader (WGSL): Kode yang berjalan di GPU untuk memanipulasi piksel.
  5. Membuat Render Pipeline: Mengatur bagaimana shader akan dieksekusi dan bagaimana tekstur input/output akan digunakan.
  6. Membaca Frame, Menggambar, dan Mengulang: Loop utama untuk memproses setiap frame.

💡 Analogi: Anggaplah GPU Anda adalah pabrik percetakan super cepat.

4. Mengubah Frame Video Menjadi Tekstur WebGPU

Setiap kali frame baru tersedia dari elemen <video>, kita perlu mengunggahnya ke GPU sebagai tekstur. WebGPU memiliki metode copyExternalImageToTexture yang sangat efisien untuk tujuan ini.

// ... (setelah inisialisasi adapter dan device WebGPU)

async function setupWebGPU(videoElement) {
    const canvas = document.createElement('canvas');
    canvas.width = videoElement.width;
    canvas.height = videoElement.height;
    document.body.appendChild(canvas); // Tambahkan canvas ke DOM

    const adapter = await navigator.gpu.requestAdapter();
    const device = await adapter.requestDevice();
    const context = canvas.getContext('webgpu');
    const presentationFormat = navigator.gpu.getPreferredCanvasFormat();

    context.configure({
        device,
        format: presentationFormat,
        alphaMode: 'opaque',
    });

    // Tekstur untuk input video
    const videoTexture = device.createTexture({
        size: [videoElement.videoWidth, videoElement.videoHeight],
        format: 'rgba8unorm', // Format piksel
        usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
    });

    // Tekstur output (jika perlu, untuk efek berantai atau offscreen rendering)
    // Untuk contoh sederhana, kita langsung render ke canvas swap chain.

    return { device, context, presentationFormat, videoTexture, canvas };
}

5. Membuat Efek Grayscale Sederhana dengan WGSL Shader

Sekarang, mari kita buat shader pertama kita. Kita akan menggunakan WGSL (WebGPU Shading Language) untuk mengubah setiap piksel menjadi grayscale.

// vertex.wgsl - Shader untuk menentukan posisi dan koordinat tekstur
// Ini adalah shader standar untuk menggambar fullscreen quad
@vertex
fn vs_main(@builtin(vertex_index) vertexIndex : u32) -> @builtin(position) vec4<f32> {
    var pos = array<vec2<f32>, 6>(
        vec2<f32>(-1.0, -1.0), // bottom-left
        vec2<f32>( 1.0, -1.0), // bottom-right
        vec2<f32>(-1.0,  1.0), // top-left
        vec2<f32>(-1.0,  1.0), // top-left
        vec2<f32>( 1.0, -1.0), // bottom-right
        vec2<f32>( 1.0,  1.0)  // top-right
    );
    return vec4<f32>(pos[vertexIndex], 0.0, 1.0);
}

// fragment.wgsl - Shader untuk memanipulasi warna piksel
@group(0) @binding(0) var videoSampler: sampler;
@group(0) @binding(1) var videoTexture: texture_2d<f32>;

@fragment
fn fs_main(@builtin(position) fragCoord : vec4<f32>) -> @location(0) vec4<f32> {
    // Menghitung koordinat tekstur (normalisasi dari 0 ke 1)
    let uv = fragCoord.xy / vec2<f32>(textureDimensions(videoTexture));

    // Mengambil warna piksel dari tekstur input
    let color = textureSample(videoTexture, videoSampler, uv);

    // Mengubah ke grayscale: rata-rata komponen RGB
    let gray = dot(color.rgb, vec3<f32>(0.299, 0.587, 0.114));

    // Mengembalikan warna grayscale dengan alpha asli
    return vec4<f32>(gray, gray, gray, color.a);
}

Kode WGSL ini memiliki dua bagian:

6. Menggabungkan Semuanya: Render Loop

Sekarang, kita akan menyatukan semua bagian dalam loop rendering utama.

async function main() {
    const videoElement = await getVideoFeed();
    if (!videoElement) return;

    const { device, context, presentationFormat, videoTexture, canvas } = await setupWebGPU(videoElement);

    // Compile shaders
    const vertexShaderModule = device.createShaderModule({
        code: `
            @vertex
            fn vs_main(@builtin(vertex_index) vertexIndex : u32) -> @builtin(position) vec4<f32> {
                var pos = array<vec2<f32>, 6>(
                    vec2<f32>(-1.0, -1.0), // bottom-left
                    vec2<f32>( 1.0, -1.0), // bottom-right
                    vec2<f32>(-1.0,  1.0), // top-left
                    vec2<f32>(-1.0,  1.0), // top-left
                    vec2<f32>( 1.0, -1.0), // bottom-right
                    vec2<f32>( 1.0,  1.0)  // top-right
                );
                return vec4<f32>(pos[vertexIndex], 0.0, 1.0);
            }
        `,
    });

    const fragmentShaderModule = device.createShaderModule({
        code: `
            @group(0) @binding(0) var videoSampler: sampler;
            @group(0) @binding(1) var videoTexture: texture_2d<f32>;

            @fragment
            fn fs_main(@builtin(position) fragCoord : vec4<f32>) -> @location(0) vec4<f32> {
                let uv = fragCoord.xy / vec2<f32>(textureDimensions(videoTexture));
                let color = textureSample(videoTexture, videoSampler, uv);
                let gray = dot(color.rgb, vec3<f32>(0.299, 0.587, 0.114));
                return vec4<f32>(gray, gray, gray, color.a);
            }
        `,
    });

    // Buat sampler untuk tekstur video
    const sampler = device.createSampler({
        magFilter: 'linear', // Interpolasi halus
        minFilter: 'linear',
    });

    // Buat Bind Group Layout dan Render Pipeline
    const bindGroupLayout = device.createBindGroupLayout({
        entries: [
            {
                binding: 0,
                visibility: GPUShaderStage.FRAGMENT,
                sampler: {},
            },
            {
                binding: 1,
                visibility: GPUShaderStage.FRAGMENT,
                texture: {},
            },
        ],
    });

    const pipelineLayout = device.createPipelineLayout({
        bindGroupLayouts: [bindGroupLayout],
    });

    const renderPipeline = device.createRenderPipeline({
        layout: pipelineLayout,
        vertex: {
            module: vertexShaderModule,
            entryPoint: 'vs_main',
        },
        fragment: {
            module: fragmentShaderModule,
            entryPoint: 'fs_main',
            targets: [{ format: presentationFormat }],
        },
        primitive: {
            topology: 'triangle-list', // Menggambar 2 segitiga untuk fullscreen quad
        },
    });

    // Loop rendering utama
    function renderLoop() {
        // Hanya proses jika video sudah siap dan ada frame baru
        if (videoElement.readyState >= videoElement.HAVE_CURRENT_DATA) {
            // Copy frame video ke tekstur GPU
            device.queue.copyExternalImageToTexture(
                { source: videoElement },
                { texture: videoTexture },
                [videoElement.videoWidth, videoElement.videoHeight]
            );

            // Buat Bind Group dengan sampler dan tekstur video
            const bindGroup = device.createBindGroup({
                layout: bindGroupLayout,
                entries: [
                    {
                        binding: 0,
                        resource: sampler,
                    },
                    {
                        binding: 1,
                        resource: videoTexture.createView(),
                    },
                ],
            });

            const commandEncoder = device.createCommandEncoder();
            const textureView = context.getCurrentTexture().createView();

            const renderPassDescriptor = {
                colorAttachments: [
                    {
                        view: textureView,
                        clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
                        loadOp: 'clear',
                        storeOp: 'store',
                    },
                ],
            };

            const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
            passEncoder.setPipeline(renderPipeline);
            passEncoder.setBindGroup(0, bindGroup);
            passEncoder.draw(6); // Gambar 6 vertex untuk fullscreen quad
            passEncoder.end();
            device.queue.submit([commandEncoder.finish()]);
        }

        requestAnimationFrame(renderLoop);
    }

    renderLoop();
}

main();

✅ Jalankan Kode: Untuk menjalankan kode ini, Anda perlu menyimpan kode WGSL di dalam string atau memuatnya dari file terpisah. Pastikan Anda memiliki browser yang mendukung WebGPU (Chrome Canary, Edge Canary dengan flag enable-webgpu di chrome://flags).

⚠️ Penting: Performa WebGPU sangat bergantung pada GPU dan driver perangkat Anda. Pastikan browser dan sistem operasi Anda up-to-date.

7. Pola Desain untuk Efek Kompleks: Chaining dan Uniforms

Efek grayscale hanyalah permulaan. Anda bisa membuat efek yang jauh lebih kompleks dengan:

🎯 Contoh Efek Lanjutan:

Kesimpulan

WebGPU membuka babak baru untuk kreativitas dan performa di dunia web, terutama dalam pemrosesan media real-time. Dengan kemampuan untuk mengakses kekuatan GPU secara langsung dan menulis shader yang efisien, kita kini bisa menciptakan efek video yang sebelumnya hanya mungkin di aplikasi desktop native.

Memang, WebGPU memiliki kurva belajar. Namun, dengan memahami konsep dasar seperti tekstur, shader WGSL, dan pipeline rendering, Anda sudah memiliki fondasi yang kuat untuk mulai bereksperimen. Potensinya tidak terbatas: dari filter video yang personal, game interaktif, hingga aplikasi augmented reality berbasis browser.

Jadi, jangan ragu untuk menyelami API baru ini. Dunia kreatif visual di web menanti eksplorasi Anda!

🔗 Baca Juga