我会尽量使这个问题简明扼要,但不要犹豫要求澄清。
我在处理遗留代码,我试图从磁盘加载数千个8位图像,为每个图像创建一个纹理。
我尝试了很多方法,我正试图将8位图像加载到32位曲面中,然后从该曲面创建纹理。
问题是:当加载8位图像到32位曲面上时,当我尝试SDL_CreateTextureFromSurface
时,我得到了很多完全空白的纹理(充满透明像素,0x00000000)。
不是所有的纹理都错了,思想。每次我运行程序,我得到不同的“坏”纹理。有时候更多,有时候更少。当我追踪程序的时候,我总是得到一个正确的纹理(这是时间问题吗?)
我知道加载到SDL_Surface
是有效的,因为我把所有的表面都保存到磁盘上,而且它们都是正确的。但我使用nvidia nsight图形检查了纹理,其中一半以上是空白的。
下面是违规代码:
int __cdecl IMG_SavePNG(SDL_Surface*, const char*);
SDL_Texture* Resource8bitToTexture32(SDL_Renderer* renderer, SDL_Color* palette, int paletteSize, void* dataAddress, int Width, int Height)
{
u32 uiCurrentOffset;
u32 uiSourceLinearSize = (Width * Height);
SDL_Color *currentColor;
char strSurfacePath[500];
// The texture we're creating
SDL_Texture* newTexture = NULL;
// Load image at specified address
SDL_Surface* tempSurface = SDL_CreateRGBSurface(0x00, Width, Height, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
SDL_SetSurfaceBlendMode(tempSurface, SDL_BLENDMODE_NONE);
if(SDL_MUSTLOCK(tempSurface)
SDL_LockSurface(tempSurface);
for(uiCurrentOffset = 0; uiCurrentOffset < uiSourceLinearSize; uiCurrentOffset++)
{
currentColor = &palette[pSourceData[uiCurrentOffset]];
if(pSourceData[uiCurrentOffset] != PC_COLOR_TRANSPARENT)
{
((u32*)tempSurface->pixels)[uiCurrentOffset] = (u32)((currentColor->a << 24) + (currentColor->r << 16) + (currentColor->g << 8) + (currentColor->b << 0));
}
}
if(SDL_MUSTLOCK(tempSurface)
SDL_UnlockSurface(tempSurface);
// Create texture from surface pixels
newTexture = SDL_CreateTextureFromSurface(renderer, tempSurface);
// Save the surface to disk for verification only
sprintf(strSurfacePath, "c:\\tmp\\surfaces\\%s.png", GenerateUniqueName());
IMG_SavePNG(tempSurface, strSurfacePath);
// Get rid of old loaded surface
SDL_FreeSurface(tempSurface);
return newTexture;
}
注意,在原始代码中,我正在检查边界,以及
SDL_Create*
之后的空值。我也知道,最好有一个纹理的精灵表,而不是单独加载每个纹理。编辑:
下面是我在nsight中观察到的一个例子,如果我捕获一个帧并使用resources视图。
前3186个纹理是正确的。然后我得到43个空纹理。然后我得到228个正确的纹理。然后是100个坏的。然后是539个正确的。然后是665个坏的。它像那样随机进行,每次我运行我的程序时它都会改变。
同样,每次
IMG_SavePNG
保存的曲面都是正确的。这似乎表明当我调用SDL_CreateTextureFromSurface
时会发生一些事情,但在这一点上,我不想排除任何可能,因为这是一个非常奇怪的问题,而且到处都是未定义的行为。但我就是找不到问题。 最佳答案
在马克·本宁菲尔德的帮助下,我找到了问题所在。
TL;博士
在使用dx11渲染器的sdl中有一个bug(或者至少是一个未记录的特性)。有一项工作要做;看最后。
上下文
当我的程序启动时,我试图加载大约12000个纹理。我知道这不是个好主意,但我正计划用它作为另一个更理智的系统的垫脚石。
细节
在调试该问题时,我意识到directx 11的sdl渲染器在创建纹理时会这样做:
result = ID3D11Device_CreateTexture2D(rendererData->d3dDevice,
&textureDesc,
NULL,
&textureData->mainTexture
);
微软的ID3D11Device::CreateTexture2D method页面表明:
如果不向pininitialdata传递任何内容,则资源的内存初始内容是未定义的。在这种情况下,您需要在读取资源之前以其他方式写入资源内容。
如果我们相信:
默认用法
最常见的用法类型是默认用法。要填充默认纹理(使用d3d11_usage_default创建的纹理),可以:
[…]
调用id3d11device::createTexture2d后,使用id3d11deviceContext::updateSubResource用应用程序提供的指针中的数据填充默认纹理。
所以看起来
D3D11_CreateTexture
使用默认用法的第二种方法来初始化纹理及其内容。但在那之后,在sdl中,我们调用
SDL_UpdateTexture
(不检查返回值;我稍后再讨论)。如果我们挖到d3d11渲染器,我们会得到:static int
D3D11_UpdateTextureInternal(D3D11_RenderData *rendererData, ID3D11Texture2D *texture, int bpp, int x, int y, int w, int h, const void *pixels, int pitch)
{
ID3D11Texture2D *stagingTexture;
[...]
/* Create a 'staging' texture, which will be used to write to a portion of the main texture. */
ID3D11Texture2D_GetDesc(texture, &stagingTextureDesc);
[...]
result = ID3D11Device_CreateTexture2D(rendererData->d3dDevice, &stagingTextureDesc, NULL, &stagingTexture);
[...]
/* Get a write-only pointer to data in the staging texture: */
result = ID3D11DeviceContext_Map(rendererData->d3dContext, (ID3D11Resource *)stagingTexture, 0, D3D11_MAP_WRITE, 0, &textureMemory);
[...]
/* Commit the pixel buffer's changes back to the staging texture: */
ID3D11DeviceContext_Unmap(rendererData->d3dContext, (ID3D11Resource *)stagingTexture, 0);
/* Copy the staging texture's contents back to the texture: */
ID3D11DeviceContext_CopySubresourceRegion(rendererData->d3dContext, (ID3D11Resource *)texture, 0, x, y, 0, (ID3D11Resource *)stagingTexture, 0, NULL);
SAFE_RELEASE(stagingTexture);
return 0;
}
注意:为了简洁起见,代码被剪掉了。
这似乎表明,基于that article,sdl使用默认用法的第二种方法来分配gpu上的纹理内存,但使用分段用法来上载实际像素。
我不太了解DX11编程,但是混合技术让程序员感到刺痛。
我联系了一个我认识的游戏程序员并向他解释了这个问题。他告诉我以下有趣的事:
驱动程序可以决定在哪里存储分段纹理。它通常位于CPU RAM中。
最好指定一个PinitialData指针,因为驱动程序可以决定异步上载纹理。
如果加载太多的分段纹理而不将它们提交给GPU,则可以填充RAM。
然后我想知道为什么SDL在我调用
SDL_CreateTextureFromSurface
时没有返回一个“内存不足”错误,我发现了原因(同样,为了简洁起见,截取了):SDL_Texture *
SDL_CreateTextureFromSurface(SDL_Renderer * renderer, SDL_Surface * surface)
{
[...]
SDL_Texture *texture;
[...]
texture = SDL_CreateTexture(renderer, format, SDL_TEXTUREACCESS_STATIC,
surface->w, surface->h);
if (!texture) {
return NULL;
}
[...]
if (SDL_MUSTLOCK(surface)) {
SDL_LockSurface(surface);
SDL_UpdateTexture(texture, NULL, surface->pixels, surface->pitch);
SDL_UnlockSurface(surface);
} else {
SDL_UpdateTexture(texture, NULL, surface->pixels, surface->pitch);
}
[...]
return texture;
}
如果纹理的创建成功,则它不关心更新纹理是否成功(不检查
SDL_UpdateTexture
的返回值)。解决办法
这个可怜的人解决这个问题的办法就是每次你打电话给a
SDL_RenderPresent
。根据你的纹理大小,每100个纹理做一次可能没问题。但是请注意,在不更新渲染器的情况下重复调用
SDL_CreateTextureFromSurface
实际上会填满系统ram,sdl不会返回任何错误条件来检查这一点。讽刺的是,如果我实现了一个“正确”的加载循环,屏幕上的完成百分比,我就永远不会有这个问题。但命运让我以一种快速而肮脏的方式来实现这一点,作为一个更大系统概念的证明,我被卷入了这个问题。