
mycl.pyimport pyopencl as cl import numpy as np class EasyOpenCL: 简易 PyOpenCL 封装类适配新手使用核心功能全覆盖 def __init__(self, device_typeNone): 自动初始化 OpenCL 平台/设备/上下文/命令队列 :param device_type: 设备类型默认 GPU → cl.device_type.GPU可选 CPU → cl.device_type.CPU # 1. 获取第一个可用平台 self.platform cl.get_platforms()[0] # 2. 自动选择设备优先GPU无GPU则用CPU try: if device_type is None: self.device self.platform.get_devices(cl.device_type.GPU)[0] else: self.device self.platform.get_devices(device_type)[0] except: self.device self.platform.get_devices(cl.device_type.CPU)[0] # 3. 创建上下文 命令队列开启性能分析可选 self.ctx cl.Context([self.device]) self.queue cl.CommandQueue(self.ctx) # 打印设备信息确认初始化成功 print(f✅ OpenCL 初始化完成) print(f 设备名称: {self.device.name}) print(f 设备类型: {cl.device_type.to_string(self.device.type)}) def build_kernel(self, kernel_source: str): 编译 OpenCL 内核代码 :param kernel_source: OpenCL C 语言内核字符串 :return: 编译完成的程序对象 program cl.Program(self.ctx, kernel_source).build() return program def create_buffer_with_data(self, data, flagsNone): ✅ 创建【带主机数据】的缓冲区自动将数据拷贝到GPU :param data: 主机数据列表 / numpy 数组 :param flags: 内存标志默认 READ_ONLY 拷贝主机数据 :return: cl.Buffer 对象, 数据字节大小 # 默认只读缓冲区 拷贝主机数据到GPU if flags is None: flags cl.mem_flags.READ_ONLY | cl.mem_flags.COPY_HOST_PTR # 自动转换为 numpy 数组PyOpenCL 原生兼容numpy if not isinstance(data, np.ndarray): data np.asarray(data) buffer cl.Buffer(self.ctx, flags, hostbufdata) return buffer def create_empty_buffer(self, size: int, dtype, flagsNone): ✅ 创建【GPU空缓冲区】仅分配显存无初始数据 :param size: 元素个数 :param dtype: 数据类型如 np.float32 / np.int32 :param flags: 内存标志默认 WRITE_ONLY :return: cl.Buffer 对象, 缓冲区字节大小 # 默认只写缓冲区用于存储GPU计算结果 if flags is None: flags cl.mem_flags.READ_WRITE # 计算显存大小 dtype np.dtype(dtype) byte_size dtype.itemsize * size buffer cl.Buffer(self.ctx, flags, byte_size) return buffer def read_buffer(self, buffer, shape: tuple, dtype): ✅ 读取GPU缓冲区数据 → 主机内存 :param buffer: GPU缓冲区对象 :param shape: 输出数组形状如 (1024,) / (32,32) :param dtype: 数据类型与GPU端一致 :return: 主机端 numpy 数组 host_array np.empty(shape, dtypedtype) cl.enqueue_copy(self.queue, host_array, buffer) return host_array def create_executor(self,program,func_name,global_size, local_sizeNone): if isinstance(global_size,int): global_size(global_size,) if isinstance(local_size,int): local_size(local_size,) def g(*args): funcgetattr(program,func_name) #print(args) func.set_args(*args) cl.enqueue_nd_range_kernel(self.queue, func, global_size, local_size) self.queue.finish() # 等待内核执行完成 return gopencl代码draw.c/** * 计算列优先 3x3 浮点矩阵的行列式 * param mat 输入矩阵格式为 float[3][3]第一个索引是列第二个是行 * 例如mat[col][row] 表示 列col、行row 的元素 * return 矩阵的行列式值float类型 */ float calc_det(__private float mat[3][3]) { float c0r0 mat[0][0], c0r1 mat[0][1], c0r2 mat[0][2]; float c1r0 mat[1][0], c1r1 mat[1][1], c1r2 mat[1][2]; float c2r0 mat[2][0], c2r1 mat[2][1], c2r2 mat[2][2]; float term1 c0r0 * (c1r1 * c2r2 - c1r2 * c2r1); float term2 c1r0 * (c0r1 * c2r2 - c0r2 * c2r1); float term3 c2r0 * (c0r1 * c1r2 - c0r2 * c1r1); return term1 - term2 term3; } int jie(__private float mat[3][3],__private float eq[3],__private float *res){ float detcalc_det(mat); if(det0.0f){ return 0; } // 显式声明栈上数组为__private可省略但更规范 __private float mats[3][3][3]; for(int i0;i3;i){ for(int j0;j3;j){ if(ij){ for(int k0;k3;k){ mats[i][j][k]eq[k]; } }else{ for(int k0;k3;k){ mats[i][j][k]mat[j][k]; } } } } for(int i0;i3;i){ float dcalc_det(mats[i]); res[i]d/det; // 注释printfOpenCL内核中printf需CL1.2支持且会降低性能 // printf(%f\n,res[i]); } return 1; } // 关键修复v0/v1/v2改为__global匹配传入的全局内存指针 int calc_mg(__global float v0[3],__global float v1[3],__global float v2[3], __private float xy[2],float z,__private float *res){ __private float dv1[3]; __private float dv2[3]; for(int i0;i3;i){ dv1[i]v1[i]-v0[i]; } for(int i0;i3;i){ dv2[i]v2[i]-v0[i]; } __private float mat[3][3]; for(int i0;i3;i){ mat[0][i]dv1[i]; } for(int i0;i3;i){ mat[1][i]dv2[i]; } mat[2][0]0.0f; mat[2][1]0.0f; mat[2][2]z; __private float eq[3]{ xy[0]-v0[0],xy[1]-v0[1],z-v0[2] }; int rjie(mat,eq,res); if(!r){ return 0; } if(res[0]0.0f||res[1]0.0f||res[0]res[1]1.0f||res[2]0.0f){ return 0; } return 1; } // 关键修复内核函数用__kernel修饰而非__global这是OpenCL标准要求 // 宽 高 三角形数 顶点/颜色数据 像素缓冲区 深度缓冲区 __kernel void draw(const int width,const int height,const int len, __global float *sj,__global float *pixels,__global float *deeps){ int gidget_global_id(0); int xgid%width; int ygid/width; float fx(float)x/(float)width-0.5f; float fy(float)y/(float)height-0.5f; __private float xy[2]{fx,fy}; float z-1.0f; int indexgid*3; for(int i0;ilen;i){ // 关键修复offset从gid*12改为i*18遍历所有三角形而非仅gid对应项 int offseti*18; __global float*v0sj[offset]; __global float*v1sj[offset3]; __global float*v2sj[offset6]; __global float*colorsj[offset9]; __private float res[3]; if(calc_mg(v0,v1,v2,xy,z,res)){ float deepdeeps[gid]; if(deep!0.0f(deepres[2]||res[2]0.0f)){ continue; } deeps[gid]res[2]; float v1res[0]; float v2res[1]; //这里要通过v1 v2和 9个color float插值计算颜色 pixels[index]v1*color[0]v2*color[3]color[6]; pixels[index1]v1*color[1]v2*color[4]color[7]; pixels[index2]v1*color[2]v2*color[5]color[8]; } } }主程序a.pyimport tkinter as tk from PIL import Image,ImageTk from mycl import EasyOpenCL import numpy as np import math def ndarray_to_image( arr: np.ndarray, width: int, height: int, mode: str RGB ) - Image.Image: 将 numpy NDArray 转换为 PIL Image 对象 :param arr: 输入数组 (支持一维展平/二维/三维) 数据类型float32(0~1) / uint8(0~255) 均可 :param width: 图像宽度 (像素) :param height: 图像高度 (像素) :param mode: 图像模式支持 L(灰度)、RGB、RGBA :return: PIL Image 对象 :raises ValueError: 模式不支持/数组尺寸不匹配/数据非法 # 1. 定义模式与通道数映射 mode_channels { L: 1, # 灰度图1通道 RGB: 3, # 彩色图3通道 RGBA: 4 # 带透明通道4通道 } if mode not in mode_channels: raise ValueError(f仅支持模式{list(mode_channels.keys())}) channels mode_channels[mode] total_pixels width * height total_elements total_pixels * channels # 2. 输入数组尺寸校验 if arr.size ! total_elements: raise ValueError( f数组元素数量不匹配\n f要求宽{width}×高{height}×{channels}通道 {total_elements} 个元素\n f输入数组大小 {arr.size} ) # 3. 数据类型转换float → uint8 # 复制数组避免修改原数据 img_arr arr.copy() # 浮点型数据归一化到 0~255 if np.issubdtype(img_arr.dtype, np.floating): img_arr np.clip(img_arr, 0.0, 1.0) # 截断非法值 img_arr (img_arr * 255).astype(np.uint8) # 整型数据直接转uint8 elif np.issubdtype(img_arr.dtype, np.integer): img_arr np.clip(img_arr, 0, 255).astype(np.uint8) else: raise TypeError(仅支持浮点型(float32/float64)或整型数组) # 4. 重塑数组形状适配PIL格式 # PIL要求形状(高度, 宽度, 通道数) 或 (高度, 宽度) if channels 1: # 灰度图(H, W) img_arr img_arr.reshape(height, width) else: # RGB/RGBA(H, W, C) img_arr img_arr.reshape(height, width, channels) # 5. 生成图像 return Image.fromarray(img_arr, modemode) #围绕ab旋转x y def rotate(a,b,x,y,angle): cmath.cos(angle) smath.sin(angle) dxx-a dyy-b dx2dx*c-dy*s dy2dy*cdx*s return dx2a,dy2b # 1. 初始化OpenCL ocl EasyOpenCL() # 2. 图像尺寸 H, W 800,800 N H * W fopen(draw.c,r,encodingutf-8) kernelf.read() f.close() # 4. 编译运行GPU内核 program ocl.build_kernel(kernel) focl.create_executor(program,draw,N) sjs [ -0.3,0.3,2, 0.3,0.3,2, 0,-0.3,2, 1,0,0, 0,1,0, 0,0,1, -0.3,0.3,2, 0,0.3,1.5, 0,-0.3,2, 1,0,0, 1,1,1, 0,0,1, 0.3,0.3,2, 0,0.3,1.5, 0,-0.3,2, 0,1,0, 1,1,1, 0,0,1, ] t0 def draw(): global t t1 sjs2[] k0 nlen(sjs) while kn: for i in range(3): x,y,zsjs[k:k3] x,zrotate(0,2,x,z,(t*math.pi/12)%(math.pi*2)) sjs2.append(x) sjs2.append(y) sjs2.append(z) k3 for i in range(9): #print(k) sjs2.append(sjs[k]) k1 pixels_bufocl.create_buffer_with_data(np.zeros((N*3,),dtypenp.float32)) deeps_bufocl.create_buffer_with_data(np.zeros((N*3,),dtypenp.float32)) lengthlen(sjs)//4 sjs_bufocl.create_buffer_with_data(np.array(sjs2,dtypenp.float32)) f(np.int32(W),np.int32(H),np.int32(length),sjs_buf,pixels_buf,deeps_buf) # 5. 读取GPU结果 → 转为图像形状 img_data ocl.read_buffer(pixels_buf, N*3, np.float32) #print(img_data) img ndarray_to_image(img_data, W, H) tk_img ImageTk.PhotoImage(img) canvas.create_image(0, 0, anchortk.NW, imagetk_img) canvas.img tk_img # 保存引用 dataocl.read_buffer(deeps_buf, N, np.float32) #print(data) root.after(50,draw) # 6. Tkinter窗口渲染图像 root tk.Tk() root.title(Tkinter窗口 PyOpenCl渲染三角形) canvas tk.Canvas(root, widthW, heightH) canvas.pack() draw() root.mainloop()