mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-04-02 11:01:50 -04:00
Merge pull request #8649 from unknownbrackets/vulkan-buf
Dynamically reallocate buffers when out of space
This commit is contained in:
commit
2485c320f3
4 changed files with 132 additions and 61 deletions
|
@ -20,29 +20,79 @@
|
|||
|
||||
#include "Common/Vulkan/VulkanMemory.h"
|
||||
|
||||
VulkanPushBuffer::VulkanPushBuffer(VulkanContext *vulkan, size_t size) : offset_(0), size_(size), writePtr_(nullptr), deviceMemory_(0) {
|
||||
VkDevice device = vulkan->GetDevice();
|
||||
VulkanPushBuffer::VulkanPushBuffer(VulkanContext *vulkan, size_t size) : ctx_(vulkan), buf_(0), offset_(0), size_(size), writePtr_(nullptr) {
|
||||
bool res = AddBuffer();
|
||||
assert(res);
|
||||
}
|
||||
|
||||
bool VulkanPushBuffer::AddBuffer() {
|
||||
VkDevice device = ctx_->GetDevice();
|
||||
BufInfo info;
|
||||
|
||||
VkBufferCreateInfo b = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
|
||||
b.size = size;
|
||||
b.size = size_;
|
||||
b.flags = 0;
|
||||
b.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
||||
b.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
b.queueFamilyIndexCount = 0;
|
||||
b.pQueueFamilyIndices = nullptr;
|
||||
VkResult res = vkCreateBuffer(device, &b, nullptr, &buffer_);
|
||||
assert(VK_SUCCESS == res);
|
||||
|
||||
VkResult res = vkCreateBuffer(device, &b, nullptr, &info.buffer);
|
||||
if (VK_SUCCESS != res) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Okay, that's the buffer. Now let's allocate some memory for it.
|
||||
VkMemoryAllocateInfo alloc = {};
|
||||
alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
alloc.pNext = nullptr;
|
||||
VkMemoryAllocateInfo alloc = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };
|
||||
ctx_->MemoryTypeFromProperties(0xFFFFFFFF, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &alloc.memoryTypeIndex);
|
||||
alloc.allocationSize = size_;
|
||||
|
||||
vulkan->MemoryTypeFromProperties(0xFFFFFFFF, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &alloc.memoryTypeIndex);
|
||||
alloc.allocationSize = size;
|
||||
res = vkAllocateMemory(device, &alloc, nullptr, &info.deviceMemory);
|
||||
if (VK_SUCCESS != res) {
|
||||
return false;
|
||||
}
|
||||
res = vkBindBufferMemory(device, info.buffer, info.deviceMemory, 0);
|
||||
if (VK_SUCCESS != res) {
|
||||
return false;
|
||||
}
|
||||
|
||||
res = vkAllocateMemory(device, &alloc, nullptr, &deviceMemory_);
|
||||
assert(VK_SUCCESS == res);
|
||||
res = vkBindBufferMemory(device, buffer_, deviceMemory_, 0);
|
||||
assert(VK_SUCCESS == res);
|
||||
buf_ = buffers_.size();
|
||||
buffers_.resize(buf_ + 1);
|
||||
buffers_[buf_] = info;
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanPushBuffer::NextBuffer() {
|
||||
VkDevice device = ctx_->GetDevice();
|
||||
|
||||
// First, unmap the current memory.
|
||||
Unmap(device);
|
||||
|
||||
buf_++;
|
||||
if (buf_ >= buffers_.size()) {
|
||||
bool res = AddBuffer();
|
||||
assert(res);
|
||||
if (!res) {
|
||||
// Let's try not to crash at least?
|
||||
buf_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Now, move to the next buffer and map it.
|
||||
offset_ = 0;
|
||||
Map(device);
|
||||
}
|
||||
|
||||
void VulkanPushBuffer::Defragment() {
|
||||
if (buffers_.size() <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Okay, we have more than one. Destroy them all and start over with a larger one.
|
||||
size_t newSize = size_ * buffers_.size();
|
||||
Destroy(ctx_);
|
||||
|
||||
size_ = newSize;
|
||||
bool res = AddBuffer();
|
||||
assert(res);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "Common/Vulkan/VulkanContext.h"
|
||||
|
||||
// VulkanMemory
|
||||
|
@ -13,69 +14,87 @@
|
|||
// has completed.
|
||||
//
|
||||
// TODO: Make it possible to suballocate pushbuffers from a large DeviceMemory block.
|
||||
// TODO: Make this auto-grow and shrink. Need to be careful about returning and using the new
|
||||
// buffer handle on overflow.
|
||||
class VulkanPushBuffer {
|
||||
struct BufInfo {
|
||||
VkBuffer buffer;
|
||||
VkDeviceMemory deviceMemory;
|
||||
};
|
||||
|
||||
public:
|
||||
VulkanPushBuffer(VulkanContext *vulkan, size_t size);
|
||||
|
||||
~VulkanPushBuffer() {
|
||||
assert(buffer_ == VK_NULL_HANDLE);
|
||||
assert(deviceMemory_ == VK_NULL_HANDLE);
|
||||
assert(buffers_.empty());
|
||||
}
|
||||
|
||||
void Destroy(VulkanContext *vulkan) {
|
||||
vulkan->Delete().QueueDeleteBuffer(buffer_);
|
||||
vulkan->Delete().QueueDeleteDeviceMemory(deviceMemory_);
|
||||
buffer_ = VK_NULL_HANDLE;
|
||||
deviceMemory_ = VK_NULL_HANDLE;
|
||||
for (const BufInfo &info : buffers_) {
|
||||
vulkan->Delete().QueueDeleteBuffer(info.buffer);
|
||||
vulkan->Delete().QueueDeleteDeviceMemory(info.deviceMemory);
|
||||
}
|
||||
|
||||
buffers_.clear();
|
||||
}
|
||||
|
||||
void Reset() { offset_ = 0; }
|
||||
|
||||
void Begin(VkDevice device) {
|
||||
buf_ = 0;
|
||||
offset_ = 0;
|
||||
VkResult res = vkMapMemory(device, deviceMemory_, 0, size_, 0, (void **)(&writePtr_));
|
||||
assert(VK_SUCCESS == res);
|
||||
Defragment();
|
||||
Map(device);
|
||||
}
|
||||
|
||||
void End(VkDevice device) {
|
||||
Unmap(device);
|
||||
}
|
||||
|
||||
void Map(VkDevice device) {
|
||||
assert(!writePtr_);
|
||||
VkResult res = vkMapMemory(device, buffers_[buf_].deviceMemory, offset_, size_, 0, (void **)(&writePtr_));
|
||||
assert(VK_SUCCESS == res);
|
||||
}
|
||||
|
||||
void Unmap(VkDevice device) {
|
||||
assert(writePtr_);
|
||||
/*
|
||||
VkMappedMemoryRange range = { VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE };
|
||||
range.offset = 0;
|
||||
range.size = offset_;
|
||||
range.memory = deviceMemory_;
|
||||
range.memory = buffers_[buf_].deviceMemory;
|
||||
vkFlushMappedMemoryRanges(device, 1, &range);
|
||||
*/
|
||||
vkUnmapMemory(device, deviceMemory_);
|
||||
vkUnmapMemory(device, buffers_[buf_].deviceMemory);
|
||||
writePtr_ = nullptr;
|
||||
}
|
||||
|
||||
// When using the returned memory, make sure to bind the returned vkbuf.
|
||||
// This will later allow for handling overflow correctly.
|
||||
size_t Allocate(size_t numBytes, VkBuffer *vkbuf) {
|
||||
assert(numBytes < size_);
|
||||
|
||||
size_t out = offset_;
|
||||
offset_ += (numBytes + 3) & ~3; // Round up to 4 bytes.
|
||||
|
||||
if (offset_ >= size_) {
|
||||
// TODO: Allocate a second buffer, then combine them on the next frame.
|
||||
#ifdef _WIN32
|
||||
DebugBreak();
|
||||
#endif
|
||||
NextBuffer();
|
||||
out = offset_;
|
||||
offset_ += (numBytes + 3) & ~3;
|
||||
}
|
||||
*vkbuf = buffer_;
|
||||
*vkbuf = buffers_[buf_].buffer;
|
||||
return out;
|
||||
}
|
||||
|
||||
// TODO: Add alignment support?
|
||||
// Returns the offset that should be used when binding this buffer to get this data.
|
||||
size_t Push(const void *data, size_t size, VkBuffer *vkbuf) {
|
||||
assert(writePtr_);
|
||||
size_t off = Allocate(size, vkbuf);
|
||||
memcpy(writePtr_ + off, data, size);
|
||||
return off;
|
||||
}
|
||||
|
||||
uint32_t PushAligned(const void *data, size_t size, int align, VkBuffer *vkbuf) {
|
||||
assert(writePtr_);
|
||||
offset_ = (offset_ + align - 1) & ~(align - 1);
|
||||
size_t off = Allocate(size, vkbuf);
|
||||
memcpy(writePtr_ + off, data, size);
|
||||
|
@ -88,14 +107,20 @@ public:
|
|||
|
||||
// "Zero-copy" variant - you can write the data directly as you compute it.
|
||||
void *Push(size_t size, uint32_t *bindOffset, VkBuffer *vkbuf) {
|
||||
assert(writePtr_);
|
||||
size_t off = Allocate(size, vkbuf);
|
||||
*bindOffset = (uint32_t)off;
|
||||
return writePtr_ + off;
|
||||
}
|
||||
|
||||
private:
|
||||
VkDeviceMemory deviceMemory_;
|
||||
VkBuffer buffer_;
|
||||
bool AddBuffer();
|
||||
void NextBuffer();
|
||||
void Defragment();
|
||||
|
||||
VulkanContext *ctx_;
|
||||
std::vector<BufInfo> buffers_;
|
||||
size_t buf_;
|
||||
size_t offset_;
|
||||
size_t size_;
|
||||
uint8_t *writePtr_;
|
||||
|
|
|
@ -149,9 +149,9 @@ DrawEngineVulkan::DrawEngineVulkan(VulkanContext *vulkan)
|
|||
for (int i = 0; i < 2; i++) {
|
||||
VkResult res = vkCreateDescriptorPool(vulkan_->GetDevice(), &dp, nullptr, &frame_[i].descPool);
|
||||
assert(VK_SUCCESS == res);
|
||||
frame_[i].pushUBO = new VulkanPushBuffer(vulkan_, 16 * 1024 * 1024); // TODO: Do something more dynamic
|
||||
frame_[i].pushVertex = new VulkanPushBuffer(vulkan_, 8 * 1024 * 1024); // TODO: Do something more dynamic
|
||||
frame_[i].pushIndex = new VulkanPushBuffer(vulkan_, 2 * 1024 * 1024); // TODO: Do something more dynamic
|
||||
frame_[i].pushUBO = new VulkanPushBuffer(vulkan_, 8 * 1024 * 1024);
|
||||
frame_[i].pushVertex = new VulkanPushBuffer(vulkan_, 2 * 1024 * 1024);
|
||||
frame_[i].pushIndex = new VulkanPushBuffer(vulkan_, 1 * 1024 * 1024);
|
||||
}
|
||||
|
||||
VkPipelineLayoutCreateInfo pl;
|
||||
|
@ -616,18 +616,7 @@ void DrawEngineVulkan::DoFlush(VkCommandBuffer cmd) {
|
|||
}
|
||||
vkCmdBindPipeline(cmd_, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->pipeline); // TODO: Avoid if same as last draw.
|
||||
|
||||
if ((dirtyUniforms_ & DIRTY_BASE_UNIFORMS) || baseBuf == VK_NULL_HANDLE) {
|
||||
baseUBOOffset = shaderManager_->PushBaseBuffer(frame->pushUBO, &baseBuf);
|
||||
dirtyUniforms_ &= ~DIRTY_BASE_UNIFORMS;
|
||||
}
|
||||
if ((dirtyUniforms_ & DIRTY_LIGHT_UNIFORMS) || lightBuf == VK_NULL_HANDLE) {
|
||||
lightUBOOffset = shaderManager_->PushLightBuffer(frame->pushUBO, &lightBuf);
|
||||
dirtyUniforms_ &= ~DIRTY_LIGHT_UNIFORMS;
|
||||
}
|
||||
if ((dirtyUniforms_ & DIRTY_BONE_UNIFORMS) || boneBuf == VK_NULL_HANDLE) {
|
||||
boneUBOOffset = shaderManager_->PushBoneBuffer(frame->pushUBO, &boneBuf);
|
||||
dirtyUniforms_ &= ~DIRTY_BONE_UNIFORMS;
|
||||
}
|
||||
UpdateUBOs(frame);
|
||||
|
||||
VkDescriptorSet ds = GetDescriptorSet(imageView, sampler, baseBuf, lightBuf, boneBuf);
|
||||
|
||||
|
@ -722,19 +711,8 @@ void DrawEngineVulkan::DoFlush(VkCommandBuffer cmd) {
|
|||
}
|
||||
vkCmdBindPipeline(cmd_, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->pipeline); // TODO: Avoid if same as last draw.
|
||||
|
||||
if ((dirtyUniforms_ & DIRTY_BASE_UNIFORMS) || baseBuf == VK_NULL_HANDLE) {
|
||||
baseUBOOffset = shaderManager_->PushBaseBuffer(frame->pushUBO, &baseBuf);
|
||||
dirtyUniforms_ &= ~DIRTY_BASE_UNIFORMS;
|
||||
}
|
||||
// Even if the first draw is through-mode, make sure we at least have one copy of these uniforms buffered
|
||||
if ((dirtyUniforms_ & DIRTY_LIGHT_UNIFORMS) || lightBuf == VK_NULL_HANDLE) {
|
||||
lightUBOOffset = shaderManager_->PushLightBuffer(frame->pushUBO, &lightBuf);
|
||||
dirtyUniforms_ &= ~DIRTY_LIGHT_UNIFORMS;
|
||||
}
|
||||
if ((dirtyUniforms_ & DIRTY_BONE_UNIFORMS) || boneBuf == VK_NULL_HANDLE) {
|
||||
boneUBOOffset = shaderManager_->PushBoneBuffer(frame->pushUBO, &boneBuf);
|
||||
dirtyUniforms_ &= ~DIRTY_BONE_UNIFORMS;
|
||||
}
|
||||
UpdateUBOs(frame);
|
||||
|
||||
VkDescriptorSet ds = GetDescriptorSet(imageView, sampler, baseBuf, lightBuf, boneBuf);
|
||||
const uint32_t dynamicUBOOffsets[3] = {
|
||||
|
@ -827,6 +805,21 @@ void DrawEngineVulkan::DoFlush(VkCommandBuffer cmd) {
|
|||
host->GPUNotifyDraw();
|
||||
}
|
||||
|
||||
void DrawEngineVulkan::UpdateUBOs(FrameData *frame) {
|
||||
if ((dirtyUniforms_ & DIRTY_BASE_UNIFORMS) || baseBuf == VK_NULL_HANDLE) {
|
||||
baseUBOOffset = shaderManager_->PushBaseBuffer(frame->pushUBO, &baseBuf);
|
||||
dirtyUniforms_ &= ~DIRTY_BASE_UNIFORMS;
|
||||
}
|
||||
if ((dirtyUniforms_ & DIRTY_LIGHT_UNIFORMS) || lightBuf == VK_NULL_HANDLE) {
|
||||
lightUBOOffset = shaderManager_->PushLightBuffer(frame->pushUBO, &lightBuf);
|
||||
dirtyUniforms_ &= ~DIRTY_LIGHT_UNIFORMS;
|
||||
}
|
||||
if ((dirtyUniforms_ & DIRTY_BONE_UNIFORMS) || boneBuf == VK_NULL_HANDLE) {
|
||||
boneUBOOffset = shaderManager_->PushBoneBuffer(frame->pushUBO, &boneBuf);
|
||||
dirtyUniforms_ &= ~DIRTY_BONE_UNIFORMS;
|
||||
}
|
||||
}
|
||||
|
||||
void DrawEngineVulkan::Resized() {
|
||||
decJitCache_->Clear();
|
||||
lastVTypeID_ = -1;
|
||||
|
|
|
@ -144,8 +144,11 @@ public:
|
|||
void DirtyAllUBOs();
|
||||
|
||||
private:
|
||||
struct FrameData;
|
||||
|
||||
void DecodeVerts(VulkanPushBuffer *push, uint32_t *bindOffset, VkBuffer *vkbuf);
|
||||
void DoFlush(VkCommandBuffer cmd);
|
||||
void UpdateUBOs(FrameData *frame);
|
||||
|
||||
VkDescriptorSet GetDescriptorSet(VkImageView imageView, VkSampler sampler, VkBuffer base, VkBuffer light, VkBuffer bone);
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue