Permalink
Browse files

Merge pull request #10242 from hrydgard/thin3d-texture-alloc

Use a VulkanDeviceAllocator for thin3d textures. Saves on allocations.
  • Loading branch information...
hrydgard committed Dec 3, 2017
2 parents 97bdc72 + 6a2f0f4 commit cc2e162a0035ecc12c5388bb8c0f077c68d0d1d7
@@ -1,5 +1,6 @@
#include "Common/Vulkan/VulkanImage.h"
#include "Common/Vulkan/VulkanMemory.h"
#include "Common/Log.h"
VkResult VulkanTexture::Create(int w, int h, VkFormat format) {
tex_width = w;
@@ -118,14 +119,18 @@ void VulkanTexture::Unlock(VkCommandBuffer cmd) {
mem_alloc.memoryTypeIndex = 0;
mem_alloc.allocationSize = mem_reqs.size;
// Find memory type - don't specify any mapping requirements
bool pass = vulkan_->MemoryTypeFromProperties(mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &mem_alloc.memoryTypeIndex);
assert(pass);
res = vkAllocateMemory(vulkan_->GetDevice(), &mem_alloc, NULL, &mem);
assert(res == VK_SUCCESS);
if (allocator_) {
offset_ = allocator_->Allocate(mem_reqs, &mem);
} else {
offset_ = 0;
// Find memory type - don't specify any mapping requirements
bool pass = vulkan_->MemoryTypeFromProperties(mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &mem_alloc.memoryTypeIndex);
assert(pass);
res = vkAllocateMemory(vulkan_->GetDevice(), &mem_alloc, NULL, &mem);
assert(res == VK_SUCCESS);
}
res = vkBindImageMemory(vulkan_->GetDevice(), image, mem, 0);
res = vkBindImageMemory(vulkan_->GetDevice(), image, mem, offset_);
assert(res == VK_SUCCESS);
// Since we're going to blit from the mappable image, set its layout to SOURCE_OPTIMAL
@@ -294,6 +299,7 @@ bool VulkanTexture::CreateDirect(VkCommandBuffer cmd, int w, int h, int numMips,
res = vkAllocateMemory(vulkan_->GetDevice(), &mem_alloc, NULL, &mem);
if (res != VK_SUCCESS) {
_assert_msg_(G3D, res != VK_ERROR_TOO_MANY_OBJECTS, "Too many Vulkan memory objects!");
assert(res == VK_ERROR_OUT_OF_HOST_MEMORY || res == VK_ERROR_OUT_OF_DEVICE_MEMORY || res == VK_ERROR_TOO_MANY_OBJECTS);
return false;
}
@@ -8,7 +8,7 @@ class VulkanDeviceAllocator;
// Not very optimal - if you have many small textures you should use other strategies.
class VulkanTexture {
public:
VulkanTexture(VulkanContext *vulkan, VulkanDeviceAllocator *allocator = nullptr)
VulkanTexture(VulkanContext *vulkan, VulkanDeviceAllocator *allocator)
: vulkan_(vulkan), image(VK_NULL_HANDLE), mem(VK_NULL_HANDLE), view(VK_NULL_HANDLE),
tex_width(0), tex_height(0), numMips_(1), format_(VK_FORMAT_UNDEFINED),
mappableImage(VK_NULL_HANDLE), mappableMemory(VK_NULL_HANDLE),
@@ -169,11 +169,10 @@ void VulkanDeviceAllocator::Destroy() {
for (Slab &slab : slabs_) {
// Did anyone forget to free?
for (auto pair : slab.allocSizes) {
if (slab.usage[pair.first] != 2) {
// If it's not 2 (queued), there's a problem.
// If it's zero, it means allocSizes is somehow out of sync.
Crash();
}
int slabUsage = slab.usage[pair.first];
// If it's not 2 (queued), there's a problem.
// If it's zero, it means allocSizes is somehow out of sync.
_assert_msg_(G3D, slabUsage == 2, "Destroy: slabUsage has unexpected value %d", slabUsage);
}
assert(slab.deviceMemory);
@@ -278,6 +277,8 @@ bool VulkanDeviceAllocator::AllocateFromSlab(Slab &slab, size_t &start, size_t b
void VulkanDeviceAllocator::Free(VkDeviceMemory deviceMemory, size_t offset) {
assert(!destroyed_);
_assert_msg_(G3D, !slabs_.empty(), "No slabs - can't be anything to free! double-freed?");
// First, let's validate. This will allow stack traces to tell us when frees are bad.
size_t start = offset >> SLAB_GRAIN_SHIFT;
bool found = false;
@@ -287,14 +288,9 @@ void VulkanDeviceAllocator::Free(VkDeviceMemory deviceMemory, size_t offset) {
}
auto it = slab.allocSizes.find(start);
if (it == slab.allocSizes.end()) {
// Ack, a double free?
Crash();
}
if (slab.usage[start] != 1) {
// This means a double free, while queued to actually free.
Crash();
}
_assert_msg_(G3D, it != slab.allocSizes.end(), "Double free?");
// This means a double free, while queued to actually free.
_assert_msg_(G3D, slab.usage[start] == 1, "Double free when queued to free!");
// Mark it as "free in progress".
slab.usage[start] = 2;
@@ -303,9 +299,7 @@ void VulkanDeviceAllocator::Free(VkDeviceMemory deviceMemory, size_t offset) {
}
// Wrong deviceMemory even? Maybe it was already decimated, but that means a double-free.
if (!found) {
Crash();
}
_assert_msg_(G3D, found, "Failed to find allocation to free! Double-freed?");
// Okay, now enqueue. It's valid.
FreeInfo *info = new FreeInfo(this, deviceMemory, offset);
@@ -146,6 +146,10 @@ class VulkanDeviceAllocator {
static const size_t ALLOCATE_FAILED = -1;
int GetBlockCount() const { return (int)slabs_.size(); }
int GetMinSlabSize() const { return (int)minSlabSize_; }
int GetMaxSlabSize() const { return (int)maxSlabSize_; }
private:
static const size_t SLAB_GRAIN_SIZE = 1024;
static const uint8_t SLAB_GRAIN_SHIFT = 10;
@@ -296,7 +296,7 @@ void DrawEngineVulkan::BeginFrame() {
if (!nullTexture_) {
ILOG("INIT : Creating null texture");
VkCommandBuffer cmdInit = (VkCommandBuffer)draw_->GetNativeObject(Draw::NativeObject::INIT_COMMANDBUFFER);
nullTexture_ = new VulkanTexture(vulkan_);
nullTexture_ = new VulkanTexture(vulkan_, textureCache_->GetAllocator());
int w = 8;
int h = 8;
nullTexture_->CreateDirect(cmdInit, w, h, 1, VK_FORMAT_A8B8G8R8_UNORM_PACK32, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
@@ -205,7 +205,8 @@ void FramebufferManagerVulkan::MakePixelTexture(const u8 *srcPixels, GEBufferFor
VkCommandBuffer initCmd = (VkCommandBuffer)draw_->GetNativeObject(Draw::NativeObject::INIT_COMMANDBUFFER);
drawPixelsTex_ = new VulkanTexture(vulkan_);
// There's only ever a few of these alive, don't need to stress the allocator with these big ones.
drawPixelsTex_ = new VulkanTexture(vulkan_, nullptr);
if (!drawPixelsTex_->CreateDirect(initCmd, width, height, 1, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT)) {
// out of memory?
delete drawPixelsTex_;
@@ -168,6 +168,7 @@ GPU_Vulkan::~GPU_Vulkan() {
framebufferManagerVulkan_->DestroyAllFBOs();
vulkan2D_.Shutdown();
depalShaderCache_.Clear();
drawEngine_.DeviceLost();
delete textureCacheVulkan_;
delete pipelineManager_;
delete shaderManagerVulkan_;
@@ -638,6 +639,8 @@ void GPU_Vulkan::DeviceRestore() {
void GPU_Vulkan::GetStats(char *buffer, size_t bufsize) {
const DrawEngineVulkanStats &drawStats = drawEngine_.GetStats();
char texStats[256];
textureCacheVulkan_->GetStats(texStats, sizeof(texStats));
float vertexAverageCycles = gpuStats.numVertsSubmitted > 0 ? (float)gpuStats.vertexGPUCycles / (float)gpuStats.numVertsSubmitted : 0.0f;
snprintf(buffer, bufsize - 1,
"DL processing time: %0.2f ms\n"
@@ -652,7 +655,8 @@ void GPU_Vulkan::GetStats(char *buffer, size_t bufsize) {
"Textures active: %i, decoded: %i invalidated: %i\n"
"Readbacks: %d\n"
"Vertex, Fragment, Pipelines loaded: %i, %i, %i\n"
"Pushbuffer space used: UBO %d, Vtx %d, Idx %d\n",
"Pushbuffer space used: UBO %d, Vtx %d, Idx %d\n"
"%s\n",
gpuStats.msProcessingDisplayLists * 1000.0f,
gpuStats.numDrawCalls,
gpuStats.numFlushes,
@@ -674,7 +678,8 @@ void GPU_Vulkan::GetStats(char *buffer, size_t bufsize) {
pipelineManager_->GetNumPipelines(),
drawStats.pushUBOSpaceUsed,
drawStats.pushVertexSpaceUsed,
drawStats.pushIndexSpaceUsed
drawStats.pushIndexSpaceUsed,
texStats
);
}
@@ -816,6 +816,10 @@ bool TextureCacheVulkan::GetCurrentTextureDebug(GPUDebugBuffer &buffer, int leve
// So let's dirty the things that are involved in Vulkan dynamic state. Readbacks are not frequent so this won't hurt other backends.
gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE);
framebufferManager_->RebindFramebuffer();
return true;
}
void TextureCacheVulkan::GetStats(char *ptr, size_t size) {
snprintf(ptr, size, "Alloc: %d blocks\nSlab min/max: %d/%d\n",
allocator_->GetBlockCount(), allocator_->GetMinSlabSize(), allocator_->GetMaxSlabSize());
}
@@ -107,6 +107,10 @@ class TextureCacheVulkan : public TextureCacheCommon {
bool GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level) override;
void GetStats(char *ptr, size_t size);
VulkanDeviceAllocator *GetAllocator() { return allocator_; }
protected:
void BindTexture(TexCacheEntry *entry) override;
void Unbind() override;
@@ -312,9 +312,9 @@ struct DescriptorSetKey {
class VKTexture : public Texture {
public:
VKTexture(VulkanContext *vulkan, VkCommandBuffer cmd, const TextureDesc &desc)
VKTexture(VulkanContext *vulkan, VkCommandBuffer cmd, const TextureDesc &desc, VulkanDeviceAllocator *alloc)
: vulkan_(vulkan), mipLevels_(desc.mipLevels), format_(desc.format) {
bool result = Create(cmd, desc);
bool result = Create(cmd, desc, alloc);
assert(result);
}
@@ -327,7 +327,7 @@ class VKTexture : public Texture {
private:
void SetImageData(VkCommandBuffer cmd, int x, int y, int z, int width, int height, int depth, int level, int stride, const uint8_t *data);
bool Create(VkCommandBuffer cmd, const TextureDesc &desc);
bool Create(VkCommandBuffer cmd, const TextureDesc &desc, VulkanDeviceAllocator *alloc);
void Destroy() {
if (vkTex_) {
@@ -497,6 +497,8 @@ class VKContext : public DrawContext {
VulkanRenderManager renderManager_;
VulkanDeviceAllocator *allocator_ = nullptr;
VKPipeline *curPipeline_ = nullptr;
VKBuffer *curVBuffers_[4]{};
int curVBufferOffsets_[4]{};
@@ -657,7 +659,7 @@ enum class TextureState {
PENDING_DESTRUCTION,
};
bool VKTexture::Create(VkCommandBuffer cmd, const TextureDesc &desc) {
bool VKTexture::Create(VkCommandBuffer cmd, const TextureDesc &desc, VulkanDeviceAllocator *alloc) {
// Zero-sized textures not allowed.
if (desc.width * desc.height * desc.depth == 0)
return false;
@@ -666,7 +668,7 @@ bool VKTexture::Create(VkCommandBuffer cmd, const TextureDesc &desc) {
width_ = desc.width;
height_ = desc.height;
depth_ = desc.depth;
vkTex_ = new VulkanTexture(vulkan_);
vkTex_ = new VulkanTexture(vulkan_, alloc);
if (desc.initData.size()) {
for (int i = 0; i < (int)desc.initData.size(); i++) {
this->SetImageData(cmd, 0, 0, 0, width_, height_, depth_, i, 0, desc.initData[i]);
@@ -758,9 +760,13 @@ VKContext::VKContext(VulkanContext *vulkan, bool splitSubmit)
pipelineCache_ = vulkan_->CreatePipelineCache();
renderManager_.SetSplitSubmit(splitSubmit);
allocator_ = new VulkanDeviceAllocator(vulkan_, 256 * 1024, 2048 * 1024);
}
VKContext::~VKContext() {
allocator_->Destroy();
delete allocator_;
// This also destroys all descriptor sets.
for (int i = 0; i < VulkanContext::MAX_INFLIGHT_FRAMES; i++) {
frame_[i].descSets_.clear();
@@ -996,7 +1002,7 @@ InputLayout *VKContext::CreateInputLayout(const InputLayoutDesc &desc) {
}
Texture *VKContext::CreateTexture(const TextureDesc &desc) {
return new VKTexture(vulkan_, renderManager_.GetInitCmd(), desc);
return new VKTexture(vulkan_, renderManager_.GetInitCmd(), desc, allocator_);
}
void VKTexture::SetImageData(VkCommandBuffer cmd, int x, int y, int z, int width, int height, int depth, int level, int stride, const uint8_t *data) {

0 comments on commit cc2e162

Please sign in to comment.