c - 加载多个 8 位表面后,SDL2 纹理有时为空
问题描述
我会尽量使这个问题尽可能简洁,但请毫不犹豫地要求澄清。
我正在处理遗留代码,我正在尝试从磁盘加载数千个 8 位图像来为每个图像创建一个纹理。
我已经尝试了多种方法,我正试图将我的 8 位图像加载到 32 位表面,然后从该表面创建纹理。
问题:在将 8 位图像加载到 32 位表面上工作时,当我尝试时SDL_CreateTextureFromSurface
,我最终会得到很多完全空白的纹理(充满透明像素,0x00000000)。
不是所有的纹理都是错误的,我想。每次我运行程序时,我都会得到不同的“坏”纹理。有时多,有时少。当我跟踪程序时,我总是以正确的纹理结束(这是时间问题吗?)
我知道加载到的SDL_Surface
工作正常,因为我将所有表面保存到磁盘,它们都是正确的。但我使用NVidia NSight Graphics检查了纹理,其中一半以上是空白的。
这是有问题的代码:
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 中观察到的示例。
前 3186 个纹理是正确的。然后我得到 43 个空纹理。然后我得到 228 个正确的纹理。然后是100个坏的。然后是 539 个正确的。然后是665个坏的。它像那样随机进行,每次运行程序时都会发生变化。
同样,每次保存的表面IMG_SavePNG
都是正确的。这似乎表明当我打电话时发生了一些事情,SDL_CreateTextureFromSurface
但在那一点上,我不想排除任何事情,因为这是一个非常奇怪的问题,而且它到处都是未定义的行为。但我就是找不到问题。
解决方案
在@mark-benningfield 的帮助下,我找到了问题所在。
TL;博士
DX11 渲染器在 SDL 中存在错误(或至少是未记录的功能)。有一个解决方法;见最后。
语境
当我的程序启动时,我试图加载大约 12,000 个纹理。我知道这不是一个好主意,但我正计划将其用作另一个更健全的系统的垫脚石。
细节
我在调试该问题时意识到的是 DirectX 11 的 SDL 渲染器在创建纹理时会这样做:
result = ID3D11Device_CreateTexture2D(rendererData->d3dDevice,
&textureDesc,
NULL,
&textureData->mainTexture
);
Microsoft 的ID3D11Device::CreateTexture2D 方法页面表明:
如果您不向 pInitialData 传递任何内容,则资源的内存的初始内容是未定义的。在这种情况下,您需要在读取资源之前以其他方式写入资源内容。
如果我们相信那篇文章:
默认使用 最常见的使用类型是默认使用。要填充默认纹理(使用 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;
}
注意:为了简洁起见,代码被剪掉了。
这似乎表明,根据我提到的那篇文章,SDL 正在使用 Default Usage 的第二种方法在 GPU 上分配纹理内存,但使用 Staging Usage 来上传实际像素。
我对 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
的返回值)。
解决方法
穷人解决这个问题的方法是SDL_RenderPresent
每次调用SDL_CreateTextureFromSurface
.
根据您的纹理大小,每一百个纹理执行一次可能很好。但请注意,SDL_CreateTextureFromSurface
在不更新渲染器的情况下重复调用实际上会填满系统 RAM,并且 SDL 不会返回任何错误条件来检查这一点。
具有讽刺意味的是,如果我实现了一个“正确”的加载循环,并在屏幕上显示完成百分比,我永远不会遇到这个问题。但是命运让我以快速而肮脏的方式实现了这个,作为更大系统的概念证明,我陷入了这个问题。
推荐阅读
- python - 将具有不同列和行的两个 Pandas 数据框合并为一个
- python - table=None 但表确实存在
- python - 从 Python 脚本传递和接收数据
- powershell - Powershell Pester 单元测试无法模拟 Expand-Archive
- javascript - 在 Express 的上下文中,“静态”和“非静态”是什么意思?
- sql - 使用带有存在运算符的 SQL 相关子查询
- android - 使用 LiveData 观察片段内的网络变化
- xamarin.forms - Xamarin Forms 项目中的 ADB1000 部署失败(路径中有非法字符)
- debugging - 是否可以使用符号在 TRACE32 中设置范围断点?
- typescript - TypeScript 泛型,提升函数返回类型