Текстура SDL2 иногда пуста после загрузки нескольких 8-битных поверхностей - PullRequest
4 голосов
/ 09 апреля 2019

Я постараюсь сделать этот вопрос как можно более кратким, но не стесняйтесь просить разъяснений.

Я имею дело с устаревшим кодом и пытаюсь загрузить тысячибитовые изображения с диска для создания текстуры для каждого.

Я перепробовал несколько вещей, и я нахожусь в точке, где я пытаюсь загрузить свои 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;
}

Обратите внимание, что в оригиналекод, я проверяю границы и NULL после SDL_Create*.Я также знаю, что было бы лучше иметь таблицу спрайтов для текстур, а не загружать каждую текстуру по отдельности.

РЕДАКТИРОВАТЬ: Вот пример того, что я наблюдаю в NSight, еслиЯ фиксирую кадр и использую представление ресурсов.

Первые 3186 текстур верны.Тогда я получаю 43 пустых текстуры.Тогда я получаю 228 правильных текстур.Тогда 100 плохих.Тогда 539 правильных.Тогда 665 плохих.Это происходит случайным образом и меняется каждый раз, когда я запускаю свою программу.

Опять же, каждый раз, когда поверхности, сохраненные с помощью IMG_SavePNG, верны.Кажется, это указывает на то, что что-то происходит, когда я звоню SDL_CreateTextureFromSurface, но в этот момент я не хочу ничего исключать, потому что это очень странная проблема, и везде пахнет неопределенным поведением.Но я просто не могу найти проблему.

1 Ответ

3 голосов
/ 11 апреля 2019

С помощью @ mark-benningfield я смог найти проблему.

TL; DR

В SDL есть ошибка (или, по крайней мере, недокументированная функция) с рендерером DX11. Есть обходной путь; смотри в конце.

КОНТЕКСТ

Я пытаюсь загрузить около 12 000 текстур при запуске моей программы. Я знаю, что это не очень хорошая идея, но я планировал использовать ее в качестве трамплина для другой более разумной системы.

ОПИСАНИЕ

При отладке этой проблемы я понял, что средство визуализации SDL для DirectX 11 делает это при создании текстуры:

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, но использует промежуточное использование для загрузки фактического пиксели.

Я не так много знаю о программировании на DX11, но это смешение методов вызвало у моего программиста ощущение покалывания.

Я связался с известным мне программистом и объяснил ему проблему. Он рассказал мне следующие интересные биты:

  • Драйвер решает, где хранить промежуточные текстуры. Обычно он находится в оперативной памяти.
  • Намного лучше указывать указатель pInitialData, так как драйвер может решить загружать текстуры асинхронно.
  • Если вы загружаете слишком много промежуточных текстур, не передавая их в графический процессор, вы можете заполнить оперативную память.

Затем я удивился, почему 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).

1068 * Временное решение * Обходной путь для бедного человека к этой проблеме - звонить SDL_RenderPresent каждый раз, когда вы звоните SDL_CreateTextureFromSurface. Вероятно, это хорошо делать раз в сотню текстур в зависимости от размера текстур. Но имейте в виду, что повторный вызов SDL_CreateTextureFromSurface без обновления средства визуализации фактически заполнит системную память, а SDL не вернет вам ни одной ошибки, чтобы проверить это. Ирония в том, что если бы я реализовал «правильный» цикл загрузки с процентом выполнения на экране, у меня никогда бы не было этой проблемы. Но судьба заставила меня осуществить это быстро и грязно, как доказательство концепции большой системы, и я был поглощен этой проблемой.

...