Vulkan: Handle texture allocation failure.

Users hit out of memory even using desktop GL devices, and it will
definitely be possible on mobile and desktop Vulkan.
This commit is contained in:
Unknown W. Brackets 2016-03-26 18:41:53 -07:00
parent 27a5697a96
commit 16570f10bd
3 changed files with 68 additions and 59 deletions

View File

@ -206,10 +206,8 @@ void VulkanTexture::Unlock() {
mappableMemory = VK_NULL_HANDLE;
}
VkImageViewCreateInfo view_info = {};
view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
view_info.pNext = NULL;
view_info.image = VK_NULL_HANDLE;
VkImageViewCreateInfo view_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
view_info.image = image;
view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
view_info.format = format_;
view_info.components.r = VK_COMPONENT_SWIZZLE_R;
@ -221,7 +219,6 @@ void VulkanTexture::Unlock() {
view_info.subresourceRange.levelCount = 1;
view_info.subresourceRange.baseArrayLayer = 0;
view_info.subresourceRange.layerCount = 1;
view_info.image = image;
VkResult res = vkCreateImageView(vulkan_->GetDevice(), &view_info, NULL, &view);
assert(res == VK_SUCCESS);
}
@ -244,7 +241,7 @@ void VulkanTexture::Wipe() {
}
}
void VulkanTexture::CreateDirect(int w, int h, int numMips, VkFormat format, VkImageUsageFlags usage, const VkComponentMapping *mapping) {
bool VulkanTexture::CreateDirect(int w, int h, int numMips, VkFormat format, VkImageUsageFlags usage, const VkComponentMapping *mapping) {
Wipe();
VkCommandBuffer cmd = vulkan_->GetInitCommandBuffer();
@ -268,12 +265,18 @@ void VulkanTexture::CreateDirect(int w, int h, int numMips, VkFormat format, VkI
image_create_info.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VkResult res = vkCreateImage(vulkan_->GetDevice(), &image_create_info, NULL, &image);
assert(res == VK_SUCCESS);
if (res != VK_SUCCESS) {
assert(res == VK_ERROR_OUT_OF_HOST_MEMORY || res == VK_ERROR_OUT_OF_DEVICE_MEMORY || res == VK_ERROR_TOO_MANY_OBJECTS);
return false;
}
vkGetImageMemoryRequirements(vulkan_->GetDevice(), image, &mem_reqs);
if (allocator_) {
offset_ = allocator_->Allocate(mem_reqs, &mem);
if (offset_ == VulkanDeviceAllocator::ALLOCATE_FAILED) {
return false;
}
} else {
VkMemoryAllocateInfo mem_alloc = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };
mem_alloc.memoryTypeIndex = 0;
@ -284,13 +287,19 @@ void VulkanTexture::CreateDirect(int w, int h, int numMips, VkFormat format, VkI
assert(pass);
res = vkAllocateMemory(vulkan_->GetDevice(), &mem_alloc, NULL, &mem);
assert(res == VK_SUCCESS);
if (res != VK_SUCCESS) {
assert(res == VK_ERROR_OUT_OF_HOST_MEMORY || res == VK_ERROR_OUT_OF_DEVICE_MEMORY || res == VK_ERROR_TOO_MANY_OBJECTS);
return false;
}
offset_ = 0;
}
res = vkBindImageMemory(vulkan_->GetDevice(), image, mem, offset_);
assert(res == VK_SUCCESS);
if (res != VK_SUCCESS) {
assert(res == VK_ERROR_OUT_OF_HOST_MEMORY || res == VK_ERROR_OUT_OF_DEVICE_MEMORY || res == VK_ERROR_TOO_MANY_OBJECTS);
return false;
}
// Since we're going to blit to the target, set its layout to TRANSFER_DST
TransitionImageLayout(cmd, image,
@ -299,10 +308,8 @@ void VulkanTexture::CreateDirect(int w, int h, int numMips, VkFormat format, VkI
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
// Create the view while we're at it.
VkImageViewCreateInfo view_info = {};
view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
view_info.pNext = NULL;
view_info.image = VK_NULL_HANDLE;
VkImageViewCreateInfo view_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
view_info.image = image;
view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
view_info.format = format_;
if (mapping) {
@ -318,9 +325,12 @@ void VulkanTexture::CreateDirect(int w, int h, int numMips, VkFormat format, VkI
view_info.subresourceRange.levelCount = numMips;
view_info.subresourceRange.baseArrayLayer = 0;
view_info.subresourceRange.layerCount = 1;
view_info.image = image;
res = vkCreateImageView(vulkan_->GetDevice(), &view_info, NULL, &view);
assert(res == VK_SUCCESS);
if (res != VK_SUCCESS) {
assert(res == VK_ERROR_OUT_OF_HOST_MEMORY || res == VK_ERROR_OUT_OF_DEVICE_MEMORY || res == VK_ERROR_TOO_MANY_OBJECTS);
return false;
}
return true;
}
void VulkanTexture::UploadMip(int mip, int mipWidth, int mipHeight, VkBuffer buffer, uint32_t offset, size_t rowLength) {

View File

@ -29,7 +29,7 @@ public:
void Unlock();
// Fast uploads from buffer. Mipmaps supported. Usage must at least include VK_IMAGE_USAGE_TRANSFER_DST_BIT in order to use UploadMip.
void CreateDirect(int w, int h, int numMips, VkFormat format, VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, const VkComponentMapping *mapping = nullptr);
bool CreateDirect(int w, int h, int numMips, VkFormat format, VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, const VkComponentMapping *mapping = nullptr);
void UploadMip(int mip, int mipWidth, int mipHeight, VkBuffer buffer, uint32_t offset, size_t rowLength); // rowLength is in pixels
void EndCreate();
int GetNumMips() const { return numMips_; }

View File

@ -1320,11 +1320,8 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) {
VkFormat actualFmt = scaleFactor > 1 ? VULKAN_8888_FORMAT : dstFmt;
if (replaceImages) {
if (!entry->vkTex) {
Crash();
}
} else {
if (!replaceImages || !entry->vkTex) {
delete entry->vkTex;
entry->vkTex = new CachedTextureVulkan();
entry->vkTex->texture_ = new VulkanTexture(vulkan_, allocator_);
VulkanTexture *image = entry->vkTex->texture_;
@ -1348,22 +1345,49 @@ void TextureCacheVulkan::SetTexture(VulkanPushBuffer *uploadBuffer) {
break;
}
image->CreateDirect(w * scaleFactor, h * scaleFactor, maxLevel + 1, actualFmt, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, mapping);
bool allocSuccess = image->CreateDirect(w * scaleFactor, h * scaleFactor, maxLevel + 1, actualFmt, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, mapping);
if (!allocSuccess && !lowMemoryMode_) {
WARN_LOG_REPORT(G3D, "Texture cache ran out of GPU memory; switching to low memory mode");
lowMemoryMode_ = true;
decimationCounter_ = 0;
Decimate();
// TODO: We should stall the GPU here and wipe things out of memory.
// As is, it will almost definitely fail the second time, but next frame it may recover.
I18NCategory *err = GetI18NCategory("Error");
if (scaleFactor > 1) {
osm.Show(err->T("Warning: Video memory FULL, reducing upscaling and switching to slow caching mode"), 2.0f);
} else {
osm.Show(err->T("Warning: Video memory FULL, switching to slow caching mode"), 2.0f);
}
scaleFactor = 1;
actualFmt = dstFmt;
allocSuccess = image->CreateDirect(w * scaleFactor, h * scaleFactor, maxLevel + 1, actualFmt, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, mapping);
}
if (!allocSuccess) {
delete entry->vkTex;
entry->vkTex = nullptr;
}
}
lastBoundTexture = entry->vkTex;
// Upload the texture data.
for (int i = 0; i <= maxLevel; i++) {
int mipWidth = gstate.getTextureWidth(i) * scaleFactor;
int mipHeight = gstate.getTextureHeight(i) * scaleFactor;
int bpp = actualFmt == VULKAN_8888_FORMAT ? 4 : 2;
int stride = (mipWidth * bpp + 15) & ~15;
int size = stride * mipHeight;
uint32_t bufferOffset;
VkBuffer texBuf;
void *data = uploadBuffer->Push(size, &bufferOffset, &texBuf);
LoadTextureLevel(*entry, (uint8_t *)data, stride, i, replaceImages, scaleFactor, dstFmt);
entry->vkTex->texture_->UploadMip(i, mipWidth, mipHeight, texBuf, bufferOffset, stride / bpp);
if (entry->vkTex) {
// Upload the texture data.
for (int i = 0; i <= maxLevel; i++) {
int mipWidth = gstate.getTextureWidth(i) * scaleFactor;
int mipHeight = gstate.getTextureHeight(i) * scaleFactor;
int bpp = actualFmt == VULKAN_8888_FORMAT ? 4 : 2;
int stride = (mipWidth * bpp + 15) & ~15;
int size = stride * mipHeight;
uint32_t bufferOffset;
VkBuffer texBuf;
void *data = uploadBuffer->Push(size, &bufferOffset, &texBuf);
LoadTextureLevel(*entry, (uint8_t *)data, stride, i, replaceImages, scaleFactor, dstFmt);
entry->vkTex->texture_->UploadMip(i, mipWidth, mipHeight, texBuf, bufferOffset, stride / bpp);
}
}
gstate_c.textureFullAlpha = entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL;
@ -1658,29 +1682,4 @@ void TextureCacheVulkan::LoadTextureLevel(TexCacheEntry &entry, uint8_t *writePt
memcpy(writePtr + rowPitch * y, (const uint8_t *)pixelData + decPitch * y, rowBytes);
}
}
/*
if (!lowMemoryMode_) {
GLenum err = glGetError();
if (err == GL_OUT_OF_MEMORY) {
WARN_LOG_REPORT(G3D, "Texture cache ran out of GPU memory; switching to low memory mode");
lowMemoryMode_ = true;
decimationCounter_ = 0;
Decimate();
// TODO: We need to stall the GPU here and wipe things out of memory.
// Try again, now that we've cleared out textures in lowMemoryMode_.
glTexImage2D(GL_TEXTURE_2D, level, components, w, h, 0, components2, dstFmt, pixelData);
I18NCategory *err = GetI18NCategory("Error");
if (scaleFactor > 1) {
osm.Show(err->T("Warning: Video memory FULL, reducing upscaling and switching to slow caching mode"), 2.0f);
} else {
osm.Show(err->T("Warning: Video memory FULL, switching to slow caching mode"), 2.0f);
}
} else if (err != GL_NO_ERROR) {
// We checked the err anyway, might as well log if there is one.
WARN_LOG(G3D, "Got an error in texture upload: %08x", err);
}
}*/
}