我正在尝试使用CUDA内核修改OpenGL纹理,但是遇到一个奇怪的问题,我对surf2Dwrite()
的调用似乎与纹理的先前内容混合在一起,如您在图像中所见下面。背面的木质纹理是使用我的CUDA内核修改之前的纹理。预期的输出将仅包括颜色渐变,而不包括其后面的木材纹理。我不明白为什么会发生这种融合。
我是CUDA和OpenGL的新手。在这里,我将尝试解释导致我编写此代码的思考过程:
cudaArray
来访问纹理(而不是浮点数数组),因为我读到读写纹理时,缓存局部性更好。cudaArray
的唯一方法我的代码可能存在一些我不知道如何检查/测试的问题:
float
而不是unsigned char
?您可以找到一个full minimum working example in this GitHub Gist。由于所有的活动部件,时间很长,但我将尝试进行总结。我欢迎有关如何缩短MWE的建议。总体结构如下:
cudaGraphicsGLRegisterImage()
向CUDA注册纹理cudaGraphicsSubResourceGetMappedArray()
以获得代表纹理的cudaArray
cudaSurfaceObject_t
我可以用来写到cudaArray
surf2Dwrite()
我是OpenGL的新手,所以我以LearnOpenGL tutorials的“纹理”部分为起点。这是我设置纹理的方法(使用图像库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中注册纹理:
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内核如下:
__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);
}
}
答案 0 :(得分:5)
如果我理解正确,则首先注册纹理,将其映射一次,为表示映射纹理的数组创建一个表面对象,然后取消映射纹理。在每一帧中,您都再次映射资源,要求提供代表映射纹理的数组,然后完全忽略该帧,并使用为您第一次映射资源时返回的数组创建的表面对象。 From the documentation:
[…]每次映射
array
时,在resource
中设置的值可能会更改。
每次映射资源时,您都必须创建一个新的表面对象,因为您每次可能会得到一个不同的数组。而且,根据我的经验,实际上您会经常得到不同的人。仅在数组实际更改时才创建一个新的表面对象可能是一件正确的事。该文档似乎允许这样做,但是我从未尝试过,所以我无法确定它是否确实有效……
除此之外:您还可以为纹理生成mipmap。您只覆盖了mip级别0。然后使用带有三线性插值的mipmapping渲染纹理。因此,我的猜测是,您恰好以与mip级别0的分辨率完全不匹配的分辨率渲染纹理,因此,最终将在级别0(您在其中编写)和级别1(其中写入)之间进行插值是从原始纹理生成的)…
答案 1 :(得分:1)