/* * Copyright 2019-2021 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * 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. * * In no event and under no legal theory, whether in tort (including negligence), * contract, or otherwise, unless required by applicable law (such as deliberate * and grossly negligent acts) or agreed to in writing, shall any Contributor be * liable for any damages, including any direct, indirect, special, incidental, * or consequential damages of any character arising as a result of this License or * out of the use or inability to use the software (including but not limited to damages * for loss of goodwill, work stoppage, computer failure or malfunction, or any and * all other commercial damages or losses), even if such Contributor has been advised * of the possibility of such damages. */ #include #include #include #include #include #include "TextureUploaderGL.hpp" #include "ThreadSignal.hpp" #include "GraphicsAccessories.hpp" #include "Align.hpp" namespace Diligent { namespace { class UploadBufferGL : public UploadBufferBase { public: UploadBufferGL(IReferenceCounters* pRefCounters, const UploadBufferDesc& Desc) : // clang-format off UploadBufferBase{pRefCounters, Desc}, m_SubresourceOffsets(Desc.MipLevels * Desc.ArraySize + 1), m_SubresourceStrides(Desc.MipLevels * Desc.ArraySize ) // clang-format on { TextureDesc TexDesc; TexDesc.Format = Desc.Format; TexDesc.Width = Desc.Width; TexDesc.Height = Desc.Height; TexDesc.Depth = Desc.Depth; TexDesc.Type = Desc.ArraySize == 1 ? RESOURCE_DIM_TEX_2D : RESOURCE_DIM_TEX_2D_ARRAY; Uint32 SubRes = 0; for (Uint32 Slice = 0; Slice < Desc.ArraySize; ++Slice) { for (Uint32 Mip = 0; Mip < Desc.MipLevels; ++Mip) { auto MipProps = GetMipLevelProperties(TexDesc, Mip); // Stride must be 32-bit aligned in OpenGL auto RowStride = AlignUp(MipProps.RowSize, Uint32{4}); m_SubresourceStrides[SubRes] = RowStride; auto MipSize = MipProps.StorageHeight * RowStride; m_SubresourceOffsets[SubRes + 1] = m_SubresourceOffsets[SubRes] + MipSize; ++SubRes; } } } // http://en.cppreference.com/w/cpp/thread/condition_variable void WaitForMap() { m_BufferMappedSignal.Wait(); } void SignalMapped() { m_BufferMappedSignal.Trigger(); } void SignalCopyScheduled() { m_CopyScheduledSignal.Trigger(); } virtual void WaitForCopyScheduled() override final { m_CopyScheduledSignal.Wait(); } bool DbgIsCopyScheduled() const { return m_CopyScheduledSignal.IsTriggered(); } void SetDataPtr(Uint8* pBufferData) { for (Uint32 Slice = 0; Slice < m_Desc.ArraySize; ++Slice) { for (Uint32 Mip = 0; Mip < m_Desc.MipLevels; ++Mip) { SetMappedData(Mip, Slice, MappedTextureSubresource{pBufferData + GetOffset(Mip, Slice), GetStride(Mip, Slice), 0}); } } } Uint32 GetOffset(Uint32 Mip, Uint32 Slice) { VERIFY_EXPR(Mip < m_Desc.MipLevels && Slice < m_Desc.ArraySize); return m_SubresourceOffsets[m_Desc.MipLevels * Slice + Mip]; } void Reset() { m_BufferMappedSignal.Reset(); m_CopyScheduledSignal.Reset(); UploadBufferBase::Reset(); } Uint32 GetTotalSize() const { return m_SubresourceOffsets.back(); } private: Uint32 GetStride(Uint32 Mip, Uint32 Slice) { VERIFY_EXPR(Mip < m_Desc.MipLevels && Slice < m_Desc.ArraySize); return m_SubresourceStrides[m_Desc.MipLevels * Slice + Mip]; } friend TextureUploaderGL; ThreadingTools::Signal m_BufferMappedSignal; ThreadingTools::Signal m_CopyScheduledSignal; RefCntAutoPtr m_pStagingBuffer; std::vector m_SubresourceOffsets; std::vector m_SubresourceStrides; }; } // namespace struct TextureUploaderGL::InternalData { void SwapMapQueues() { std::lock_guard QueueLock(m_PendingOperationsMtx); m_PendingOperations.swap(m_InWorkOperations); } void EnqueCopy(UploadBufferGL* pUploadBuffer, ITexture* pDstTexture, Uint32 dstSlice, Uint32 dstMip) { std::lock_guard QueueLock(m_PendingOperationsMtx); m_PendingOperations.emplace_back(PendingBufferOperation::Operation::Copy, pUploadBuffer, pDstTexture, dstSlice, dstMip); } void EnqueMap(UploadBufferGL* pUploadBuffer) { std::lock_guard QueueLock(m_PendingOperationsMtx); m_PendingOperations.emplace_back(PendingBufferOperation::Operation::Map, pUploadBuffer); } struct PendingBufferOperation { enum Operation { Map, Copy } operation; RefCntAutoPtr pUploadBuffer; RefCntAutoPtr pDstTexture; Uint32 DstSlice = 0; Uint32 DstMip = 0; // clang-format off PendingBufferOperation(Operation op, UploadBufferGL* pBuff) : operation {op }, pUploadBuffer{pBuff} {} PendingBufferOperation(Operation op, UploadBufferGL* pBuff, ITexture *pDstTex, Uint32 dstSlice, Uint32 dstMip) : operation {op }, pUploadBuffer{pBuff }, pDstTexture {pDstTex }, DstSlice {dstSlice}, DstMip {dstMip } {} // clang-format on }; void Execute(IRenderDevice* pDevice, IDeviceContext* pContext, PendingBufferOperation& OperationInfo); std::mutex m_PendingOperationsMtx; std::vector m_PendingOperations; std::vector m_InWorkOperations; std::mutex m_UploadBuffCacheMtx; std::unordered_map>> m_UploadBufferCache; }; TextureUploaderGL::TextureUploaderGL(IReferenceCounters* pRefCounters, IRenderDevice* pDevice, const TextureUploaderDesc Desc) : TextureUploaderBase{pRefCounters, pDevice, Desc}, m_pInternalData{new InternalData{}} { } TextureUploaderGL::~TextureUploaderGL() { auto Stats = TextureUploaderGL::GetStats(); if (Stats.NumPendingOperations != 0) { LOG_WARNING_MESSAGE("TextureUploaderGL::~TextureUploaderGL(): there ", (Stats.NumPendingOperations > 1 ? "are " : "is "), Stats.NumPendingOperations, (Stats.NumPendingOperations > 1 ? " pending operations" : " pending operation"), " in the queue. If other threads wait for ", (Stats.NumPendingOperations > 1 ? "these operations" : "this operation"), ", they may deadlock."); } for (auto BuffQueueIt : m_pInternalData->m_UploadBufferCache) { if (BuffQueueIt.second.size()) { const auto& desc = BuffQueueIt.first; auto& FmtInfo = m_pDevice->GetTextureFormatInfo(desc.Format); LOG_INFO_MESSAGE("TextureUploaderGL: releasing ", BuffQueueIt.second.size(), ' ', desc.Width, 'x', desc.Height, 'x', desc.Depth, ' ', FmtInfo.Name, " upload buffer", (BuffQueueIt.second.size() != 1 ? "s" : "")); } } } void TextureUploaderGL::RenderThreadUpdate(IDeviceContext* pContext) { m_pInternalData->SwapMapQueues(); if (!m_pInternalData->m_InWorkOperations.empty()) { for (auto& OperationInfo : m_pInternalData->m_InWorkOperations) { m_pInternalData->Execute(m_pDevice, pContext, OperationInfo); } m_pInternalData->m_InWorkOperations.clear(); } } void TextureUploaderGL::InternalData::Execute(IRenderDevice* pDevice, IDeviceContext* pContext, PendingBufferOperation& OperationInfo) { auto& pBuffer = OperationInfo.pUploadBuffer; const auto& UploadBuffDesc = pBuffer->GetDesc(); switch (OperationInfo.operation) { case InternalData::PendingBufferOperation::Map: { if (pBuffer->m_pStagingBuffer == nullptr) { BufferDesc BuffDesc; BuffDesc.Name = "Staging buffer for UploadBufferGL"; BuffDesc.CPUAccessFlags = CPU_ACCESS_WRITE; BuffDesc.Usage = USAGE_STAGING; BuffDesc.uiSizeInBytes = pBuffer->GetTotalSize(); RefCntAutoPtr pStagingBuffer; pDevice->CreateBuffer(BuffDesc, nullptr, &pBuffer->m_pStagingBuffer); } PVoid CpuAddress = nullptr; pContext->MapBuffer(pBuffer->m_pStagingBuffer, MAP_WRITE, MAP_FLAG_DISCARD, CpuAddress); pBuffer->SetDataPtr(reinterpret_cast(CpuAddress)); pBuffer->SignalMapped(); } break; case InternalData::PendingBufferOperation::Copy: { const auto& TexDesc = OperationInfo.pDstTexture->GetDesc(); pContext->UnmapBuffer(pBuffer->m_pStagingBuffer, MAP_WRITE); for (Uint32 Slice = 0; Slice < UploadBuffDesc.ArraySize; ++Slice) { for (Uint32 Mip = 0; Mip < UploadBuffDesc.MipLevels; ++Mip) { auto SrcOffset = pBuffer->GetOffset(Mip, Slice); auto SrcStride = pBuffer->GetMappedData(Mip, Slice).Stride; TextureSubResData SubResData(pBuffer->m_pStagingBuffer, SrcOffset, SrcStride); auto MipLevelProps = GetMipLevelProperties(TexDesc, OperationInfo.DstMip + Mip); Box DstBox; DstBox.MaxX = MipLevelProps.LogicalWidth; DstBox.MaxY = MipLevelProps.LogicalHeight; pContext->UpdateTexture(OperationInfo.pDstTexture, OperationInfo.DstMip + Mip, OperationInfo.DstSlice + Slice, DstBox, SubResData, RESOURCE_STATE_TRANSITION_MODE_TRANSITION, RESOURCE_STATE_TRANSITION_MODE_TRANSITION); } } pBuffer->SignalCopyScheduled(); } break; } } void TextureUploaderGL::AllocateUploadBuffer(IDeviceContext* pContext, const UploadBufferDesc& Desc, IUploadBuffer** ppBuffer) { *ppBuffer = nullptr; RefCntAutoPtr pUploadBuffer; { std::lock_guard CacheLock(m_pInternalData->m_UploadBuffCacheMtx); auto& Cache = m_pInternalData->m_UploadBufferCache; if (!Cache.empty()) { auto DequeIt = Cache.find(Desc); if (DequeIt != Cache.end()) { auto& Deque = DequeIt->second; if (!Deque.empty()) { pUploadBuffer.Attach(Deque.front().Detach()); Deque.pop_front(); } } } } if (!pUploadBuffer) { pUploadBuffer = MakeNewRCObj()(Desc); LOG_INFO_MESSAGE("TextureUploaderGL: created upload buffer for ", Desc.Width, 'x', Desc.Height, 'x', Desc.Depth, ' ', Desc.MipLevels, "-mip ", Desc.ArraySize, "-slice ", m_pDevice->GetTextureFormatInfo(Desc.Format).Name, " texture"); } if (pContext != nullptr) { // Render thread InternalData::PendingBufferOperation MapOp{InternalData::PendingBufferOperation::Operation::Map, pUploadBuffer}; m_pInternalData->Execute(m_pDevice, pContext, MapOp); } else { // Worker thread m_pInternalData->EnqueMap(pUploadBuffer); pUploadBuffer->WaitForMap(); } *ppBuffer = pUploadBuffer.Detach(); } void TextureUploaderGL::ScheduleGPUCopy(IDeviceContext* pContext, ITexture* pDstTexture, Uint32 ArraySlice, Uint32 MipLevel, IUploadBuffer* pUploadBuffer) { auto* pUploadBufferGL = ValidatedCast(pUploadBuffer); if (pContext != nullptr) { // Render thread InternalData::PendingBufferOperation CopyOp // { InternalData::PendingBufferOperation::Operation::Copy, pUploadBufferGL, pDstTexture, ArraySlice, MipLevel // }; m_pInternalData->Execute(m_pDevice, pContext, CopyOp); } else { // Worker thread m_pInternalData->EnqueCopy(pUploadBufferGL, pDstTexture, ArraySlice, MipLevel); } } void TextureUploaderGL::RecycleBuffer(IUploadBuffer* pUploadBuffer) { auto* pUploadBufferGL = ValidatedCast(pUploadBuffer); VERIFY(pUploadBufferGL->DbgIsCopyScheduled(), "Upload buffer must be recycled only after copy operation has been scheduled on the GPU"); pUploadBufferGL->Reset(); std::lock_guard CacheLock(m_pInternalData->m_UploadBuffCacheMtx); auto& Cache = m_pInternalData->m_UploadBufferCache; auto& Deque = Cache[pUploadBufferGL->GetDesc()]; Deque.emplace_back(pUploadBufferGL); } TextureUploaderStats TextureUploaderGL::GetStats() { TextureUploaderStats Stats; std::lock_guard QueueLock(m_pInternalData->m_PendingOperationsMtx); Stats.NumPendingOperations = static_cast(m_pInternalData->m_PendingOperations.size()); return Stats; } } // namespace Diligent