Jump to content

OpenCL: Hello World (lengkap)

From Haidhi Wiki

Core Concepts OpenCL

Platform & Device

  • Platform = driver OpenCL (dari vendor seperti NVIDIA, AMD, Intel)
  • Device = hardware fisik (GPU, CPU, dll)

Context

  • Seperti "workspace" yang manage semua OpenCL objects
  • Menghubungkan host (CPU) dengan device (GPU)

Command Queue

  • Antrian tugas yang akan dieksekusi di device
  • Commands berjalan secara asinkron

Memory Buffers

  • Data harus di-copy dari RAM (host) ke VRAM (device)
  • Tiga jenis: READ_ONLY, WRITE_ONLY, READ_WRITE

Program & Kernel

  • Program = kumpulan kernel code yang sudah di-compile
  • Kernel = fungsi yang dijalankan parallel di device

Execution

  • Kernel dijalankan oleh banyak work-items secara parallel
  • Setiap work-item seperti thread yang memproses 1 elemen

Workflow

1. discover hardware (Platform & Device)

  • Cari platform (driver) dan device (hardware)

2. create context

  • Buat environment untuk manage OpenCL objects

3. command queue

  • Buat antrian untuk submit commands ke device

4. allocate memory

  • Buat buffers di device memory
  • Copy data dari host ke device

5. create & build program

  • Load kernel source code
  • Compile untuk device

6. create kernel & set arguments

  • Ambil kernel function dari program
  • Set parameter yang diperlukan

7. execute kernel

  • Enqueue kernel untuk dijalankan parallel

8. read results

  • Copy hasil dari device memory ke host memory

9. cleanup

  • Release semua resources

Code

#define CL_TARGET_OPENCL_VERSION 300
#include <CL/cl.h>
#include <iostream>
#include <vector>
#include <fstream>
#include <string>
#include <streambuf>

// Fungsi helper untuk membaca source code kernel dari file
std::string readKernelSource(const char* filename) {
    std::ifstream file(filename);
    if (!file.is_open()) {
        return "";
    }
    std::string source((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
    file.close();
    return source;
}


int main() {
    const std::string kernelPath = "/home/haidhi/Documents/learn/opencl/opencl/vector_add.cl"; 

    // ============================================================
    // 1. PLATFORM & DEVICE - Hardware Discovery
    // ============================================================
    // PLATFORM: Representasi dari OpenCL implementation (driver dari vendor seperti NVIDIA, AMD, Intel)
    // Satu sistem bisa punya multiple platform (misal: Intel CPU + NVIDIA GPU)
    cl_platform_id platform; 
    
    // DEVICE: Hardware fisik yang akan menjalankan komputasi (GPU, CPU, FPGA, dll)
    cl_device_id device; 
    
    cl_int err; 

    // Ambil platform pertama yang tersedia
    err = clGetPlatformIDs(1, &platform, NULL); 
    if (err != CL_SUCCESS) {
        std::cerr << "Error getting platform IDs" << std::endl;
        return 1;
    }   

    // Coba cari GPU dulu, kalau gagal pakai CPU
    // CL_DEVICE_TYPE_GPU: Cari device tipe GPU
    // CL_DEVICE_TYPE_CPU: Fallback ke CPU kalau GPU tidak ada
    err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 1, &device, NULL);
    if (err != CL_SUCCESS) {
        err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_CPU, 1, &device, NULL);
        if (err != CL_SUCCESS) {
            std::cerr << "Error getting device IDs" << std::endl;
            return 1;
        }
        std::cout << "Using CPU device" << std::endl;
    } else {
        std::cout << "Using GPU device" << std::endl;
    }   

    // Query informasi device (nama device)
    char deviceName[128];
    clGetDeviceInfo(device, CL_DEVICE_NAME, sizeof(deviceName), deviceName, NULL);
    std::cout << "Device: " << deviceName << std::endl;

    // ============================================================
    // 2. CONTEXT - Execution Environment
    // ============================================================
    // CONTEXT: Environment yang manage semua OpenCL objects (memory, queues, programs)
    // Seperti "workspace" atau "session" untuk operasi OpenCL
    // Context menghubungkan host (CPU) dengan device (GPU/CPU)
    cl_context context = clCreateContext(nullptr, 1, &device, nullptr, nullptr, nullptr);

    // ============================================================
    // 3. COMMAND QUEUE - Task Scheduler
    // ============================================================
    // COMMAND QUEUE: Antrian yang menerima commands untuk dieksekusi di device
    // Commands dieksekusi secara asinkron dan bisa out-of-order (tergantung properties)
    // Seperti "to-do list" untuk device yang akan diproses secara berurutan
    cl_command_queue queue = clCreateCommandQueueWithProperties(
        context,
        device,
        nullptr,  // nullptr = default properties (in-order execution)
        nullptr
    );

    // ============================================================
    // 4. HOST MEMORY - Data di CPU
    // ============================================================
    // Data input/output yang ada di memori CPU (RAM)
    const int ARRAY_SIZE = 1024; 
    std::vector<float> a(ARRAY_SIZE, 2.0f);  // Array a: semua isi 2.0
    std::vector<float> b(ARRAY_SIZE, 3.0f);  // Array b: semua isi 3.0
    std::vector<float> c(ARRAY_SIZE);         // Array c: untuk hasil

    // ============================================================
    // 5. DEVICE MEMORY - Buffer Objects di GPU/Device
    // ============================================================
    // BUFFER: Memory object yang ada di device memory (VRAM untuk GPU)
    // Data harus di-copy dari host memory ke device memory sebelum diproses
    
    // Buffer A: READ_ONLY, copy data dari host ke device saat dibuat
    cl_mem bufA = clCreateBuffer(
        context, 
        CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,  // Flags: read-only + copy data
        sizeof(float) * ARRAY_SIZE,                // Ukuran buffer
        a.data(),                                  // Source data dari host
        nullptr
    );
    
    // Buffer B: READ_ONLY, copy data dari host ke device
    cl_mem bufB = clCreateBuffer(
        context,
        CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
        sizeof(float) * ARRAY_SIZE, 
        b.data(), 
        nullptr
    );
    
    // Buffer C: WRITE_ONLY, hanya untuk output (tidak perlu copy data)
    cl_mem bufC = clCreateBuffer(
        context,
        CL_MEM_WRITE_ONLY,        // Flags: write-only (untuk hasil)
        sizeof(float) * ARRAY_SIZE, 
        nullptr,                   // Tidak ada initial data
        nullptr
    );

    // ============================================================
    // 6. PROGRAM - Compiled Kernel Code
    // ============================================================
    // PROGRAM: Container untuk kernel code yang sudah di-compile
    // Source code (.cl file) harus dibaca, di-compile, dan di-load ke device
    
    // Baca kernel source code dari file
    const std::string kernelSource = readKernelSource(kernelPath.c_str());
    const char* sourceCStr = kernelSource.c_str();
    size_t sourceSize = kernelSource.length();
    cl_int program_err;

    // Create program dari source code
    cl_program program = clCreateProgramWithSource(context, 1, &sourceCStr, &sourceSize, &program_err);
    
    // Compile program untuk device yang dipilih
    // Seperti compile C++ code, tapi untuk GPU/device
    clBuildProgram(program, 1, &device, nullptr, nullptr, nullptr);

    // ============================================================
    // 7. KERNEL - Fungsi yang Dijalankan di Device
    // ============================================================
    // KERNEL: Function yang akan dieksekusi di device secara parallel
    // Satu instance kernel dijalankan untuk setiap work-item (seperti thread)
    
    // Ambil kernel bernama "vector_mul" dari program
    cl_kernel kernel = clCreateKernel(program, "vector_mul", nullptr);
    
    // Set arguments untuk kernel (seperti passing parameter ke function)
    clSetKernelArg(kernel, 0, sizeof(cl_mem), &bufA);  // Arg 0: input buffer A
    clSetKernelArg(kernel, 1, sizeof(cl_mem), &bufB);  // Arg 1: input buffer B
    clSetKernelArg(kernel, 2, sizeof(cl_mem), &bufC);  // Arg 2: output buffer C

    // ============================================================
    // 8. EXECUTION - Jalankan Kernel
    // ============================================================
    // NDRange: N-Dimensional Range - menentukan berapa banyak work-item yang dijalankan
    // Global size: Total jumlah work-items (threads) yang akan dijalankan
    size_t globalSize = ARRAY_SIZE;  // 1024 work-items = 1 work-item per elemen array
    
    // Enqueue kernel untuk dieksekusi
    // 1D execution: 1024 work-items akan dijalankan parallel
    // Setiap work-item memproses 1 elemen array
    clEnqueueNDRangeKernel(queue, kernel, 1, nullptr, &globalSize, nullptr, 0, nullptr, nullptr);

    // ============================================================
    // 9. TRANSFER RESULTS - Copy dari Device ke Host
    // ============================================================
    // Read hasil dari device memory (bufC) ke host memory (c.data())
    // CL_TRUE: blocking read (tunggu sampai selesai)
    clEnqueueReadBuffer(queue, bufC, CL_TRUE, 0, sizeof(float) * ARRAY_SIZE, c.data(), 0, nullptr, nullptr);
    
    // Print hasil
    std::cout << "First 5 results: ";
    for (int i = 0; i < 5; i++) {
        std::cout << c[i] << " ";  // Harusnya: 2.0 * 3.0 = 6.0
    }
    std::cout << std::endl;
    
    // ============================================================
    // 10. CLEANUP - Release semua OpenCL resources
    // ============================================================
    // Penting untuk release memory dan objects untuk hindari memory leak
    clReleaseMemObject(bufA);
    clReleaseMemObject(bufB);
    clReleaseMemObject(bufC);
    clReleaseKernel(kernel);
    clReleaseProgram(program);
    clReleaseCommandQueue(queue);
    clReleaseContext(context);

    return 0;
}