You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 05:44:59