| // Copyright 2018 The Dawn & Tint Authors | |
| // | |
| // Redistribution and use in source and binary forms, with or without | |
| // modification, are permitted provided that the following conditions are met: | |
| // | |
| // 1. Redistributions of source code must retain the above copyright notice, this | |
| // list of conditions and the following disclaimer. | |
| // | |
| // 2. Redistributions in binary form must reproduce the above copyright notice, | |
| // this list of conditions and the following disclaimer in the documentation | |
| // and/or other materials provided with the distribution. | |
| // | |
| // 3. Neither the name of the copyright holder nor the names of its | |
| // contributors may be used to endorse or promote products derived from | |
| // this software without specific prior written permission. | |
| // | |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| #include "dawn/native/opengl/QueueGL.h" | |
| #include "dawn/native/BlitBufferToDepthStencil.h" | |
| #include "dawn/native/CommandBuffer.h" | |
| #include "dawn/native/CommandEncoder.h" | |
| #include "dawn/native/opengl/BufferGL.h" | |
| #include "dawn/native/opengl/CommandBufferGL.h" | |
| #include "dawn/native/opengl/DeviceGL.h" | |
| #include "dawn/native/opengl/EGLFunctions.h" | |
| #include "dawn/native/opengl/PhysicalDeviceGL.h" | |
| #include "dawn/native/opengl/SharedFenceEGL.h" | |
| #include "dawn/native/opengl/TextureGL.h" | |
| #include "dawn/platform/DawnPlatform.h" | |
| #include "dawn/platform/tracing/TraceEvent.h" | |
| namespace dawn::native::opengl { | |
| ResultOrError<Ref<Queue>> Queue::Create(Device* device, const QueueDescriptor* descriptor) { | |
| return AcquireRef(new Queue(device, descriptor)); | |
| } | |
| Queue::Queue(Device* device, const QueueDescriptor* descriptor) : QueueBase(device, descriptor) { | |
| const auto& egl = device->GetEGL(false); | |
| if (egl.HasExt(EGLExt::NativeFenceSync)) { | |
| mEGLSyncType = EGL_SYNC_NATIVE_FENCE_ANDROID; | |
| } else if (egl.HasExt(EGLExt::FenceSync)) { | |
| mEGLSyncType = EGL_SYNC_FENCE; | |
| } else if (egl.HasExt(EGLExt::ReusableSync)) { | |
| mEGLSyncType = EGL_SYNC_REUSABLE_KHR; | |
| } else { | |
| DAWN_UNREACHABLE(); | |
| } | |
| } | |
| MaybeError Queue::SubmitImpl(uint32_t commandCount, CommandBufferBase* const* commands) { | |
| TRACE_EVENT_BEGIN0(GetDevice()->GetPlatform(), Recording, "CommandBufferGL::Execute"); | |
| for (uint32_t i = 0; i < commandCount; ++i) { | |
| DAWN_TRY(ToBackend(commands[i])->Execute()); | |
| } | |
| TRACE_EVENT_END0(GetDevice()->GetPlatform(), Recording, "CommandBufferGL::Execute"); | |
| return {}; | |
| } | |
| MaybeError Queue::WriteBufferImpl(BufferBase* buffer, | |
| uint64_t bufferOffset, | |
| const void* data, | |
| size_t size) { | |
| const OpenGLFunctions& gl = ToBackend(GetDevice())->GetGL(); | |
| ToBackend(buffer)->EnsureDataInitializedAsDestination(bufferOffset, size); | |
| gl.BindBuffer(GL_ARRAY_BUFFER, ToBackend(buffer)->GetHandle()); | |
| gl.BufferSubData(GL_ARRAY_BUFFER, bufferOffset, size, data); | |
| buffer->MarkUsedInPendingCommands(); | |
| return {}; | |
| } | |
| MaybeError Queue::WriteTextureImpl(const ImageCopyTexture& destination, | |
| const void* data, | |
| size_t dataSize, | |
| const TextureDataLayout& dataLayout, | |
| const Extent3D& writeSizePixel) { | |
| TextureCopy textureCopy; | |
| textureCopy.texture = destination.texture; | |
| textureCopy.mipLevel = destination.mipLevel; | |
| textureCopy.origin = destination.origin; | |
| textureCopy.aspect = SelectFormatAspects(destination.texture->GetFormat(), destination.aspect); | |
| DeviceBase* device = GetDevice(); | |
| if (textureCopy.aspect == Aspect::Stencil && | |
| (textureCopy.texture->GetFormat().aspects & Aspect::Depth || | |
| device->IsToggleEnabled(Toggle::UseBlitForStencilTextureWrite))) { | |
| // Workaround when write to stencil is unsupported: | |
| // - when the texture is stencil-only but OES_texture_stencil8 is unavailable. | |
| // - when the texture is depth-stencil-combined and writing to the stencil aspect. | |
| // Call WriteTexture to upload data to an intermediate R8Uint texture. | |
| TextureDescriptor dataTextureDesc = {}; | |
| dataTextureDesc.format = wgpu::TextureFormat::R8Uint; | |
| dataTextureDesc.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::TextureBinding; | |
| dataTextureDesc.size = writeSizePixel; | |
| dataTextureDesc.mipLevelCount = 1; | |
| Ref<TextureBase> dataTexture; | |
| DAWN_TRY_ASSIGN(dataTexture, device->CreateTexture(&dataTextureDesc)); | |
| { | |
| ImageCopyTexture destinationDataTexture; | |
| destinationDataTexture.texture = dataTexture.Get(); | |
| destinationDataTexture.aspect = wgpu::TextureAspect::All; | |
| // The size of R8Uint texture equals to writeSizePixel and only has 1 mip level. | |
| // So the x,y,z origins and mipLevel are always 0. | |
| destinationDataTexture.mipLevel = 0; | |
| destinationDataTexture.origin = {0, 0, 0}; | |
| DAWN_TRY_CONTEXT(WriteTextureImpl(destinationDataTexture, data, dataSize, dataLayout, | |
| writeSizePixel), | |
| "writing to stencil aspect of %s using blit workaround when writing " | |
| "to an intermediate r8uint texture.", | |
| textureCopy.texture.Get()); | |
| } | |
| // Blit from R8Uint texture to the stencil texture. | |
| Ref<CommandEncoderBase> commandEncoder; | |
| DAWN_TRY_ASSIGN(commandEncoder, device->CreateCommandEncoder()); | |
| DAWN_TRY_CONTEXT(BlitR8ToStencil(device, commandEncoder.Get(), dataTexture.Get(), | |
| textureCopy, writeSizePixel), | |
| "writing to stencil aspect of %s using blit workaround.", | |
| textureCopy.texture.Get()); | |
| Ref<CommandBufferBase> commandBuffer; | |
| DAWN_TRY_ASSIGN(commandBuffer, commandEncoder->Finish()); | |
| CommandBufferBase* commands = commandBuffer.Get(); | |
| APISubmit(1, &commands); | |
| return {}; | |
| } | |
| SubresourceRange range = GetSubresourcesAffectedByCopy(textureCopy, writeSizePixel); | |
| if (IsCompleteSubresourceCopiedTo(destination.texture, writeSizePixel, destination.mipLevel, | |
| destination.aspect)) { | |
| destination.texture->SetIsSubresourceContentInitialized(true, range); | |
| } else { | |
| DAWN_TRY(ToBackend(destination.texture)->EnsureSubresourceContentInitialized(range)); | |
| } | |
| DoTexSubImage(ToBackend(GetDevice())->GetGL(), textureCopy, data, dataLayout, writeSizePixel); | |
| return {}; | |
| } | |
| void Queue::OnGLUsed() { | |
| mHasPendingCommands = true; | |
| } | |
| ResultOrError<bool> Queue::WaitForQueueSerial(ExecutionSerial serial, Nanoseconds timeout) { | |
| // Search for the first fence >= serial. | |
| return mFencesInFlight.Use([&](auto fencesInFlight) -> ResultOrError<bool> { | |
| Ref<WrappedEGLSync> sync; | |
| for (auto it = fencesInFlight->begin(); it != fencesInFlight->end(); ++it) { | |
| if (it->second >= serial) { | |
| sync = it->first; | |
| break; | |
| } | |
| } | |
| if (sync == nullptr) { | |
| // Fence sync not found. This serial must have already completed. | |
| // Return a success status. | |
| return true; | |
| } | |
| // Wait for the fence sync. | |
| GLenum result; | |
| DAWN_TRY_ASSIGN(result, sync->ClientWait(EGL_SYNC_FLUSH_COMMANDS_BIT, timeout)); | |
| switch (result) { | |
| case EGL_TIMEOUT_EXPIRED: | |
| return false; | |
| case EGL_CONDITION_SATISFIED: | |
| return true; | |
| default: | |
| DAWN_UNREACHABLE(); | |
| } | |
| }); | |
| } | |
| MaybeError Queue::SubmitFenceSync() { | |
| return mFencesInFlight.Use([&](auto fencesInFlight) -> MaybeError { | |
| if (!mHasPendingCommands) { | |
| return {}; | |
| } | |
| DisplayEGL* display = ToBackend(GetDevice()->GetPhysicalDevice())->GetDisplay(); | |
| Ref<WrappedEGLSync> sync; | |
| DAWN_TRY_ASSIGN(sync, WrappedEGLSync::Create(display, mEGLSyncType, nullptr)); | |
| // Signal the sync if it is EGL_SYNC_REUSABLE_KHR. On the other hand, | |
| // EGL_SYNC_FENCE_KHR has its signal scheduled on creation. | |
| if (mEGLSyncType == EGL_SYNC_REUSABLE_KHR) { | |
| DAWN_TRY(sync->Signal(EGL_SIGNALED)); | |
| } | |
| IncrementLastSubmittedCommandSerial(); | |
| fencesInFlight->emplace_back(sync, GetLastSubmittedCommandSerial()); | |
| mHasPendingCommands = false; | |
| return {}; | |
| }); | |
| } | |
| ResultOrError<Ref<SharedFence>> Queue::GetOrCreateSharedFence(ExecutionSerial lastUsageSerial) { | |
| DAWN_ASSERT(mEGLSyncType == EGL_SYNC_NATIVE_FENCE_ANDROID); | |
| // Look for an existing sync that can represent this serial. | |
| Ref<WrappedEGLSync> sync = mFencesInFlight.Use([&](auto fencesInFlight) -> Ref<WrappedEGLSync> { | |
| for (auto it = fencesInFlight->begin(); it != fencesInFlight->end(); ++it) { | |
| if (it->second >= lastUsageSerial) { | |
| return it->first; | |
| } | |
| } | |
| return {}; | |
| }); | |
| Device* device = ToBackend(GetDevice()); | |
| if (sync == nullptr) { | |
| DisplayEGL* display = ToBackend(device->GetPhysicalDevice())->GetDisplay(); | |
| DAWN_TRY_ASSIGN(sync, WrappedEGLSync::Create(display, mEGLSyncType, nullptr)); | |
| } | |
| // If we are sharing this sync externally, make sure to flush all commands. | |
| // The FD cannot be queried before the flush and clients may hang if they try to wait on the | |
| // sync. | |
| const OpenGLFunctions& gl = device->GetGL(); | |
| gl.Flush(); | |
| EGLint fd; | |
| DAWN_TRY_ASSIGN(fd, sync->DupFD()); | |
| DAWN_ASSERT(sync != nullptr); | |
| return AcquireRef(new SharedFenceEGL(ToBackend(GetDevice()), "Internal EGLSync", | |
| wgpu::SharedFenceType::SyncFD, SystemHandle::Acquire(fd), | |
| sync)); | |
| } | |
| bool Queue::HasPendingCommands() const { | |
| return mHasPendingCommands; | |
| } | |
| MaybeError Queue::SubmitPendingCommands() { | |
| DAWN_TRY(SubmitFenceSync()); | |
| return {}; | |
| } | |
| ResultOrError<ExecutionSerial> Queue::CheckAndUpdateCompletedSerials() { | |
| return mFencesInFlight.Use([&](auto fencesInFlight) -> ResultOrError<ExecutionSerial> { | |
| ExecutionSerial fenceSerial{0}; | |
| while (!fencesInFlight->empty()) { | |
| auto [sync, tentativeSerial] = fencesInFlight->front(); | |
| // Fence are added in order, so we can stop searching as soon | |
| // as we see one that's not ready. | |
| GLenum result; | |
| DAWN_TRY_ASSIGN(result, sync->ClientWait(EGL_SYNC_FLUSH_COMMANDS_BIT, Nanoseconds(0))); | |
| if (result == EGL_TIMEOUT_EXPIRED) { | |
| return fenceSerial; | |
| } | |
| // Update fenceSerial since fence is ready. | |
| fenceSerial = tentativeSerial; | |
| fencesInFlight->pop_front(); | |
| DAWN_ASSERT(fenceSerial > GetCompletedCommandSerial()); | |
| } | |
| return fenceSerial; | |
| }); | |
| } | |
| void Queue::ForceEventualFlushOfCommands() { | |
| mHasPendingCommands = true; | |
| } | |
| MaybeError Queue::WaitForIdleForDestruction() { | |
| const OpenGLFunctions& gl = ToBackend(GetDevice())->GetGL(); | |
| gl.Finish(); | |
| DAWN_TRY(CheckPassedSerials()); | |
| DAWN_ASSERT(mFencesInFlight->empty()); | |
| mHasPendingCommands = false; | |
| return {}; | |
| } | |
| } // namespace dawn::native::opengl |