OpenCL: Hello World (lengkap)
Appearance
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;
}