From b655d7bc8327a7bbd00b618b91cff79e8ab60025 Mon Sep 17 00:00:00 2001 From: nemerle Date: Sun, 4 Aug 2019 22:36:45 +0200 Subject: [PATCH] Feature: Introduce `writeSubData` in `Texture` API This is mostly a proposal and a call for help from D3D11 and Vulkan gurus out there. What is missing: - Vulkan support - D3D11 support - Optional written-area-within-target checks. --- Source/Foundation/bsfCore/Image/BsTexture.cpp | 32 +++++++++++-- Source/Foundation/bsfCore/Image/BsTexture.h | 47 +++++++++++++++---- .../bsfD3D11RenderAPI/BsD3D11Texture.cpp | 6 +++ .../bsfD3D11RenderAPI/BsD3D11Texture.h | 6 ++- .../bsfGLRenderAPI/BsGLPixelBuffer.cpp | 20 ++++---- Source/Plugins/bsfGLRenderAPI/BsGLTexture.cpp | 35 ++++++++++++-- Source/Plugins/bsfGLRenderAPI/BsGLTexture.h | 6 ++- .../Plugins/bsfNullRenderAPI/BsNullTexture.h | 6 ++- .../bsfVulkanRenderAPI/BsVulkanTexture.cpp | 19 +++++--- .../bsfVulkanRenderAPI/BsVulkanTexture.h | 6 ++- 10 files changed, 147 insertions(+), 36 deletions(-) diff --git a/Source/Foundation/bsfCore/Image/BsTexture.cpp b/Source/Foundation/bsfCore/Image/BsTexture.cpp index 0ecf6c979..1bb27bc79 100644 --- a/Source/Foundation/bsfCore/Image/BsTexture.cpp +++ b/Source/Foundation/bsfCore/Image/BsTexture.cpp @@ -67,7 +67,7 @@ namespace bs Texture::Texture(const TEXTURE_DESC& desc) :mProperties(desc) { - + } Texture::Texture(const TEXTURE_DESC& desc, const SPtr& pixelData) @@ -125,7 +125,26 @@ namespace bs return gCoreThread().queueReturnCommand(std::bind(func, getCore(), face, mipLevel, data, discardEntireBuffer, std::placeholders::_1)); } + AsyncOp Texture::writeSubData(const SPtr& data, Vector3I dstPosition, UINT32 face, UINT32 mipLevel) + { + UINT32 subresourceIdx = mProperties.mapToSubresourceIdx(face, mipLevel); + updateCPUBuffers(subresourceIdx, *data); + + data->_lock(); + + std::function&, Vector3I, UINT32, UINT32, const SPtr&, AsyncOp&)> func = + [&](const SPtr& texture, Vector3I dstPos, UINT32 _face, UINT32 _mipLevel, const SPtr& _pixData, + AsyncOp& asyncOp) + { + texture->writeSubData(*_pixData, dstPos,_mipLevel, _face); + _pixData->_unlock(); + asyncOp._completeOperation(); + }; + + return gCoreThread().queueReturnCommand(std::bind(func, getCore(), dstPosition,face, mipLevel, + data, std::placeholders::_1)); + } AsyncOp Texture::readData(const SPtr& data, UINT32 face, UINT32 mipLevel) { data->_lock(); @@ -306,7 +325,7 @@ namespace bs return static_resource_cast(gResources()._createResourceHandle(texturePtr)); } - + HTexture Texture::create(const SPtr& pixelData, int usage, bool hwGammaCorrection) { SPtr texturePtr = _createPtr(pixelData, usage, hwGammaCorrection); @@ -372,6 +391,13 @@ namespace bs writeDataImpl(src, mipLevel, face, discardEntireBuffer, queueIdx); } + void Texture::writeSubData(const PixelData& src, Vector3I dstPosition, UINT32 mipLevel, UINT32 face, + UINT32 queueIdx) + { + THROW_IF_NOT_CORE_THREAD; + writeSubDataImpl(src, dstPosition,mipLevel, face, queueIdx); + } + void Texture::readData(PixelData& dest, UINT32 mipLevel, UINT32 face, UINT32 deviceIdx, UINT32 queueIdx) { THROW_IF_NOT_CORE_THREAD; @@ -555,7 +581,7 @@ namespace bs { SPtr data = mProperties.allocBuffer(face, mipLevel); data->setColors(value); - + writeData(*data, mipLevel, face, true, queueIdx); } diff --git a/Source/Foundation/bsfCore/Image/BsTexture.h b/Source/Foundation/bsfCore/Image/BsTexture.h index c045bb4b9..d1c5f5a02 100644 --- a/Source/Foundation/bsfCore/Image/BsTexture.h +++ b/Source/Foundation/bsfCore/Image/BsTexture.h @@ -171,7 +171,7 @@ namespace bs * Allocates a buffer that exactly matches the format of the texture described by these properties, for the provided * face and mip level. This is a helper function, primarily meant for creating buffers when reading from, or writing * to a texture. - * + * * @note Thread safe. */ SPtr allocBuffer(UINT32 face, UINT32 mipLevel) const; @@ -221,6 +221,20 @@ namespace bs AsyncOp writeData(const SPtr& data, UINT32 face = 0, UINT32 mipLevel = 0, bool discardEntireBuffer = false); + /** + * Updates part of the texture with new data. Provided data buffer will be locked until the operation completes. + * + * @param[in] data Pixel data to write. User must ensure it is in format and size compatible with + * the texture. + * @param[in] dstPosition Position that will be overwritten by provided data. + * @param[in] face Texture face to write to. + * @param[in] mipLevel Mipmap level to write to. + * @return Async operation object you can use to track operation completion. + * + * @note This is an @ref asyncMethod "asynchronous method". + */ + AsyncOp writeSubData(const SPtr& data, Vector3I dstPosition, UINT32 face = 0, UINT32 mipLevel = 0); + /** * Reads internal texture data to the provided previously allocated buffer. Provided data buffer will be locked * until the operation completes. @@ -250,13 +264,13 @@ namespace bs /** * Reads data from the cached system memory texture buffer into the provided buffer. - * + * * @param[out] data Pre-allocated buffer of proper size and format where data will be read to. You can use * TextureProperties::allocBuffer() to allocate a buffer of a correct format and size. * @param[in] face Texture face to read from. * @param[in] mipLevel Mipmap level to read from. * - * @note + * @note * The data read is the cached texture data. Any data written to the texture from the GPU or core thread will not * be reflected in this data. Use readData() if you require those changes. * @note @@ -377,8 +391,8 @@ namespace bs * @param[in] deviceIdx Index of the device whose memory to map. If the buffer doesn't exist on this device, * the method returns null. * @param[in] queueIdx Device queue to perform the read/write operations on. See @ref queuesDoc. - * - * @note + * + * @note * If you are just reading or writing one block of data use readData()/writeData() methods as they can be much faster * in certain situations. */ @@ -419,7 +433,7 @@ namespace bs /** * Reads data from the texture buffer into the provided buffer. - * + * * @param[out] dest Previously allocated buffer to read data into. * @param[in] mipLevel (optional) Mipmap level to read from. * @param[in] face (optional) Texture face to read from. @@ -428,11 +442,11 @@ namespace bs * @param[in] queueIdx Device queue to perform the read operation on. See @ref queuesDoc. */ void readData(PixelData& dest, UINT32 mipLevel = 0, UINT32 face = 0, UINT32 deviceIdx = 0, - UINT32 queueIdx = 0); + UINT32 queueIdx = 0); /** * Writes data from the provided buffer into the texture buffer. - * + * * @param[in] src Buffer to retrieve the data from. * @param[in] mipLevel (optional) Mipmap level to write into. * @param[in] face (optional) Texture face to write into. @@ -441,7 +455,19 @@ namespace bs * @param[in] queueIdx Device queue to perform the write operation on. See @ref queuesDoc. */ void writeData(const PixelData& src, UINT32 mipLevel = 0, UINT32 face = 0, bool discardWholeBuffer = false, - UINT32 queueIdx = 0); + UINT32 queueIdx = 0); + + /** + * Writes data from the provided buffer into the specific location in texture buffer. + * + * @param[in] src Buffer to retrieve the data from. + * @param[in] dstPosition Position that will be overwritten by provided data. + * @param[in] mipLevel (optional) Mipmap level to write into. + * @param[in] face (optional) Texture face to write into. + * @param[in] queueIdx Device queue to perform the write operation on. See @ref queuesDoc. + */ + void writeSubData(const PixelData& src, Vector3I dstPosition, UINT32 mipLevel = 0, UINT32 face = 0, + UINT32 queueIdx = 0); /** Returns properties that contain information about the texture. */ const TextureProperties& getProperties() const { return mProperties; } @@ -504,6 +530,9 @@ namespace bs virtual void writeDataImpl(const PixelData& src, UINT32 mipLevel = 0, UINT32 face = 0, bool discardWholeBuffer = false, UINT32 queueIdx = 0) = 0; + /** @copydoc writeSubData */ + virtual void writeSubDataImpl(const PixelData& src, Vector3I dstPosition, UINT32 mipLevel = 0, UINT32 face = 0, + UINT32 queueIdx = 0) = 0; /** @copydoc clear */ virtual void clearImpl(const Color& value, UINT32 mipLevel = 0, UINT32 face = 0, UINT32 queueIdx = 0); diff --git a/Source/Plugins/bsfD3D11RenderAPI/BsD3D11Texture.cpp b/Source/Plugins/bsfD3D11RenderAPI/BsD3D11Texture.cpp index 8113f8bf3..a196b5308 100644 --- a/Source/Plugins/bsfD3D11RenderAPI/BsD3D11Texture.cpp +++ b/Source/Plugins/bsfD3D11RenderAPI/BsD3D11Texture.cpp @@ -281,6 +281,12 @@ namespace bs { namespace ct } } + void D3D11Texture::writeSubDataImpl(const PixelData& src,Vector3I dst, UINT32 mipLevel, UINT32 face, + UINT32 queueIdx) + { + BS_EXCEPT(RenderingAPIException, "D3D11Texture writeSubDataImpl has not been implementeted yet"); + } + void D3D11Texture::create1DTex() { UINT32 width = mProperties.getWidth(); diff --git a/Source/Plugins/bsfD3D11RenderAPI/BsD3D11Texture.h b/Source/Plugins/bsfD3D11RenderAPI/BsD3D11Texture.h index 12fd47e8e..584b2b810 100644 --- a/Source/Plugins/bsfD3D11RenderAPI/BsD3D11Texture.h +++ b/Source/Plugins/bsfD3D11RenderAPI/BsD3D11Texture.h @@ -57,7 +57,11 @@ namespace bs { namespace ct /** @copydoc Texture::writeData */ void writeDataImpl(const PixelData& src, UINT32 mipLevel = 0, UINT32 face = 0, bool discardWholeBuffer = false, - UINT32 queueIdx = 0) override; + UINT32 queueIdx = 0) override; + + /** @copydoc Texture::writeSubData */ + void writeSubDataImpl(const PixelData& src, Vector3I dstPosition, UINT32 mipLevel = 0, UINT32 face = 0, + UINT32 queueIdx = 0) override; /** Creates a blank DX11 1D texture object. */ void create1DTex(); diff --git a/Source/Plugins/bsfGLRenderAPI/BsGLPixelBuffer.cpp b/Source/Plugins/bsfGLRenderAPI/BsGLPixelBuffer.cpp index 87f84a624..798e7d9c5 100644 --- a/Source/Plugins/bsfGLRenderAPI/BsGLPixelBuffer.cpp +++ b/Source/Plugins/bsfGLRenderAPI/BsGLPixelBuffer.cpp @@ -43,7 +43,7 @@ namespace bs { namespace ct void* GLPixelBuffer::lock(UINT32 offset, UINT32 length, GpuLockOptions options) { assert(!mIsLocked && "Cannot lock this buffer, it is already locked!"); - assert(offset == 0 && length == mSizeInBytes && "Cannot lock memory region, most lock box or entire buffer"); + assert(offset == 0 && length == mSizeInBytes && "Cannot lock memory region, must lock box or entire buffer"); PixelVolume volume(0, 0, 0, mWidth, mHeight, mDepth); const PixelData& lockedData = lock(volume, options); @@ -117,21 +117,21 @@ namespace bs { namespace ct , mLevel(level), mMultisampleCount(multisampleCount), mHwGamma(hwGamma) { GLint value = 0; - + glBindTexture(mTarget, mTextureID); BS_CHECK_GL_ERROR(); - + // Get face identifier mFaceTarget = mTarget; if(mTarget == GL_TEXTURE_CUBE_MAP) mFaceTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + (face % 6); - + // Get width glGetTexLevelParameteriv(mFaceTarget, level, GL_TEXTURE_WIDTH, &value); BS_CHECK_GL_ERROR(); mWidth = value; - + // Get height if(target == GL_TEXTURE_1D) value = 1; // Height always 1 for 1D textures @@ -142,7 +142,7 @@ namespace bs { namespace ct } mHeight = value; - + // Get depth if(target != GL_TEXTURE_3D) value = 1; // Depth always 1 for non-3D textures @@ -156,7 +156,7 @@ namespace bs { namespace ct // Default mSizeInBytes = PixelUtil::getMemorySize(mWidth, mHeight, mDepth, mFormat); - + // Set up pixel box mBuffer = PixelData(mWidth, mHeight, mDepth, mFormat); } @@ -220,7 +220,7 @@ namespace bs { namespace ct default: break; } - + } else { @@ -282,7 +282,7 @@ namespace bs { namespace ct data.getData()); BS_CHECK_GL_ERROR(); break; - } + } } // Restore defaults @@ -535,7 +535,7 @@ namespace bs { namespace ct mTextureID, mTarget, mLevel, dstBox.left, dstBox.top, mFace, srcBox.getWidth(), srcBox.getHeight(), 1); BS_CHECK_GL_ERROR(); } - } + } #endif } }} diff --git a/Source/Plugins/bsfGLRenderAPI/BsGLTexture.cpp b/Source/Plugins/bsfGLRenderAPI/BsGLTexture.cpp index 72a23948d..74b55d454 100644 --- a/Source/Plugins/bsfGLRenderAPI/BsGLTexture.cpp +++ b/Source/Plugins/bsfGLRenderAPI/BsGLTexture.cpp @@ -287,7 +287,7 @@ namespace bs { namespace ct if(height > 1) height = height/2; - if(depth > 1) + if(depth > 1) depth = depth/2; } } @@ -449,6 +449,35 @@ namespace bs { namespace ct getBuffer(face, mipLevel)->upload(src, src.getExtents()); } + void GLTexture::writeSubDataImpl(const PixelData& src,Vector3I dst, UINT32 mipLevel, UINT32 face, + UINT32 queueIdx) + { + if (mProperties.getNumSamples() > 1) + { + BS_LOG(Error, RenderBackend, "Multisampled textures cannot be accessed from the CPU directly."); + return; + } + + PixelVolume src_vol = src.getExtents(); + PixelVolume target_vol( + dst.x,dst.y,dst.z, + dst.x+src_vol.getWidth(),dst.y+src_vol.getHeight(),dst.z+src_vol.getDepth() + ); + if (src.getFormat() != mInternalFormat) + { + PixelData temp(src.getExtents(), mInternalFormat); + temp.allocateInternalBuffer(); + + PixelUtil::bulkPixelConversion(src, temp); + getBuffer(face, mipLevel)->upload(temp, target_vol); + } + else + { + + getBuffer(face, mipLevel)->upload(src, target_vol); + } + } + void GLTexture::copyImpl(const SPtr& target, const TEXTURE_COPY_DESC& desc, const SPtr& commandBuffer) { @@ -503,7 +532,7 @@ namespace bs { namespace ct void GLTexture::createSurfaceList() { mSurfaceList.clear(); - + for (UINT32 face = 0; face < mProperties.getNumFaces(); face++) { for (UINT32 mip = 0; mip <= mProperties.getNumMipmaps(); mip++) @@ -523,7 +552,7 @@ namespace bs { namespace ct } } } - + SPtr GLTexture::getBuffer(UINT32 face, UINT32 mipmap) { THROW_IF_NOT_CORE_THREAD; diff --git a/Source/Plugins/bsfGLRenderAPI/BsGLTexture.h b/Source/Plugins/bsfGLRenderAPI/BsGLTexture.h index 6ad8dd721..20c7a8932 100644 --- a/Source/Plugins/bsfGLRenderAPI/BsGLTexture.h +++ b/Source/Plugins/bsfGLRenderAPI/BsGLTexture.h @@ -74,6 +74,10 @@ namespace bs { namespace ct void writeDataImpl(const PixelData& src, UINT32 mipLevel = 0, UINT32 face = 0, bool discardWholeBuffer = false, UINT32 queueIdx = 0) override; + /** @copydoc Texture::writeSubData */ + void writeSubDataImpl(const PixelData& src, Vector3I dstPosition, UINT32 mipLevel = 0, UINT32 face = 0, + UINT32 queueIdx = 0) override; + /** Creates pixel buffers for each face and mip level. Texture must have been created previously. */ void createSurfaceList(); @@ -86,7 +90,7 @@ namespace bs { namespace ct PixelFormat mInternalFormat = PF_UNKNOWN; GLSupport& mGLSupport; SPtr mLockedBuffer; - + Vector>mSurfaceList; }; diff --git a/Source/Plugins/bsfNullRenderAPI/BsNullTexture.h b/Source/Plugins/bsfNullRenderAPI/BsNullTexture.h index f5977ecad..2458d857b 100644 --- a/Source/Plugins/bsfNullRenderAPI/BsNullTexture.h +++ b/Source/Plugins/bsfNullRenderAPI/BsNullTexture.h @@ -19,7 +19,7 @@ namespace bs /** @copydoc TextureManager::getNativeFormat */ PixelFormat getNativeFormat(TextureType ttype, PixelFormat format, int usage, bool hwGamma) override; - protected: + protected: /** @copydoc TextureManager::createRenderTextureImpl */ SPtr createRenderTextureImpl(const RENDER_TEXTURE_DESC& desc) override; }; @@ -66,6 +66,10 @@ namespace bs void writeDataImpl(const PixelData& src, UINT32 mipLevel = 0, UINT32 face = 0, bool discardWholeBuffer = false, UINT32 queueIdx = 0) override { } + /** @copydoc Texture::writeSubData */ + void writeSubDataImpl(const PixelData& src, Vector3I dstPosition, UINT32 mipLevel = 0, UINT32 face = 0, + UINT32 queueIdx = 0) override {} + protected: PixelData* mMappedBuffer = nullptr; }; diff --git a/Source/Plugins/bsfVulkanRenderAPI/BsVulkanTexture.cpp b/Source/Plugins/bsfVulkanRenderAPI/BsVulkanTexture.cpp index 4773530f3..81c6872ca 100644 --- a/Source/Plugins/bsfVulkanRenderAPI/BsVulkanTexture.cpp +++ b/Source/Plugins/bsfVulkanRenderAPI/BsVulkanTexture.cpp @@ -237,7 +237,7 @@ namespace bs { namespace ct // If it's load-store, no other flags matter, it must be in general layout if ((mUsage & TU_LOADSTORE) != 0) return VK_IMAGE_LAYOUT_GENERAL; - + if ((mUsage & TU_RENDERTARGET) != 0) return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; else if ((mUsage & TU_DEPTHSTENCIL) != 0) @@ -261,10 +261,10 @@ namespace bs { namespace ct if (hasStencil) return VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; - + return VK_IMAGE_ASPECT_DEPTH_BIT; } - + return VK_IMAGE_ASPECT_COLOR_BIT; } @@ -604,7 +604,7 @@ namespace bs { namespace ct , mMappedMip(0), mMappedFace(0), mMappedRowPitch(0), mMappedSlicePitch(0) , mMappedLockOptions(GBL_WRITE_ONLY), mDirectlyMappable(false), mSupportsGPUWrites(false), mIsMapped(false) { - + } VulkanTexture::~VulkanTexture() @@ -652,7 +652,7 @@ namespace bs { namespace ct // Note: I force rendertarget and depthstencil types to be readable in shader. Depending on performance impact // it might be beneficial to allow the user to enable this explicitly only when needed. - + mImageCI.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; int usage = props.getUsage(); @@ -666,7 +666,7 @@ namespace bs { namespace ct mImageCI.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; mSupportsGPUWrites = true; } - + if ((usage & TU_LOADSTORE) != 0) { mImageCI.usage |= VK_IMAGE_USAGE_STORAGE_BIT; @@ -732,7 +732,7 @@ namespace bs { namespace ct props.isHardwareGammaEnabled()); mImages[i] = createImage(*devices[i], mInternalFormats[i]); - + } BS_INC_RENDER_STAT_CAT(ResCreated, RenderStatObject_Texture); @@ -1475,4 +1475,9 @@ namespace bs { namespace ct BS_INC_RENDER_STAT_CAT(ResWrite, RenderStatObject_Texture); } + void VulkanTexture::writeSubDataImpl(const PixelData& src,Vector3I dst, UINT32 mipLevel, UINT32 face, + UINT32 queueIdx) + { + BS_EXCEPT(RenderingAPIException, "VulkanTexture writeSubDataImpl has not been implementeted yet"); + } }} diff --git a/Source/Plugins/bsfVulkanRenderAPI/BsVulkanTexture.h b/Source/Plugins/bsfVulkanRenderAPI/BsVulkanTexture.h index 82b5ff33a..daf953156 100644 --- a/Source/Plugins/bsfVulkanRenderAPI/BsVulkanTexture.h +++ b/Source/Plugins/bsfVulkanRenderAPI/BsVulkanTexture.h @@ -75,7 +75,7 @@ namespace bs { namespace ct * attachment flags are set on the view. */ VkImageView getView(const TextureSurface& surface, bool framebuffer) const; - + /** * Returns an image view with a specific format. * @@ -242,6 +242,10 @@ namespace bs { namespace ct void writeDataImpl(const PixelData& src, UINT32 mipLevel = 0, UINT32 face = 0, bool discardWholeBuffer = false, UINT32 queueIdx = 0) override; + /** @copydoc Texture::writeSubData */ + void writeSubDataImpl(const PixelData& src, Vector3I dstPosition, UINT32 mipLevel = 0, UINT32 face = 0, + UINT32 queueIdx = 0) override; + private: /** Creates a new image for the specified device, matching the current properties. */ VulkanImage* createImage(VulkanDevice& device, PixelFormat format);