From: Mikko Rasa Date: Tue, 28 Dec 2021 10:03:58 +0000 (+0200) Subject: Add an asynchronous version of Texture2D::sub_image X-Git-Url: http://git.tdb.fi/?p=libs%2Fgl.git;a=commitdiff_plain;h=c218e021ede4e91c0fbc22ea9d636283409f847f Add an asynchronous version of Texture2D::sub_image --- diff --git a/source/backends/opengl/texture2d_backend.cpp b/source/backends/opengl/texture2d_backend.cpp index ed10f6c1..ab0c6d8c 100644 --- a/source/backends/opengl/texture2d_backend.cpp +++ b/source/backends/opengl/texture2d_backend.cpp @@ -119,6 +119,47 @@ void OpenGLTexture2D::unload() } +OpenGLTexture2D::AsyncTransfer::AsyncTransfer(AsyncTransfer &&other): + pixel_buffer(other.pixel_buffer) +{ + other.pixel_buffer = 0; +} + +OpenGLTexture2D::AsyncTransfer &OpenGLTexture2D::AsyncTransfer::operator=(AsyncTransfer &&other) +{ + delete pixel_buffer; + pixel_buffer = other.pixel_buffer; + other.pixel_buffer = 0; + + return *this; +} + +OpenGLTexture2D::AsyncTransfer::~AsyncTransfer() +{ + delete pixel_buffer; +} + +void *OpenGLTexture2D::AsyncTransfer::allocate() +{ + const Texture2D::AsyncTransfer &self = *static_cast(this); + + pixel_buffer = new Buffer; + pixel_buffer->storage(self.data_size, STREAMING); + return pixel_buffer->map(); +} + +void OpenGLTexture2D::AsyncTransfer::finalize() +{ + const Texture2D::AsyncTransfer &self = *static_cast(this); + + pixel_buffer->unmap(); + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pixel_buffer->id); + self.texture->OpenGLTexture2D::sub_image(self.level, self.x, self.y, self.width, self.height, 0); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); +} + + OpenGLTexture2D::AsyncLoader::AsyncLoader(Texture2D &t, IO::Seekable &i): texture(t), io(i) diff --git a/source/backends/opengl/texture2d_backend.h b/source/backends/opengl/texture2d_backend.h index af9f482c..5de10795 100644 --- a/source/backends/opengl/texture2d_backend.h +++ b/source/backends/opengl/texture2d_backend.h @@ -11,6 +11,20 @@ class Buffer; class OpenGLTexture2D: public Texture { protected: + class AsyncTransfer: public NonCopyable + { + protected: + Buffer *pixel_buffer = 0; + + AsyncTransfer() = default; + AsyncTransfer(AsyncTransfer &&); + AsyncTransfer &operator=(AsyncTransfer &&); + ~AsyncTransfer(); + + void *allocate(); + void finalize(); + }; + class AsyncLoader; OpenGLTexture2D(); diff --git a/source/backends/vulkan/texture2d_backend.cpp b/source/backends/vulkan/texture2d_backend.cpp index 7edee3b2..7c6b55f1 100644 --- a/source/backends/vulkan/texture2d_backend.cpp +++ b/source/backends/vulkan/texture2d_backend.cpp @@ -24,33 +24,8 @@ void VulkanTexture2D::fill_image_info(void *ii) const void VulkanTexture2D::sub_image(unsigned level, int x, int y, unsigned wd, unsigned ht, const void *data) { - const Texture2D &self = *static_cast(this); - - auto level_size = self.get_level_size(level); - bool discard = (x==0 && y==0 && wd==level_size.x && ht==level_size.y); - - TransferQueue &tq = device.get_transfer_queue(); - size_t data_size = wd*ht*get_pixel_size(storage_fmt); - void *staging = tq.prepare_transfer(this, false, data_size, - [this, level, discard](){ - change_layout(level, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, discard); - }, - [this, level, x, y, wd, ht](VkCommandBuffer cmd_buf, VkBuffer staging_buf, size_t src_off){ - const VulkanFunctions &vk = device.get_functions(); - - VkBufferImageCopy region = { }; - region.bufferOffset = src_off; - region.imageSubresource.aspectMask = get_vulkan_aspect(get_components(storage_fmt)); - region.imageSubresource.mipLevel = level; - region.imageSubresource.baseArrayLayer = 0; - region.imageSubresource.layerCount = 1; - region.imageOffset = { x, y, 0 }; - region.imageExtent = { wd, ht, 1 }; - vk.CmdCopyBufferToImage(cmd_buf, staging_buf, handle, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); - }); - - stage_pixels(staging, data, wd*ht); - tq.finalize_transfer(staging); + Texture2D::AsyncTransfer transfer(*static_cast(this), level, x, y, wd, ht); + stage_pixels(transfer.get_address(), data, wd*ht); } void VulkanTexture2D::fill_mipmap_blit(unsigned level, void *b) @@ -79,5 +54,46 @@ void VulkanTexture2D::unload() { } + +void *VulkanTexture2D::AsyncTransfer::allocate() +{ + const Texture2D::AsyncTransfer &self = *static_cast(this); + + Texture2D &tex = *self.texture; + unsigned level = self.level; + int x = self.x; + int y = self.y; + unsigned wd = self.width; + unsigned ht = self.height; + + auto level_size = tex.get_level_size(level); + bool discard = (x==0 && y==0 && wd==level_size.x && ht==level_size.y); + + TransferQueue &tq = tex.device.get_transfer_queue(); + return tq.prepare_transfer(&tex, false, self.data_size, + [&tex, level, discard](){ + tex.change_layout(level, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, discard); + }, + [&tex, level, x, y, wd, ht](VkCommandBuffer cmd_buf, VkBuffer staging_buf, size_t src_off){ + const VulkanFunctions &vk = tex.device.get_functions(); + + VkBufferImageCopy region = { }; + region.bufferOffset = src_off; + region.imageSubresource.aspectMask = get_vulkan_aspect(get_components(tex.storage_fmt)); + region.imageSubresource.mipLevel = level; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = { x, y, 0 }; + region.imageExtent = { wd, ht, 1 }; + vk.CmdCopyBufferToImage(cmd_buf, staging_buf, tex.handle, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + }); +} + +void VulkanTexture2D::AsyncTransfer::finalize() +{ + const Texture2D::AsyncTransfer &self = *static_cast(this); + self.texture->device.get_transfer_queue().finalize_transfer(self.dest_addr); +} + } // namespace GL } // namespace Msp diff --git a/source/backends/vulkan/texture2d_backend.h b/source/backends/vulkan/texture2d_backend.h index d4b7db1d..7686a9e0 100644 --- a/source/backends/vulkan/texture2d_backend.h +++ b/source/backends/vulkan/texture2d_backend.h @@ -9,6 +9,17 @@ namespace GL { class VulkanTexture2D: public Texture { protected: + class AsyncTransfer: public NonCopyable + { + protected: + AsyncTransfer() = default; + AsyncTransfer(AsyncTransfer &&) { } + AsyncTransfer &operator=(AsyncTransfer &&) { return *this; } + + void *allocate(); + void finalize(); + }; + VulkanTexture2D(); virtual void fill_image_info(void *) const; diff --git a/source/core/texture2d.cpp b/source/core/texture2d.cpp index 7ac1c5ec..74071535 100644 --- a/source/core/texture2d.cpp +++ b/source/core/texture2d.cpp @@ -48,6 +48,11 @@ void Texture2D::sub_image(unsigned level, unsigned x, unsigned y, unsigned wd, u Texture2DBackend::sub_image(level, x, y, wd, ht, data); } +Texture2D::AsyncTransfer Texture2D::sub_image_async(unsigned level, unsigned x, unsigned y, unsigned wd, unsigned ht) +{ + return AsyncTransfer(*this, level, x, y, wd, ht); +} + void Texture2D::image(const Graphics::Image &img, unsigned lv) { storage(pixelformat_from_image(img, use_srgb_format), img.get_width(), img.get_height(), lv); @@ -96,5 +101,60 @@ void Texture2D::Loader::storage_levels(PixelFormat fmt, unsigned w, unsigned h, obj.storage(fmt, w, h, l); } + +Texture2D::AsyncTransfer::AsyncTransfer(Texture2D &t, unsigned l, unsigned x_, unsigned y_, unsigned wd, unsigned ht): + texture(&t), + level(l), + x(x_), + y(y_), + width(wd), + height(ht), + data_size(width*height*get_pixel_size(texture->storage_fmt)), + dest_addr(0) +{ + dest_addr = allocate(); +} + +Texture2D::AsyncTransfer::AsyncTransfer(AsyncTransfer &&other): + Texture2DBackend::AsyncTransfer(move(other)), + texture(other.texture), + level(other.level), + x(other.x), + y(other.y), + width(other.width), + height(other.height), + data_size(other.data_size), + dest_addr(other.dest_addr) +{ + other.dest_addr = 0; +} + +Texture2D::AsyncTransfer &Texture2D::AsyncTransfer::operator=(AsyncTransfer &&other) +{ + if(dest_addr) + finalize(); + + Texture2DBackend::AsyncTransfer::operator=(move(other)); + + texture = other.texture; + level = other.level; + x = other.x; + y = other.y; + width = other.width; + height = other.height; + data_size = other.data_size; + dest_addr = other.dest_addr; + + other.dest_addr = 0; + + return *this; +} + +Texture2D::AsyncTransfer::~AsyncTransfer() +{ + if(dest_addr) + finalize(); +} + } // namespace GL } // namespace Msp diff --git a/source/core/texture2d.h b/source/core/texture2d.h index 05c3283b..02cdade5 100644 --- a/source/core/texture2d.h +++ b/source/core/texture2d.h @@ -28,6 +28,38 @@ public: void storage_levels(PixelFormat, unsigned, unsigned, unsigned); }; + /** + An RAII handle for asynchronously writing texel data into a texture. + */ + class AsyncTransfer: public Texture2DBackend::AsyncTransfer + { + friend Texture2DBackend; + friend class Texture2D; + friend class Texture2DBackend::AsyncTransfer; + + private: + Texture2D *texture = 0; + unsigned level = 0; + unsigned x = 0; + unsigned y = 0; + unsigned width = 0; + unsigned height = 0; + std::size_t data_size = 0; + void *dest_addr = 0; + + AsyncTransfer(Texture2D &, unsigned, unsigned, unsigned, unsigned, unsigned); + public: + AsyncTransfer() = default; + AsyncTransfer(AsyncTransfer &&); + AsyncTransfer &operator=(AsyncTransfer &&); + ~AsyncTransfer(); + + public: + /** Returns an address for writing the texel data. It should not be used + beyond the lifetime of the object. */ + void *get_address() { return dest_addr; } + }; + private: unsigned width = 0; unsigned height = 0; @@ -49,6 +81,8 @@ public: the region must be fully inside the selected mipmap level. */ void sub_image(unsigned level, unsigned x, unsigned y, unsigned wd, unsigned ht, const void *); + AsyncTransfer sub_image_async(unsigned level, unsigned x, unsigned y, unsigned wd, unsigned ht); + virtual void image(const Graphics::Image &, unsigned = 0); unsigned get_width() const { return width; }