簡體   English   中英

CUDA / OpenGL互操作性:寫入表面對象不會擦除先前的內容

[英]CUDA/OpenGL Interop: Writing to surface object does not erase previous contents

我試圖使用CUDA內核修改OpenGL紋理,但是遇到一個奇怪的問題,我對surf2Dwrite()調用似乎與紋理的先前內容混合在一起,如下圖所示。 背面的木質紋理是使用我的CUDA內核修改之前的紋理。 預期的輸出將僅包括顏色漸變,而不包括其后面的木材紋理。 我不明白為什么會這樣混合。

怪異的紋理融合

可能的問題/誤解

我是CUDA和OpenGL的新手。 在這里,我將解釋導致我編寫此代碼的思考過程:

  • 我使用cudaArray來訪問紋理(而不是例如浮點數數組),因為我讀到,讀取/寫入紋理時,緩存局部性更好。
  • 我使用曲面是因為我在某處讀到這是修改cudaArray的唯一方法
  • 我想使用表面對象,我知道這是更新的工作方式。 舊的方法是使用表面參考。

我不知道如何檢查/測試的代碼可能存在的一些問題:

  • 我與圖像格式不一致嗎? 也許我沒有在某處指定正確的位數/通道數? 也許我應該使用float而不是unsigned char

代碼摘要

您可以在此GitHub Gist中找到完整的最低限度的工作示例 由於所有的活動部件,時間很長,但我將嘗試進行總結。 我歡迎有關如何縮短MWE的建議。 總體結構如下:

  1. 從本地存儲的文件創建OpenGL紋理
  2. 使用cudaGraphicsGLRegisterImage()向CUDA注冊紋理
  3. 調用cudaGraphicsSubResourceGetMappedArray()以獲取表示紋理的cudaArray
  4. 創建一個cudaSurfaceObject_t ,我可以用它來寫cudaArray
  5. 將表面對象傳遞給內核,該內核使用surf2Dwrite()寫入紋理
  6. 使用紋理在屏幕上繪制矩形

OpenGL紋理創建

我是OpenGL的新手,所以我將LearnOpenGL教程的“紋理”部分作為起點。 這是我設置紋理的方法(使用圖像庫stb_image.h

GLuint initTexturesGL(){
    // load texture from file
    int numChannels;
    unsigned char *data = stbi_load("img/container.jpg", &g_imageWidth, &g_imageHeight, &numChannels, 4);
    if(!data){
        std::cerr << "Error:  Failed to load texture image!" << std::endl;
        exit(1);
    }

    // opengl texture
    GLuint textureId;
    glGenTextures(1, &textureId);
    glBindTexture(GL_TEXTURE_2D, textureId);

    // wrapping
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);

    // filtering
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    // set texture image
    glTexImage2D(
        GL_TEXTURE_2D,    // target
        0,                // mipmap level
        GL_RGBA8,         // internal format (#channels, #bits/channel, ...)
        g_imageWidth,     // width
        g_imageHeight,    // height
        0,                // border (must be zero)
        GL_RGBA,          // format of input image
        GL_UNSIGNED_BYTE, // type
        data              // data
    );
    glGenerateMipmap(GL_TEXTURE_2D);

    // unbind and free image
    glBindTexture(GL_TEXTURE_2D, 0);
    stbi_image_free(data);

    return textureId;
}

CUDA圖形互操作

調用上面的函數后,我向CUDA注冊了紋理:

void initTexturesCuda(GLuint textureId){
    // register texture
    HANDLE(cudaGraphicsGLRegisterImage(
        &g_textureResource,                       // resource
        textureId,                                // image
        GL_TEXTURE_2D,                            // target
        cudaGraphicsRegisterFlagsSurfaceLoadStore // flags
    ));

    // resource description for surface
    memset(&g_resourceDesc, 0, sizeof(g_resourceDesc));
    g_resourceDesc.resType = cudaResourceTypeArray;
}

渲染循環

在每一幀中,我運行以下命令來修改紋理並渲染圖像:

while(!glfwWindowShouldClose(window)){
        // -- CUDA --

        // map
        HANDLE(cudaGraphicsMapResources(1, &g_textureResource));


        HANDLE(cudaGraphicsSubResourceGetMappedArray(
            &g_textureArray,   // array through which to access subresource
            g_textureResource, // mapped resource to access
            0,                 // array index
            0                  // mipLevel
        ));

        // create surface object (compute >= 3.0)
        g_resourceDesc.res.array.array = g_textureArray;
        HANDLE(cudaCreateSurfaceObject(&g_surfaceObj, &g_resourceDesc));

        // run kernel
        kernel<<<gridDim, blockDim>>>(g_surfaceObj, g_imageWidth, g_imageHeight);

        // unmap
        HANDLE(cudaGraphicsUnmapResources(1, &g_textureResource));

        // --- OpenGL ---

        // clear
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // use program
        shader.use();

        // triangle
        glBindVertexArray(vao);
        glBindTexture(GL_TEXTURE_2D, textureId);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
        glBindVertexArray(0);

        // glfw:  swap buffers and poll i/o events
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

CUDA內核

實際的CUDA內核如下:

__global__ void kernel(cudaSurfaceObject_t surface, int nx, int ny){
    int x = blockIdx.x * blockDim.x + threadIdx.x;
    int y = blockIdx.y * blockDim.y + threadIdx.y;
    if(x < nx && y < ny){
        uchar4 data = make_uchar4(x % 255, 
                                  y % 255, 
                                  0, 255);
        surf2Dwrite(data, surface, x * sizeof(uchar4), y);
    }
}

如果我理解正確,則首先注冊紋理,將其映射一次,為表示映射紋理的數組創建一個表面對象,然后取消映射紋理。 在每一幀中,您都再次映射資源,要求提供代表映射紋理的數組,然后完全忽略該幀,並使用為您第一次映射資源時返回的數組創建的表面對象。 從文檔中

[…]每次映射resource時, array設置的值可能會更改。

每次映射資源時,您都必須創建一個新的表面對象,因為您每次可能會得到一個不同的數組。 而且,根據我的經驗,實際上您會經常得到不同的人。 僅在數組實際更改時才創建一個新的表面對象可能是一件正確的事。 該文檔似乎允許這樣做,但是我從未嘗試過,所以我無法確定這是否肯定有效……

除此之外:您可以為紋理生成mipmap。 您只覆蓋了mip級別0。然后使用帶有三線性插值的mipmapping渲染紋理。 因此,我的猜測是,您恰好以與mip級別0的分辨率完全不匹配的分辨率渲染紋理,因此,最終將在級別0(您在其中編寫)和級別1(即其中寫入)之間進行插值是從原始紋理生成的)…

原來,問題是,我曾誤產生的原始木質紋理的貼圖 ,和我的CUDA內核只改變0級紋理貼圖。 我注意到的混合是OpenGL在我修改的0級Mipmap和較低分辨率的木材紋理之間進行插值的結果。

這是通過禁用mipmap插值獲得的正確輸出。 學過的知識!

正確的輸出,沒有mipmapping

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM