diff --git a/xrtl/examples/triangle_example.cc b/xrtl/examples/triangle_example.cc index 3736b3f..bc3f518 100644 --- a/xrtl/examples/triangle_example.cc +++ b/xrtl/examples/triangle_example.cc @@ -32,7 +32,7 @@ using gfx::Context; using gfx::ContextFactory; using gfx::Image; using gfx::ImageView; -using gfx::MemoryPool; +using gfx::MemoryHeap; using gfx::RenderPass; using gfx::RenderPipeline; using gfx::RenderState; @@ -121,12 +121,12 @@ class TriangleExample : private Control::Listener { return false; } - // Allocate a memory pool to allocate buffers and textures. - memory_pool_ = context_->CreateMemoryPool( + // Allocate a memory heap to allocate buffers and textures. + memory_heap_ = context_->CreateMemoryHeap( gfx::MemoryType::kHostVisible | gfx::MemoryType::kHostCoherent, - 1 * 1024 * 1024); - if (!memory_pool_) { - LOG(ERROR) << "Unable to create memory pool"; + 16 * 1024 * 1024); + if (!memory_heap_) { + LOG(ERROR) << "Unable to create memory heap"; return false; } @@ -148,10 +148,10 @@ class TriangleExample : private Control::Listener { }; // Allocate a buffer for the geometry. - auto allocation_result = memory_pool_->AllocateBuffer( + auto allocation_result = memory_heap_->AllocateBuffer( sizeof(kVertexData), Buffer::Usage::kVertexBuffer, &triangle_buffer_); switch (allocation_result) { - case MemoryPool::AllocationResult::kSuccess: + case MemoryHeap::AllocationResult::kSuccess: break; default: LOG(ERROR) << "Failed to allocate geometry buffer"; @@ -187,13 +187,12 @@ class TriangleExample : private Control::Listener { create_params.format = gfx::PixelFormats::kR8G8B8A8UNorm; create_params.tiling_mode = Image::TilingMode::kLinear; create_params.size = {kWidth, kHeight}; - create_params.usage_mask = Image::Usage::kSampled; create_params.initial_layout = Image::Layout::kPreinitialized; - auto allocation_result = - memory_pool_->AllocateImage(create_params, &grid_image_); + auto allocation_result = memory_heap_->AllocateImage( + create_params, Image::Usage::kSampled, &grid_image_); switch (allocation_result) { - case MemoryPool::AllocationResult::kSuccess: + case MemoryHeap::AllocationResult::kSuccess: break; default: LOG(ERROR) << "Failed to allocate texture image"; @@ -359,10 +358,10 @@ void main() { } // Allocate the uniform buffer. - auto allocation_result = memory_pool_->AllocateBuffer( + auto allocation_result = memory_heap_->AllocateBuffer( sizeof(UniformBlock), Buffer::Usage::kUniformBuffer, &uniform_buffer_); switch (allocation_result) { - case MemoryPool::AllocationResult::kSuccess: + case MemoryHeap::AllocationResult::kSuccess: break; default: LOG(ERROR) << "Failed to allocate uniform buffer"; @@ -527,7 +526,7 @@ void main() { resource_set_.reset(); render_pipeline_.reset(); render_pass_.reset(); - memory_pool_.reset(); + memory_heap_.reset(); swap_chain_.reset(); context_.reset(); } @@ -565,7 +564,7 @@ void main() { ref_ptr render_pipeline_; ref_ptr resource_set_; - ref_ptr memory_pool_; + ref_ptr memory_heap_; ref_ptr triangle_buffer_; ref_ptr grid_image_; ref_ptr grid_image_view_; diff --git a/xrtl/gfx/BUILD b/xrtl/gfx/BUILD index f712bb7..22293cb 100644 --- a/xrtl/gfx/BUILD +++ b/xrtl/gfx/BUILD @@ -82,7 +82,7 @@ cc_library( ":device", ":framebuffer", ":image_view", - ":memory_pool", + ":memory_heap", ":pipeline", ":pipeline_layout", ":pixel_format", @@ -199,8 +199,8 @@ cc_library( ) cc_library( - name = "memory_pool", - hdrs = ["memory_pool.h"], + name = "memory_heap", + hdrs = ["memory_heap.h"], deps = [ ":buffer", ":image", diff --git a/xrtl/gfx/context.h b/xrtl/gfx/context.h index 2f69a35..dcce217 100644 --- a/xrtl/gfx/context.h +++ b/xrtl/gfx/context.h @@ -27,7 +27,7 @@ #include "xrtl/gfx/device.h" #include "xrtl/gfx/framebuffer.h" #include "xrtl/gfx/image_view.h" -#include "xrtl/gfx/memory_pool.h" +#include "xrtl/gfx/memory_heap.h" #include "xrtl/gfx/pipeline.h" #include "xrtl/gfx/pipeline_layout.h" #include "xrtl/gfx/pixel_format.h" @@ -146,20 +146,20 @@ class Context : public RefObject { ref_ptr control, SwapChain::PresentMode present_mode, int image_count, ArrayView pixel_formats) = 0; - // Creates a new resource memory pool. - // The pool can be used to create images and buffers of the given memory + // Creates a new resource memory heap. + // The heap can be used to create images and buffers of the given memory // type. // - // The memory pool will request hardware resources in the provided chunk size - // and then dole out images and buffers from those chunks. Chunk sizes + // The memory heap will request hardware resources in the provided heap size + // and then dole out images and buffers from that allocation. Heap sizes // should be sufficiently large to prevent frequent exhaustion but not so // large as to potentially run out of device memory. 64-128MB is often a good - // size to start with. The provided chunk size may be rounded up to alignment + // size to start with. The provided heap size may be rounded up to alignment // restrictions of the device. // // Returns nullptr if the memory type mask is invalid. - virtual ref_ptr CreateMemoryPool(MemoryType memory_type_mask, - size_t chunk_size) = 0; + virtual ref_ptr CreateMemoryHeap(MemoryType memory_type_mask, + size_t heap_size) = 0; // Creates a new image sampler. virtual ref_ptr CreateSampler(Sampler::Params params) = 0; diff --git a/xrtl/gfx/es3/BUILD b/xrtl/gfx/es3/BUILD index a29de84..68a9606 100644 --- a/xrtl/gfx/es3/BUILD +++ b/xrtl/gfx/es3/BUILD @@ -13,6 +13,7 @@ cc_library( ":es3_common", ":es3_platform_context", "//xrtl/gfx:buffer", + "//xrtl/gfx:memory_heap", ], ) @@ -70,7 +71,7 @@ cc_library( ":es3_common", ":es3_framebuffer", ":es3_image_view", - ":es3_memory_pool", + ":es3_memory_heap", ":es3_pipeline", ":es3_pipeline_layout", ":es3_platform_context", @@ -149,6 +150,7 @@ cc_library( ":es3_pixel_format", ":es3_platform_context", "//xrtl/gfx:image", + "//xrtl/gfx:memory_heap", ], ) @@ -162,17 +164,18 @@ cc_library( ) cc_library( - name = "es3_memory_pool", - srcs = ["es3_memory_pool.cc"], - hdrs = ["es3_memory_pool.h"], + name = "es3_memory_heap", + srcs = ["es3_memory_heap.cc"], + hdrs = ["es3_memory_heap.h"], deps = [ ":es3_buffer", ":es3_common", ":es3_image", ":es3_pixel_format", ":es3_platform_context", + "//xrtl/base:math", "//xrtl/base:tracing", - "//xrtl/gfx:memory_pool", + "//xrtl/gfx:memory_heap", ], ) @@ -379,7 +382,7 @@ cc_library( "//xrtl/base/threading:event", "//xrtl/base/threading:semaphore", "//xrtl/base/threading:thread", - "//xrtl/gfx:memory_pool", + "//xrtl/gfx:memory_heap", "//xrtl/gfx:swap_chain", "//xrtl/ui:control", ], diff --git a/xrtl/gfx/es3/es3_buffer.cc b/xrtl/gfx/es3/es3_buffer.cc index fee8641..39cec5a 100644 --- a/xrtl/gfx/es3/es3_buffer.cc +++ b/xrtl/gfx/es3/es3_buffer.cc @@ -16,16 +16,18 @@ #include +#include "xrtl/gfx/memory_heap.h" + namespace xrtl { namespace gfx { namespace es3 { ES3Buffer::ES3Buffer(ref_ptr platform_context, - MemoryType memory_type_mask, size_t allocation_size, + ref_ptr memory_heap, size_t allocation_size, Usage usage_mask) : Buffer(allocation_size, usage_mask), platform_context_(std::move(platform_context)), - memory_type_mask_(memory_type_mask) { + memory_heap_(std::move(memory_heap)) { auto context_lock = ES3PlatformContext::LockTransientContext(platform_context_); @@ -65,6 +67,10 @@ ES3Buffer::~ES3Buffer() { glDeleteBuffers(1, &buffer_id_); } +ref_ptr ES3Buffer::memory_heap() const { return memory_heap_; } + +void ES3Buffer::Release() { memory_heap_->ReleaseBuffer(this); } + bool ES3Buffer::ReadData(size_t source_offset, void* data, size_t data_length) { DCHECK_LE(source_offset + data_length, allocation_size()); // TODO(benvanik): buffer. @@ -91,7 +97,8 @@ bool ES3Buffer::MapMemory(MemoryAccess memory_access, size_t* byte_offset, *out_data = nullptr; // Must be mappable. - bool is_mappable = any(memory_type_mask_ & MemoryType::kHostVisible); + bool is_mappable = + any(memory_heap_->memory_type_mask() & MemoryType::kHostVisible); DCHECK(is_mappable); if (!is_mappable) { LOG(ERROR) << "Attempting to map a non-host-visible memory buffer"; @@ -123,7 +130,7 @@ bool ES3Buffer::MapMemory(MemoryAccess memory_access, size_t* byte_offset, if (access & GL_MAP_WRITE_BIT) { // Non-host-coherent memory requires explicit flushes. - if (!any(memory_type_mask_ & MemoryType::kHostCoherent)) { + if (!any(memory_heap_->memory_type_mask() & MemoryType::kHostCoherent)) { access |= GL_MAP_UNSYNCHRONIZED_BIT; access |= GL_MAP_FLUSH_EXPLICIT_BIT; } @@ -164,7 +171,7 @@ void ES3Buffer::InvalidateMappedMemory(size_t byte_offset, size_t byte_length) { void ES3Buffer::FlushMappedMemory(size_t byte_offset, size_t byte_length) { // Flushes are ignored with kHostCoherent memory. - if (any(memory_type_mask_ & MemoryType::kHostCoherent)) { + if (any(memory_heap_->memory_type_mask() & MemoryType::kHostCoherent)) { return; } diff --git a/xrtl/gfx/es3/es3_buffer.h b/xrtl/gfx/es3/es3_buffer.h index f9cfe67..47ca527 100644 --- a/xrtl/gfx/es3/es3_buffer.h +++ b/xrtl/gfx/es3/es3_buffer.h @@ -15,6 +15,8 @@ #ifndef XRTL_GFX_ES3_ES3_BUFFER_H_ #define XRTL_GFX_ES3_ES3_BUFFER_H_ +#include + #include "xrtl/gfx/buffer.h" #include "xrtl/gfx/es3/es3_common.h" #include "xrtl/gfx/es3/es3_platform_context.h" @@ -26,10 +28,12 @@ namespace es3 { class ES3Buffer : public Buffer { public: ES3Buffer(ref_ptr platform_context, - MemoryType memory_type_mask, size_t allocation_size, + ref_ptr memory_heap, size_t allocation_size, Usage usage_mask); ~ES3Buffer() override; + ref_ptr memory_heap() const override; + GLenum target() const { return target_; } GLuint buffer_id() const { return buffer_id_; } @@ -42,13 +46,15 @@ class ES3Buffer : public Buffer { void FlushMappedMemory(size_t byte_offset, size_t byte_length) override; + void Release() override; + private: bool MapMemory(MemoryAccess memory_access, size_t* byte_offset, size_t* byte_length, void** out_data) override; void UnmapMemory(size_t byte_offset, size_t byte_length, void* data) override; ref_ptr platform_context_; - MemoryType memory_type_mask_ = MemoryType::kDeviceLocal; + ref_ptr memory_heap_; GLenum target_ = GL_COPY_READ_BUFFER; GLuint buffer_id_ = 0; diff --git a/xrtl/gfx/es3/es3_context.cc b/xrtl/gfx/es3/es3_context.cc index 1d2b8dc..947de4a 100644 --- a/xrtl/gfx/es3/es3_context.cc +++ b/xrtl/gfx/es3/es3_context.cc @@ -19,7 +19,7 @@ #include "xrtl/gfx/es3/es3_command_fence.h" #include "xrtl/gfx/es3/es3_framebuffer.h" #include "xrtl/gfx/es3/es3_image_view.h" -#include "xrtl/gfx/es3/es3_memory_pool.h" +#include "xrtl/gfx/es3/es3_memory_heap.h" #include "xrtl/gfx/es3/es3_pipeline.h" #include "xrtl/gfx/es3/es3_pipeline_layout.h" #include "xrtl/gfx/es3/es3_program.h" @@ -215,18 +215,19 @@ ref_ptr ES3Context::CreateSwapChain( int image_count, ArrayView pixel_formats) { // Shared memory pool for all frame buffer images. // TODO(benvanik): pool across swap chains. - auto memory_pool = CreateMemoryPool(MemoryType::kDeviceLocal, 0); - DCHECK(memory_pool); + auto memory_heap = + CreateMemoryHeap(MemoryType::kDeviceLocal, 64 * 1024 * 1024); + DCHECK(memory_heap); return ES3SwapChain::Create(platform_context_, presentation_queue_.get(), - std::move(memory_pool), std::move(control), + std::move(memory_heap), std::move(control), present_mode, image_count, pixel_formats); } -ref_ptr ES3Context::CreateMemoryPool(MemoryType memory_type_mask, - size_t chunk_size) { - return make_ref(platform_context_, memory_type_mask, - chunk_size); +ref_ptr ES3Context::CreateMemoryHeap(MemoryType memory_type_mask, + size_t heap_size) { + return make_ref(platform_context_, memory_type_mask, + heap_size); } ref_ptr ES3Context::CreateSampler(Sampler::Params params) { diff --git a/xrtl/gfx/es3/es3_context.h b/xrtl/gfx/es3/es3_context.h index b5ca778..4c96ba6 100644 --- a/xrtl/gfx/es3/es3_context.h +++ b/xrtl/gfx/es3/es3_context.h @@ -83,8 +83,8 @@ class ES3Context : public Context { ref_ptr control, SwapChain::PresentMode present_mode, int image_count, ArrayView pixel_formats) override; - ref_ptr CreateMemoryPool(MemoryType memory_type_mask, - size_t chunk_size) override; + ref_ptr CreateMemoryHeap(MemoryType memory_type_mask, + size_t heap_size) override; ref_ptr CreateSampler(Sampler::Params params) override; diff --git a/xrtl/gfx/es3/es3_image.cc b/xrtl/gfx/es3/es3_image.cc index 88e5013..5833f87 100644 --- a/xrtl/gfx/es3/es3_image.cc +++ b/xrtl/gfx/es3/es3_image.cc @@ -17,6 +17,7 @@ #include #include "xrtl/gfx/es3/es3_image_view.h" +#include "xrtl/gfx/memory_heap.h" namespace xrtl { namespace gfx { @@ -47,10 +48,12 @@ size_t ES3Image::ComputeAllocationSize( } ES3Image::ES3Image(ref_ptr platform_context, + ref_ptr memory_heap, ES3TextureParams texture_params, size_t allocation_size, CreateParams create_params) : Image(allocation_size, create_params), platform_context_(std::move(platform_context)), + memory_heap_(std::move(memory_heap)), texture_params_(texture_params) { auto context_lock = ES3PlatformContext::LockTransientContext(platform_context_); @@ -104,6 +107,10 @@ ES3Image::~ES3Image() { glDeleteTextures(1, &texture_id_); } +ref_ptr ES3Image::memory_heap() const { return memory_heap_; } + +void ES3Image::Release() { memory_heap_->ReleaseImage(this); } + ref_ptr ES3Image::CreateView() { return CreateView(create_params_.type, create_params_.format, entire_range()); } diff --git a/xrtl/gfx/es3/es3_image.h b/xrtl/gfx/es3/es3_image.h index eeb3c46..7d93d2e 100644 --- a/xrtl/gfx/es3/es3_image.h +++ b/xrtl/gfx/es3/es3_image.h @@ -15,6 +15,8 @@ #ifndef XRTL_GFX_ES3_ES3_IMAGE_H_ #define XRTL_GFX_ES3_ES3_IMAGE_H_ +#include + #include "xrtl/gfx/es3/es3_common.h" #include "xrtl/gfx/es3/es3_pixel_format.h" #include "xrtl/gfx/es3/es3_platform_context.h" @@ -29,10 +31,12 @@ class ES3Image : public Image { static size_t ComputeAllocationSize(const Image::CreateParams& create_params); ES3Image(ref_ptr platform_context, - ES3TextureParams texture_params, size_t allocation_size, - CreateParams create_params); + ref_ptr memory_heap, ES3TextureParams texture_params, + size_t allocation_size, CreateParams create_params); ~ES3Image() override; + ref_ptr memory_heap() const override; + GLenum target() const { return target_; } GLuint texture_id() const { return texture_id_; } @@ -47,9 +51,13 @@ class ES3Image : public Image { bool WriteData(LayerRange target_range, const void* data, size_t data_length) override; + void Release() override; + private: ref_ptr platform_context_; + ref_ptr memory_heap_; ES3TextureParams texture_params_; + GLenum target_ = GL_TEXTURE_2D; GLuint texture_id_ = 0; }; diff --git a/xrtl/gfx/es3/es3_memory_heap.cc b/xrtl/gfx/es3/es3_memory_heap.cc new file mode 100644 index 0000000..f2bb496 --- /dev/null +++ b/xrtl/gfx/es3/es3_memory_heap.cc @@ -0,0 +1,119 @@ +// Copyright 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xrtl/gfx/es3/es3_memory_heap.h" + +#include + +#include "xrtl/base/math.h" +#include "xrtl/base/tracing.h" +#include "xrtl/gfx/es3/es3_buffer.h" +#include "xrtl/gfx/es3/es3_image.h" +#include "xrtl/gfx/es3/es3_pixel_format.h" + +namespace xrtl { +namespace gfx { +namespace es3 { + +ES3MemoryHeap::ES3MemoryHeap(ref_ptr platform_context, + MemoryType memory_type_mask, size_t heap_size) + : MemoryHeap(memory_type_mask, heap_size), + platform_context_(std::move(platform_context)) { + // TODO(benvanik): query? or always leave like this? (common in vk). + allocation_alignment_ = 128; +} + +ES3MemoryHeap::~ES3MemoryHeap() { + std::lock_guard lock_guard(mutex_); + DCHECK_EQ(used_size_, 0); +} + +size_t ES3MemoryHeap::used_size() { + std::lock_guard lock_guard(mutex_); + return used_size_; +} + +MemoryHeap::AllocationResult ES3MemoryHeap::AllocateBuffer( + size_t size, Buffer::Usage usage_mask, ref_ptr* out_buffer) { + WTF_SCOPE0("ES3MemoryHeap#AllocateBuffer"); + + // Ensure we can allocate the requested amount. + size_t allocation_size = math::RoundToAlignment(size, allocation_alignment_); + { + std::lock_guard lock_guard(mutex_); + if (used_size_ + allocation_size > heap_size_) { + return AllocationResult::kOutOfMemory; + } + used_size_ += allocation_size; + } + + // Create the buffer and allocate underlying storage. + *out_buffer = + make_ref(platform_context_, ref_ptr(this), + allocation_size, usage_mask); + + return AllocationResult::kSuccess; +} + +void ES3MemoryHeap::ReleaseBuffer(Buffer* buffer) { + std::lock_guard lock_guard(mutex_); + used_size_ -= buffer->allocation_size(); +} + +MemoryHeap::AllocationResult ES3MemoryHeap::AllocateImage( + Image::CreateParams create_params, Image::Usage usage_mask, + ref_ptr* out_image) { + WTF_SCOPE0("ES3MemoryHeap#AllocateImage"); + + // Pick a pixel format for the texture. + ES3TextureParams texture_params; + if (!ConvertPixelFormatToTextureParams(create_params.format, + &texture_params)) { + LOG(ERROR) << "Unsupported GL pixel format"; + return AllocationResult::kUnsupported; + } + + // TODO(benvanik): validate limits. + + // Compute allocated data size (once uploaded). This is how much memory the + // image will consume on the GPU (at least). + size_t allocation_size = ES3Image::ComputeAllocationSize(create_params); + + // Ensure we can allocate the requested amount. + allocation_size = + math::RoundToAlignment(allocation_size, allocation_alignment_); + { + std::lock_guard lock_guard(mutex_); + if (used_size_ + allocation_size > heap_size_) { + return AllocationResult::kOutOfMemory; + } + used_size_ += allocation_size; + } + + // Create the image and allocate underlying texture. + *out_image = + make_ref(platform_context_, ref_ptr(this), + texture_params, allocation_size, create_params); + + return AllocationResult::kSuccess; +} + +void ES3MemoryHeap::ReleaseImage(Image* image) { + std::lock_guard lock_guard(mutex_); + used_size_ -= image->allocation_size(); +} + +} // namespace es3 +} // namespace gfx +} // namespace xrtl diff --git a/xrtl/gfx/es3/es3_memory_pool.h b/xrtl/gfx/es3/es3_memory_heap.h similarity index 61% rename from xrtl/gfx/es3/es3_memory_pool.h rename to xrtl/gfx/es3/es3_memory_heap.h index 0fa6970..2a66380 100644 --- a/xrtl/gfx/es3/es3_memory_pool.h +++ b/xrtl/gfx/es3/es3_memory_heap.h @@ -12,37 +12,49 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef XRTL_GFX_ES3_ES3_MEMORY_POOL_H_ -#define XRTL_GFX_ES3_ES3_MEMORY_POOL_H_ +#ifndef XRTL_GFX_ES3_ES3_MEMORY_HEAP_H_ +#define XRTL_GFX_ES3_ES3_MEMORY_HEAP_H_ + +#include #include "xrtl/gfx/es3/es3_common.h" #include "xrtl/gfx/es3/es3_platform_context.h" -#include "xrtl/gfx/memory_pool.h" +#include "xrtl/gfx/memory_heap.h" namespace xrtl { namespace gfx { namespace es3 { -class ES3MemoryPool : public MemoryPool { +class ES3MemoryHeap : public MemoryHeap { public: - ES3MemoryPool(ref_ptr platform_context, - MemoryType memory_type_mask, size_t chunk_size); - ~ES3MemoryPool() override; + ES3MemoryHeap(ref_ptr platform_context, + MemoryType memory_type_mask, size_t heap_size); + ~ES3MemoryHeap() override; - void Reclaim() override; + size_t allocation_alignment() const override { return allocation_alignment_; } + size_t used_size() override; AllocationResult AllocateBuffer(size_t size, Buffer::Usage usage_mask, ref_ptr* out_buffer) override; AllocationResult AllocateImage(Image::CreateParams create_params, + Image::Usage usage_mask, ref_ptr* out_image) override; private: + void ReleaseBuffer(Buffer* buffer) override; + void ReleaseImage(Image* image) override; + ref_ptr platform_context_; + + size_t allocation_alignment_ = 0; + + std::mutex mutex_; + size_t used_size_ = 0; }; } // namespace es3 } // namespace gfx } // namespace xrtl -#endif // XRTL_GFX_ES3_ES3_MEMORY_POOL_H_ +#endif // XRTL_GFX_ES3_ES3_MEMORY_HEAP_H_ diff --git a/xrtl/gfx/es3/es3_memory_pool.cc b/xrtl/gfx/es3/es3_memory_pool.cc deleted file mode 100644 index 819957e..0000000 --- a/xrtl/gfx/es3/es3_memory_pool.cc +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2017 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "xrtl/gfx/es3/es3_memory_pool.h" - -#include - -#include "xrtl/base/tracing.h" -#include "xrtl/gfx/es3/es3_buffer.h" -#include "xrtl/gfx/es3/es3_image.h" -#include "xrtl/gfx/es3/es3_pixel_format.h" - -namespace xrtl { -namespace gfx { -namespace es3 { - -ES3MemoryPool::ES3MemoryPool(ref_ptr platform_context, - MemoryType memory_type_mask, size_t chunk_size) - : MemoryPool(memory_type_mask, chunk_size), - platform_context_(std::move(platform_context)) {} - -ES3MemoryPool::~ES3MemoryPool() = default; - -void ES3MemoryPool::Reclaim() { - WTF_SCOPE0("ES3MemoryPool#Reclaim"); - - // TODO(benvanik): reclaim unused resources. -} - -MemoryPool::AllocationResult ES3MemoryPool::AllocateBuffer( - size_t size, Buffer::Usage usage_mask, ref_ptr* out_buffer) { - WTF_SCOPE0("ES3MemoryPool#AllocateBuffer"); - - // Create the buffer and allocate underlying storage. - *out_buffer = make_ref(platform_context_, memory_type_mask_, size, - usage_mask); - - return AllocationResult::kSuccess; -} - -MemoryPool::AllocationResult ES3MemoryPool::AllocateImage( - Image::CreateParams create_params, ref_ptr* out_image) { - WTF_SCOPE0("ES3MemoryPool#AllocateImage"); - - // Pick a pixel format for the texture. - ES3TextureParams texture_params; - if (!ConvertPixelFormatToTextureParams(create_params.format, - &texture_params)) { - LOG(ERROR) << "Unsupported GL pixel format"; - return AllocationResult::kUnsupported; - } - - // TODO(benvanik): validate limits. - - // Compute allocated data size (once uploaded). This is how much memory the - // image will consume on the GPU (at least). - size_t allocation_size = ES3Image::ComputeAllocationSize(create_params); - - // Create the image and allocate underlying texture. - *out_image = make_ref(platform_context_, texture_params, - allocation_size, create_params); - - return AllocationResult::kSuccess; -} - -} // namespace es3 -} // namespace gfx -} // namespace xrtl diff --git a/xrtl/gfx/es3/es3_swap_chain.cc b/xrtl/gfx/es3/es3_swap_chain.cc index df92ca8..3a6b4f6 100644 --- a/xrtl/gfx/es3/es3_swap_chain.cc +++ b/xrtl/gfx/es3/es3_swap_chain.cc @@ -29,7 +29,7 @@ namespace es3 { // TODO(benvanik): remove the need for this when we have multiple impls. ref_ptr ES3SwapChain::Create( ref_ptr shared_platform_context, - ES3Queue* present_queue, ref_ptr memory_pool, + ES3Queue* present_queue, ref_ptr memory_heap, ref_ptr control, PresentMode present_mode, int image_count, ArrayView pixel_formats) { WTF_SCOPE0("ES3SwapChain#Create"); @@ -46,7 +46,7 @@ ref_ptr ES3SwapChain::Create( } auto swap_chain = make_ref( - present_queue, memory_pool, control, platform_context, present_mode, + present_queue, memory_heap, control, platform_context, present_mode, image_count, pixel_formats); if (!swap_chain->Initialize()) { return nullptr; @@ -55,13 +55,13 @@ ref_ptr ES3SwapChain::Create( } ES3PlatformSwapChain::ES3PlatformSwapChain( - ES3Queue* present_queue, ref_ptr memory_pool, + ES3Queue* present_queue, ref_ptr memory_heap, ref_ptr control, ref_ptr platform_context, PresentMode present_mode, int image_count, ArrayView pixel_formats) : ES3SwapChain(present_mode, image_count, pixel_formats), present_queue_(present_queue), - memory_pool_(std::move(memory_pool)), + memory_heap_(std::move(memory_heap)), control_(std::move(control)), platform_context_(std::move(platform_context)) {} @@ -81,9 +81,6 @@ bool ES3PlatformSwapChain::Initialize() { image_create_params_.format = available_pixel_formats_[0]; image_create_params_.size = Size3D(size_); - image_create_params_.usage_mask = - Image::Usage::kTransferSource | Image::Usage::kSampled | - Image::Usage::kColorAttachment | Image::Usage::kInputAttachment; // Allocate framebuffers we'll use for copying. framebuffers_.resize(image_count()); @@ -161,16 +158,20 @@ SwapChain::ResizeResult ES3PlatformSwapChain::ResizeWithContext( size_ = platform_context_->QuerySize(); image_create_params_.size = Size3D(size_); + auto usage_mask = Image::Usage::kTransferSource | Image::Usage::kSampled | + Image::Usage::kColorAttachment | + Image::Usage::kInputAttachment; + // Resize all images by recreating them. for (int i = 0; i < image_views_.size(); ++i) { image_views_[i].reset(); } - memory_pool_->Reclaim(); for (int i = 0; i < image_views_.size(); ++i) { // Allocate image. ref_ptr image; - auto result = memory_pool_->AllocateImage(image_create_params_, &image); - DCHECK(result == MemoryPool::AllocationResult::kSuccess); + auto result = + memory_heap_->AllocateImage(image_create_params_, usage_mask, &image); + DCHECK(result == MemoryHeap::AllocationResult::kSuccess); if (!image) { return ResizeResult::kOutOfMemory; } diff --git a/xrtl/gfx/es3/es3_swap_chain.h b/xrtl/gfx/es3/es3_swap_chain.h index b7cc71e..6ccdb3d 100644 --- a/xrtl/gfx/es3/es3_swap_chain.h +++ b/xrtl/gfx/es3/es3_swap_chain.h @@ -23,7 +23,7 @@ #include "xrtl/gfx/es3/es3_common.h" #include "xrtl/gfx/es3/es3_platform_context.h" #include "xrtl/gfx/es3/es3_queue.h" -#include "xrtl/gfx/memory_pool.h" +#include "xrtl/gfx/memory_heap.h" #include "xrtl/gfx/swap_chain.h" #include "xrtl/ui/control.h" @@ -35,7 +35,7 @@ class ES3SwapChain : public SwapChain { public: static ref_ptr Create( ref_ptr shared_platform_context, - ES3Queue* present_queue, ref_ptr memory_pool, + ES3Queue* present_queue, ref_ptr memory_heap, ref_ptr control, PresentMode present_mode, int image_count, ArrayView pixel_formats); @@ -54,7 +54,7 @@ class ES3SwapChain : public SwapChain { class ES3PlatformSwapChain : public ES3SwapChain { public: - ES3PlatformSwapChain(ES3Queue* present_queue, ref_ptr memory_pool, + ES3PlatformSwapChain(ES3Queue* present_queue, ref_ptr memory_heap, ref_ptr control, ref_ptr platform_context, PresentMode present_mode, int image_count, @@ -90,7 +90,7 @@ class ES3PlatformSwapChain : public ES3SwapChain { void MarkPresentComplete(int image_index); ES3Queue* present_queue_; - ref_ptr memory_pool_; + ref_ptr memory_heap_; ref_ptr control_; ref_ptr platform_context_; diff --git a/xrtl/gfx/image.h b/xrtl/gfx/image.h index 22500ae..ffd90b2 100644 --- a/xrtl/gfx/image.h +++ b/xrtl/gfx/image.h @@ -139,12 +139,14 @@ class Image : public Resource { Size3D size; int mip_level_count = 1; int array_layer_count = 1; - Usage usage_mask = Usage::kNone; Layout initial_layout = Layout::kUndefined; }; ~Image() override = default; + // Bitmask describing how the image is to be used. + Usage usage_mask() const { return usage_mask_; } + // Image type the view is representing. Image::Type type() const { return create_params_.type; } // Format of the pixel data. @@ -160,8 +162,6 @@ class Image : public Resource { int mip_level_count() const { return create_params_.mip_level_count; } // Total number of layers in the array, if the image is an array type. int array_layer_count() const { return create_params_.array_layer_count; } - // Bitmask describing how the image is to be used. - Usage usage_mask() const { return create_params_.usage_mask; } // Returns a layer range encompassing the entire image. LayerRange entire_range() const { @@ -195,6 +195,7 @@ class Image : public Resource { Image(size_t allocation_size, CreateParams create_params) : Resource(allocation_size), create_params_(create_params) {} + Usage usage_mask_ = Usage::kNone; CreateParams create_params_; }; diff --git a/xrtl/gfx/memory_pool.h b/xrtl/gfx/memory_heap.h similarity index 53% rename from xrtl/gfx/memory_pool.h rename to xrtl/gfx/memory_heap.h index eeb5c22..b6b4eb9 100644 --- a/xrtl/gfx/memory_pool.h +++ b/xrtl/gfx/memory_heap.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef XRTL_GFX_MEMORY_POOL_H_ -#define XRTL_GFX_MEMORY_POOL_H_ +#ifndef XRTL_GFX_MEMORY_HEAP_H_ +#define XRTL_GFX_MEMORY_HEAP_H_ #include "xrtl/base/macros.h" #include "xrtl/base/ref_ptr.h" @@ -24,33 +24,54 @@ namespace xrtl { namespace gfx { -// Memory pool for images and buffers. +// Memory heap for images and buffers. // Allocations that require reaching into the device to allocate memory are // expensive and there may be limits on the number of allocations that can be -// performed by a process (sometimes on the order of low hundreds). MemoryPools +// performed by a process (sometimes on the order of low hundreds). MemoryHeaps // work around this by allocating large chunks of memory and then handing out // slices of that when requested as buffers or images. // -// MemoryPools and the Resources allocated from them must be kept alive together +// Note that its possible for internal fragmentation to decrease the total bytes +// that can be allocated from the heap. Ensure that resources allocated from the +// heap are of consistent sizes or have similar lifetimes to ensure +// fragmentation is kept to a minimum. +// +// Resources allocated from the heap may reserve more memory than requested +// if the heap has allocation alignment restrictions. Because of this +// Resource::allocation_size may differ from the requested size and total heap +// usage may exceed expectations. The native heap alignment can be queried with +// the allocation_alignment member, noting that it may differ among heap types. +// +// MemoryHeaps and the Resources allocated from them must be kept alive together // and the ref_ptr system should take care of this. This means that callers must // be careful not to allow Resources to hang around longer than required as it // may keep large chunks of memory reserved by a no-longer-used allocator. -class MemoryPool : public RefObject { +// +// MemoryHeaps are thread-safe and allocations may occur from multiple threads +// simultaneously. Note that because of races and fragmentation used_size() +// must not be used to make assumptions about whether an allocation will succeed +// and callers must always check the AllocationResult. +// +// MemoryHeap roughly maps to the follow backend concepts: +// - D3D12: ID3D12Heap +// - Metal: MTLHeap +// - Vulkan: VkMemoryHeap +class MemoryHeap : public RefObject { public: - virtual ~MemoryPool() = default; + virtual ~MemoryHeap() = default; // TODO(benvanik): expose stats (total chunk size, chunks allocated, etc). // TODO(benvanik): expose properties to query granularities for mapping/etc. // A bitmask of MemoryType values describing the behavior of this memory. MemoryType memory_type_mask() const { return memory_type_mask_; } - // Size of each chunk the allocator uses for backing memory in bytes. - size_t chunk_size() const { return chunk_size_; } - - // Attempts to reclaim unused chunks from the system. - // Chunks will not be reclaimed so long as any resources allocated within it - // are still alive. - virtual void Reclaim() = 0; + // Byte alignment of resources allocated from the heap. Allocations will + // start on and extend to addresses aligned with this value. + virtual size_t allocation_alignment() const = 0; + // Total size of the heap in bytes. + size_t heap_size() const { return heap_size_; } + // Total bytes currently allocated from the heap in bytes. + virtual size_t used_size() = 0; // Defines the result of an allocation request. enum class AllocationResult { @@ -63,38 +84,40 @@ class MemoryPool : public RefObject { kUnsupported, // One or more device limits were exceeded by the specified parameters. kLimitsExceeded, - // Device memory allocation would have been over the size of a single chunk. - // Grow the chunk size or use a different allocator. - kOverChunkSize, // The memory pool servicing the memory type is exhausted. kOutOfMemory, }; - // Allocates a buffer from the allocator memory pool. + // Allocates a buffer from the heap. // Returns the buffer via out_buffer if it could be allocated successfully. - // The allocation may fail if the buffer is larger than the allocator chunk - // size, memory is not available, or the buffer parameters are invalid or + // The allocation may fail if the buffer is larger than the maximum available + // contigugous free heap memory block, or the buffer parameters are invalid or // unsupported. virtual AllocationResult AllocateBuffer(size_t size, Buffer::Usage usage_mask, ref_ptr* out_buffer) = 0; // Allocates an image from the allocator memory pool. // Returns the image via out_image if it could be allocated successfully. - // The allocation may fail if the image is larger than the allocator chunk - // size, memory is not available, or the image parameters are invalid or + // The allocation may fail if the image is larger than the maximum available + // contigugous free heap memory block, or the image parameters are invalid or // unsupported. virtual AllocationResult AllocateImage(Image::CreateParams create_params, + Image::Usage usage_mask, ref_ptr* out_image) = 0; + // TODO(benvanik): find a good way to hide these to all subclasses. + virtual void ReleaseBuffer(Buffer* buffer) = 0; + virtual void ReleaseImage(Image* image) = 0; + protected: - MemoryPool(MemoryType memory_type_mask, size_t chunk_size) - : memory_type_mask_(memory_type_mask), chunk_size_(chunk_size) {} + MemoryHeap(MemoryType memory_type_mask, size_t heap_size) + : memory_type_mask_(memory_type_mask), heap_size_(heap_size) {} MemoryType memory_type_mask_; - size_t chunk_size_ = 0; + size_t heap_size_ = 0; }; } // namespace gfx } // namespace xrtl -#endif // XRTL_GFX_MEMORY_POOL_H_ +#endif // XRTL_GFX_MEMORY_HEAP_H_ diff --git a/xrtl/gfx/resource.h b/xrtl/gfx/resource.h index 18dcb13..9eab26f 100644 --- a/xrtl/gfx/resource.h +++ b/xrtl/gfx/resource.h @@ -22,20 +22,31 @@ namespace xrtl { namespace gfx { +class MemoryHeap; + // Base type for allocated resources. class Resource : public RefObject { public: virtual ~Resource() = default; + // The memory heap this resource was allocated from. + // The heap will be kept alive so long as this resource remains allocated. + virtual ref_ptr memory_heap() const = 0; + // Size of the resource memory allocation in bytes. // This may be rounded up from the originally requested size or the ideal // size for the resource based on device restrictions. size_t allocation_size() const { return allocation_size_; } + static void Delete(Resource* resource) { resource->Release(); } + protected: explicit Resource(size_t allocation_size) : allocation_size_(allocation_size) {} + // Releases the resource back to its heap. + virtual void Release() = 0; + size_t allocation_size_ = 0; }; diff --git a/xrtl/ui/imgui_overlay.cc b/xrtl/ui/imgui_overlay.cc index aa759d2..ec0e460 100644 --- a/xrtl/ui/imgui_overlay.cc +++ b/xrtl/ui/imgui_overlay.cc @@ -32,7 +32,7 @@ namespace { using gfx::Buffer; using gfx::Image; -using gfx::MemoryPool; +using gfx::MemoryHeap; using gfx::PipelineLayout; using gfx::RenderPass; using gfx::RenderPipeline; @@ -91,20 +91,20 @@ ImGuiOverlay::~ImGuiOverlay() { linear_sampler_.reset(); nearest_sampler_.reset(); - memory_pool_.reset(); + memory_heap_.reset(); context_.reset(); } bool ImGuiOverlay::Initialize(ref_ptr context) { context_ = std::move(context); - // Allocate a memory pool to allocate buffers and textures. + // Allocate a memory heap to allocate buffers and textures. // TODO(benvanik): accept one to share. - memory_pool_ = context_->CreateMemoryPool( + memory_heap_ = context_->CreateMemoryHeap( gfx::MemoryType::kHostVisible | gfx::MemoryType::kHostCoherent, - 1 * 1024 * 1024); - if (!memory_pool_) { - LOG(ERROR) << "Unable to create memory pool"; + 8 * 1024 * 1024); + if (!memory_heap_) { + LOG(ERROR) << "Unable to create memory heap"; return false; } @@ -201,13 +201,12 @@ bool ImGuiOverlay::InitializeFont(ImGuiIO* io) { image_params.format = gfx::PixelFormats::kR8G8B8A8UNorm; image_params.tiling_mode = Image::TilingMode::kLinear; image_params.size = {width, height}; - image_params.usage_mask = Image::Usage::kSampled; image_params.initial_layout = Image::Layout::kPreinitialized; ref_ptr font_image; - auto allocation_result = - memory_pool_->AllocateImage(image_params, &font_image); + auto allocation_result = memory_heap_->AllocateImage( + image_params, Image::Usage::kSampled, &font_image); switch (allocation_result) { - case MemoryPool::AllocationResult::kSuccess: + case MemoryHeap::AllocationResult::kSuccess: break; default: LOG(ERROR) << "Failed to allocate font atlas image"; @@ -253,21 +252,21 @@ bool ImGuiOverlay::InitializePipeline() { } // Allocate the buffers we'll use to stash geometry from imgui. - auto allocation_result = memory_pool_->AllocateBuffer( + auto allocation_result = memory_heap_->AllocateBuffer( kMaxVertexCount * sizeof(ImDrawVert), Buffer::Usage::kVertexBuffer, &vertex_buffer_); switch (allocation_result) { - case MemoryPool::AllocationResult::kSuccess: + case MemoryHeap::AllocationResult::kSuccess: break; default: LOG(ERROR) << "Failed to allocate geometry vertex buffer"; return false; } allocation_result = - memory_pool_->AllocateBuffer(kMaxIndexCount * sizeof(ImDrawIdx), + memory_heap_->AllocateBuffer(kMaxIndexCount * sizeof(ImDrawIdx), Buffer::Usage::kIndexBuffer, &index_buffer_); switch (allocation_result) { - case MemoryPool::AllocationResult::kSuccess: + case MemoryHeap::AllocationResult::kSuccess: break; default: LOG(ERROR) << "Failed to allocate geometry index buffer"; diff --git a/xrtl/ui/imgui_overlay.h b/xrtl/ui/imgui_overlay.h index ca304e8..ffbd348 100644 --- a/xrtl/ui/imgui_overlay.h +++ b/xrtl/ui/imgui_overlay.h @@ -99,7 +99,7 @@ class ImGuiOverlay : public Control::InputListener { void OnMouseWheel(ref_ptr target, const MouseEvent& ev) override; ref_ptr context_; - ref_ptr memory_pool_; + ref_ptr memory_heap_; ref_ptr nearest_sampler_; ref_ptr linear_sampler_;