С помощью @ 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 не вернет вам ни одной ошибки, чтобы проверить это.
Ирония в том, что если бы я реализовал «правильный» цикл загрузки с процентом выполнения на экране, у меня никогда бы не было этой проблемы. Но судьба заставила меня осуществить это быстро и грязно, как доказательство концепции большой системы, и я был поглощен этой проблемой.