繁体   English   中英

加载多个8位表面后,SDL2纹理有时会变空

[英]SDL2 Texture sometimes empty after loading multiple 8 bit surfaces

我会尽量使这个问题尽可能简洁,但请不要犹豫要求澄清。

我正在处理遗留代码,我正在尝试从磁盘加载数千个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*之后检查NULL。 我也知道最好有纹理的spritesheet而不是单独加载每个纹理。

编辑:这是我在NSight中观察到的一个示例,如果我捕获一个帧并使用资源视图。

前3186纹理是正确的。 然后我得到43个空纹理。 然后我得到228个正确的纹理。 然后100个坏的。 然后是539个正确的。 然后是665个不好的。 它随机地继续下去,每次运行我的程序时它都会改变。

同样,每次IMG_SavePNG保存的IMG_SavePNG都是正确的。 这似乎表明当我调用SDL_CreateTextureFromSurface时会发生一些事情,但在那时,我不想排除任何事情,因为这是一个非常奇怪的问题,它会在整个地方闻到未定义的行为。 但我只是找不到问题。

在@ mark-benningfield的帮助下,我找到了问题所在。

TL; DR

使用DX11渲染器在SDL中存在一个错误(或至少是一个未记录的功能)。 有一个解决方法; 看到最后。

CONTEXT

我的程序启动时,我正在尝试加载大约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正在使用默认用法的第二种方法在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不会返回任何错误条件来检查这一点。

具有讽刺意味的是,如果我在屏幕上实现了一个“正确”的加载循环,完成百分比,我就不会遇到这个问题了。 但命运让我以快速而肮脏的方式实现这一点,作为更大系统的概念证明,我陷入了这个问题。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM