CUDA(LibTorch)与OpenGL互操作异常:部分纹理未被修改
OpenGL纹理与PyTorch张量互转异常问题
问题描述
在C++应用中实现OpenGL纹理与PyTorch张量的互转函数,测试时通过给张量每个元素加128实现图像提亮,再渲染到四边形上,但出现异常:约四分之一的纹理未受操作影响。
原图:
处理后图:
背景信息
- 纹理缓冲区
textureColorbuffer采用GL_RGB格式(每个通道8位深) - 核心调用逻辑:
cudaGraphicsGLRegisterImage(&cudaResource, textureColorbuffer, GL_TEXTURE_2D, cudaGraphicsMapFlagsNone); torch::Tensor tensor = resourceToTensor(cudaResource, WIDTH, HEIGHT); tensor = tensor + static_cast<unsigned char>(128); tensorToResource(tensor, cudaResource, WIDTH, HEIGHT);
- 互转函数代码:
torch::Tensor resourceToTensor(cudaGraphicsResource* cudaResource, int width, int height) { CUDA_CHECK_ERROR(cudaGraphicsMapResources(1, &cudaResource, 0)); cudaArray* textureArray; CUDA_CHECK_ERROR(cudaGraphicsSubResourceGetMappedArray(&textureArray, cudaResource, 0, 0)); unsigned char* devicePtr; size_t size = width * height * 3 * sizeof(unsigned char); CUDA_CHECK_ERROR(cudaMalloc(&devicePtr, size)); CUDA_CHECK_ERROR(cudaMemcpyFromArray(devicePtr, textureArray, 0, 0, size, cudaMemcpyDeviceToDevice)); auto options = torch::TensorOptions().dtype(torch::kUInt8).device(torch::kCUDA); torch::Tensor tensor = torch::from_blob(devicePtr, { height, width, 3 }, options); CUDA_CHECK_ERROR(cudaGraphicsUnmapResources(1, &cudaResource, 0)); torch::Tensor clonedTensor = tensor.clone(); CUDA_CHECK_ERROR(cudaFree(devicePtr)); return clonedTensor; } void tensorToResource(torch::Tensor tensor, cudaGraphicsResource* cudaResource, int width, int height) { tensor = tensor.to(torch::kCUDA); CUDA_CHECK_ERROR(cudaGraphicsMapResources(1, &cudaResource, 0)); cudaArray* textureArray; CUDA_CHECK_ERROR(cudaGraphicsSubResourceGetMappedArray(&textureArray, cudaResource, 0, 0)); const unsigned char* devicePtr = tensor.data_ptr<unsigned char>(); size_t size = width * height * 3 * sizeof(unsigned char); CUDA_CHECK_ERROR(cudaMemcpyToArray(textureArray, 0, 0, devicePtr, size, cudaMemcpyDeviceToDevice)); CUDA_CHECK_ERROR(cudaGraphicsUnmapResources(1, &cudaResource, 0)); }
核心原因
问题出在OpenGL纹理的行字节对齐规则与PyTorch张量的紧密内存布局不兼容:
- OpenGL默认强制纹理每行字节数为4的倍数(
GL_UNPACK_ALIGNMENT/GL_PACK_ALIGNMENT默认值为4)。对于RGB格式(每个像素3字节),如果图像宽度不是4的倍数,每行末尾会被填充空字节凑齐4的倍数。 - 代码中直接用
width*height*3计算总数据量,默认纹理是紧密排列的,但实际cudaArray中的纹理数据带填充字节。一次性复制整个内存块时,会把填充的无效字节也塞进PyTorch张量;反向复制时,张量的紧密数据又会因为缺少填充字节导致后续行数据错位,最终出现部分区域未被正确处理的现象(你看到的四分之一区域异常,大概率是宽度的3倍刚好不是4的倍数,导致每4行就有一行数据偏移)。
修复方案
方案1:逐行复制(推荐,无全局副作用)
通过获取cudaArray的行步长(含对齐填充的每行字节数),逐行复制有效数据,跳过填充字节:
修改resourceToTensor函数
torch::Tensor resourceToTensor(cudaGraphicsResource* cudaResource, int width, int height) { CUDA_CHECK_ERROR(cudaGraphicsMapResources(1, &cudaResource, 0)); cudaArray* textureArray; CUDA_CHECK_ERROR(cudaGraphicsSubResourceGetMappedArray(&textureArray, cudaResource, 0, 0)); // 获取cudaArray的行步长(含对齐填充) cudaChannelFormatDesc desc; size_t arrayPitch; CUDA_CHECK_ERROR(cudaArrayGetInfo(&desc, &arrayPitch, textureArray)); // 创建紧密布局的PyTorch张量 auto options = torch::TensorOptions().dtype(torch::kUInt8).device(torch::kCUDA); torch::Tensor tensor = torch::zeros({ height, width, 3 }, options); unsigned char* tensorPtr = tensor.data_ptr<unsigned char>(); const size_t rowDataBytes = width * 3; // 每行有效数据字节数 // 逐行复制,跳过对齐填充 for (int y = 0; y < height; ++y) { CUDA_CHECK_ERROR(cudaMemcpyFromArray( tensorPtr + y * rowDataBytes, textureArray, 0, y, // 起始位置(x=0, y当前行) rowDataBytes, cudaMemcpyDeviceToDevice )); } CUDA_CHECK_ERROR(cudaGraphicsUnmapResources(1, &cudaResource, 0)); return tensor; }
修改tensorToResource函数
void tensorToResource(torch::Tensor tensor, cudaGraphicsResource* cudaResource, int width, int height) { tensor = tensor.to(torch::kCUDA); CUDA_CHECK_ERROR(cudaGraphicsMapResources(1, &cudaResource, 0)); cudaArray* textureArray; CUDA_CHECK_ERROR(cudaGraphicsSubResourceGetMappedArray(&textureArray, cudaResource, 0, 0)); // 获取cudaArray的行步长 cudaChannelFormatDesc desc; size_t arrayPitch; CUDA_CHECK_ERROR(cudaArrayGetInfo(&desc, &arrayPitch, textureArray)); const unsigned char* tensorPtr = tensor.data_ptr<unsigned char>(); const size_t rowDataBytes = width * 3; // 逐行将张量的紧密数据复制到带对齐的cudaArray中 for (int y = 0; y < height; ++y) { CUDA_CHECK_ERROR(cudaMemcpyToArray( textureArray, 0, y, tensorPtr + y * rowDataBytes, rowDataBytes, cudaMemcpyDeviceToDevice )); } CUDA_CHECK_ERROR(cudaGraphicsUnmapResources(1, &cudaResource, 0)); }
方案2:修改OpenGL对齐规则(简单但影响全局)
如果不想逐行复制,可以在创建纹理前修改OpenGL的像素对齐设置,强制使用紧密布局:
// 在textureColorbuffer创建前调用,全局生效 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glPixelStorei(GL_PACK_ALIGNMENT, 1);
注意此设置会影响所有后续纹理操作,若其他代码依赖默认的4字节对齐,可能引发新问题,需谨慎使用。
内容的提问来源于stack exchange,提问作者asmo_192
相关产品推荐
相关产品推荐

